Busca por campos
Este guia documenta a superfície de busca por campos de GET /v1/brands (e seu gêmeo MCP
search_brands): a lista completa de parâmetros, a semântica AND entre campos, a regra
q + campos, a normalização de RUT, o novo campo matchedFields[] e a ordenação
configurável com sort=. Todos esses parâmetros são aditivos e retrocompatíveis: uma chamada
existente se comporta de forma idêntica, exceto pela mudança deliberada da ordem padrão sem-texto (veja
Ordenação e sort=).
O schema completo e sempre atualizado por campo vive na referência OpenAPI interativa em
/docs(Scalar). Este guia explica a semântica da busca combinada e enlaça a essa referência — não reformula o schema. Quando um campo não está documentado aqui,/docsé autoritativo.
Ao longo do guia a URL base é escrita como $TARNO_BASE_URL e a chave como $TARNO_API_KEY, e cada
chamada leva o cabeçalho X-API-Key — igual ao guia de primeiros passos e
ao guia de autenticação. O endpoint usa o scope brands:read.
O que este guia cobre
GET /v1/brands é uma única rota que serve dois modos livremente combináveis:
- Busca de texto —
q(ounombre) ranqueia pelos canais da denominação (exact / fuzzy / fonético / semântico, fundidos por RRF). - Filtragem por campos — predicados exatos, de intervalo e de pessoa (
clase,estado,nroSolicitud,titular,rut, …) que restringem o conjunto de resultados.
Todos os parâmetros presentes se combinam com AND (veja Semântica AND). A resposta é uma
página por keyset — a mesma forma { items: BrandSummary[], nextCursor } de
primeiros passos.
Já tem uma lista de números de solicitação? Para obter o resumo de várias marcas conhecidas (uma carteira, uma watchlist) de uma vez, não busque: use
POST /v1/brands/batch, que as resolve todas em uma chamada (veja Busca em lote).
Parâmetros
GET /v1/brands aceita estes parâmetros de consulta. Todos são opcionais. Um parâmetro
multivalor é repetido na query (?clase=9&clase=25) e faz OR dentro do campo; campos
distintos fazem AND entre si.
| Param | Tipo | Multivalor | Alias | Notas |
|---|---|---|---|---|
q |
string | não | — | Texto livre (FTS espanhol + fuzzy + fonético + semântico, RRF). É descartado apenas quando chega um campo competidor (nombre/denominacion/nroSolicitud/nroRegistro) → modo avançado; os filtros de restrição (clase, estado, tipoSigno, fechaPresentacion*, titular, representante, rut) mantêm q e apenas o restringem (veja regra q + campos). |
nombre |
string | não | denominacion |
Texto sobre a denominação, com o mesmo motor que q. |
denominacion |
string | não | nombre |
Alias de nombre. |
nroSolicitud |
string | não | — | Número de solicitação, match exato. |
nroRegistro |
string | não | — | Número de registro, match exato. |
titular |
string | sim | — | Substring sobre o nome/razão social do titular (papel titular/ambas), insensível a acentos e maiúsculas. |
representante |
string | sim | — | Substring sobre o nome do representante (papel representante/ambas), insensível a acentos e maiúsculas. |
rut |
string | sim | — | RUT normalizado (veja RUT); match exato contra titular e representante. |
vigencia |
enum | não | vigente |
Limita titular/representante/rut à titularidade atual (vigente), à que já não o é (historica) ou a ambas (todas). Sem um desses três filtros → 400. |
clase |
integer | sim | — | Classe de Nice, 1–45. |
estado |
string | sim | — | Código de estado (catálogo controlado). |
tipoSigno |
string | sim | — | Código de tipo de sinal (catálogo controlado). |
fechaPresentacionDesde |
YYYY-MM-DD |
não | fechaDesde |
Limite inferior da data de apresentação (fecha_presentacion >=), inclusivo. |
fechaPresentacionHasta |
YYYY-MM-DD |
não | fechaHasta |
Limite superior da data de apresentação (fecha_presentacion <=), inclusivo. |
sort |
enum | não | — | Ordenação dos resultados (veja Ordenação e sort=). relevance (=relevancia, padrão) · fechaPresentacion:desc/:asc (=recientes/antiguas) · fechaActualizacion:desc/:asc · denominacion:asc/:desc · estado/estado:asc/estado:desc. |
limit |
integer | não | — | Tamanho da página. Padrão 20, máximo 100. |
cursor |
string | não | — | Cursor de keyset opaco (veja paginação). Trate-o como uma caixa preta. Mutuamente exclusivo com offset. |
offset |
integer | não | — | Paginação por offset (veja paginação). 0–10000. Só com um sort não de relevância; mutuamente exclusivo com cursor. |
Um valor de sort fora do enum retorna 400 validation_error (o envelope de erro uniforme). Um rut
sintaticamente inválido retorna 400 invalid_rut (veja RUT).
Titularidade atual, e como pedir a anterior
titular, representante e rut casam por omissão com quem o é hoje. Uma marca que mudou de
mãos deixa de aparecer ao buscar pelo dono anterior — antes aparecia, e era uma resposta errada:
devolvia uma carteira que essa pessoa já não tem.
vigencia muda a pergunta:
| valor | o que devolve |
|---|---|
vigente (omissão) |
o que essa pessoa tem agora |
historica |
o que teve e já não tem |
todas |
ambas — o comportamento anterior, agora explícito |
curl -sS "$TARNO_BASE_URL/v1/brands?titular=Kangol&vigencia=historica" -H "X-API-Key: $TARNO_API_KEY"
Limita apenas esses três filtros, por isso sem nenhum deles a resposta é 400: não filtraria
nada, e quem pedisse historica e recebesse a omissão leria marcas atuais como se fossem o
histórico.
Quem foi titular antes de uma marca concreta vive em personasHistoricas do detalhe — ver
relatórios.
Semântica AND
Todos os parâmetros presentes se combinam com AND. Cada campo restringe mais o resultado:
# Marcas na classe 9 OU 25 (OR dentro de clase), E cujo titular contenha "sonda",
# E apresentadas a partir de 2020 (AND entre campos).
curl -sS "$TARNO_BASE_URL/v1/brands?clase=9&clase=25&titular=sonda&fechaPresentacionDesde=2020-01-01" \
-H "X-API-Key: $TARNO_API_KEY"
- OR dentro de um campo multivalor:
?clase=9&clase=25= classe 9 ou 25. - AND entre campos distintos: adicionar
titular=restringe aos que também cumprem esse predicado.
Não há OR entre campos nem um DSL com parênteses na v1 — o AND por campos cobre o caso
motivador (advogados de PI restringindo por titular/classe/data). O conjunto vazio de parâmetros
por campos não muda o comportamento em relação ao contrato base.
Regra q + campos (modo avançado)
q é descartado apenas quando chega junto com um campo competidor de texto/identidade —
nombre/denominacion (alias), nroSolicitud ou nroRegistro—: então ativa-se o modo avançado
e a busca combinada por campos assume. Os filtros de restrição (clase, estado, tipoSigno,
fechaPresentacion*, titular, representante, rut) não descartam q: mantêm sua busca de
texto e apenas a restringem com AND. Esta é a interpretação autoritativa do contrato (herdada da
versão de busca combinada):
?q=cafesozinho ⇒ busca de texto livre clássica (ranqueia por relevância, levascore).?q=cafe&clase=43⇒ restrição (não é modo avançado):qé mantido; o resultado é "marcas Café na classe 43" — o texto ranqueia eclaseo restringe —, ordenadas por relevância exceto se houversort=explícito (veja Ordenação).?q=cafe&nombre=nescafe⇒ modo avançado:qé descartado (chegou um campo competidor); o texto denombre=conduz a busca combinada por campos, ordenado pelo modo de ordenação vigente (veja Ordenação).- Para buscar por denominação dentro do modo avançado, use
nombre=(nãoq=):nombre=é um campo competidor e sim participa doAND.
Em outras palavras: q é o modo simples de uma única caixa de texto; assim que você combina texto com
um campo competidor (nombre/nroSolicitud/nroRegistro), mude para nombre para o texto.
Restringir por clase/estado/datas/pessoas não requer mudança: q continua vigente e apenas é
restringido.
Ordenação e sort=
O parâmetro sort= escolhe a ordem dos resultados. sort= explícito sempre prevalece sobre a
ordem automática:
sort |
Ordem | Paginação | Notas |
|---|---|---|---|
relevance (=relevancia, padrão) |
Por relevância (RRF) quando há texto; senão fecha_presentacion DESC |
keyset | Com texto ranqueia pelos canais da denominação; sem texto cai para data DESC de forma silenciosa (não é erro). |
fechaPresentacion:desc (=recientes) |
fecha_presentacion DESC, nro_solicitud ASC |
keyset ou offset | Prevalece mesmo com texto: o texto continua filtrando, mas a ordem final é por data. |
fechaPresentacion:asc (=antiguas) |
fecha_presentacion ASC, nro_solicitud ASC |
keyset ou offset | Espelho determinístico. |
fechaActualizacion:desc / :asc |
Pela última atualização do processo (updated_at) |
offset | O campo updated_at é atualizado a cada re-scrape/upsert da marca. |
denominacion:asc / :desc |
Alfabético pela denominação (insensível a acentos e maiúsculas) | offset | Ordem com acento/caixa normalizados (Ñ, acentos). |
estado / estado:asc / estado:desc |
Agrupação pelo ciclo de vida do processo | offset | Não alfabético. Ordem: en trámite → observación de fondo → publicada → oposición → concedida → registrada → esperando renovación → rechazada → denegada → desistida → abandonada → anulada → caducado → vencida; estado desconhecido/nulo por último. |
Um sort fora da lista devolve 400 validation_error. Os aliases relevancia/recientes/antiguas
continuam válidos (equivalem a relevance/fechaPresentacion:desc/fechaPresentacion:asc).
Ordem padrão (sem sort=):
- Com texto (
qounombre) ⇒ relevância (RRF). - Sem texto ⇒
fecha_presentacion DESC, nro_solicitud ASC(mais recentes primeiro).
Mudança de comportamento. A listagem sem-texto (
?clase=,?estado=sozinhos) mudou sua ordem padrão denro_solicitud ASCparafecha_presentacion DESC, nro_solicitud ASC(mais recentes primeiro). Se você precisa da ordem antiga por número de solicitação, hoje não há um valor desortpara isso; a ordem estável disponível é por data. Os cursores de keyset em trânsito de listagens sem-texto reiniciam uma vez após a mudança (umcursor=antigo de uma listagem sem-texto volta à página 1; basta reiniciar a paginação). Veja o changelog para a nota de versão.
Marcas com fecha_presentacion nula ficam ao final em ambos os modos (recientes e antiguas).
Teto de alcance com texto (F-4). Com texto (
q/nombre/denominacion),recientes/antiguasreordenam por data o conjunto fundido limitado por relevância (≈600 marcas: os melhores candidatos de cada canal), não todo o corpus que casa. Ou seja,?q=<termo comum>&sort=recientesdevolve as mais novas entre os matches de maior relevância, não as mais novas de todos os matches. A listagem sem texto (sort=semq) não tem esse teto: pagina o corpus completo por data sobre o índice de data. Para paginação por data sobre todo o corpus, liste sem texto (ou restrinja porclase/estado/fechaPresentacion*).
matchedFields[] — por que esta marca apareceu
Cada elemento de items[] pode incluir matchedFields[], um array aditivo que responde a "por
que esta marca apareceu?". É opcional e omitido quando não há match de identidade/texto (por ex.
uma listagem pura por ?clase=9, que apenas restringe).
Cada entrada é { field, kind }:
field— o campo de identidade/busca que deu match. Enum fechado:nombre,nroSolicitud,nroRegistro,titular,representante,rut. Os filtros de restrição (clase,estado,tipoSigno,fechaPresentacion*) nunca aparecem — são critérios de restrição, não de match.kind— o canal pelo qual deu match. Enum:exact,fuzzy,phonetic,semantic.nroSolicitud,nroRegistro,rut⇒ sempreexact.titular,representante⇒fuzzy(substring insensível a acentos/maiúsculas).nombre⇒ o canal dominante quando deu match por vários ao mesmo tempo, segundo a hierarquiaexact > fuzzy > phonetic > semantic. Uma única entrada por campo (nunca uma por canal).
{
"nroSolicitud": "123456",
"denominacion": "SONDA",
"score": 0.91,
"matchedFields": [
{ "field": "nombre", "kind": "exact" },
{ "field": "titular", "kind": "fuzzy" }
]
}
matchedFields[] é puramente aditivo: a forma existente de items[] não muda em nenhum outro
aspecto. Um cliente que não o leia continua funcionando igual.
RUT: normalização, invalid_rut e cobertura
O parâmetro rut dá match exato contra persona.identificador de titular e representante
(AND com o resto dos predicados). O servidor normaliza o valor antes de comparar, então aceitamos
os três formatos habituais:
| Entrada | Normaliza para |
|---|---|
76.123.456-7 |
76123456-7 |
761234567 |
76123456-7 |
76123456-7 |
76123456-7 |
O DV pode ser 0–9 ou K (maiúsculo).
⭐ Mudança de 2026-09-06: o
rutde busca já NÃO exige que o módulo 11 feche. O corpus guarda 576 pessoas cujo identificador não passa no dígito verificador — arrastam 4.631 marcas, 686 como titular vigente — de modo que o único valor capaz de encontrá-las era exatamente o que a API rejeitava. O trabalho de um filtro é encontrar o que está armazenado; o DV é uma propriedade do dado guardado, não da consulta. Umrutbem formado cujo DV não calcula devolve agora um resultado vazio em vez de400.Erro
invalid_rut. Umrutcuja forma não é a de um RUT (letras no corpo, ou um DV fora de0–9/K) retorna400com{ "error": { "code": "invalid_rut" } }— o envelope de erro uniforme. Nunca500nem um conjunto de resultados vazio silencioso. Esta validação e este código de erro são idênticos em REST e em MCP.Cobertura parcial do RUT. Nem todas as marcas do corpus têm um RUT associado à sua pessoa. A título orientativo, cerca de ~43% das marcas têm RUT no titular e ~71% o têm no representante. Um
rutválido que não está no corpus retorna um resultado vazio — isto está correto (cobertura parcial), não é um erro. Combinerutcomtitular/nombrequando quiser cobrir marcas sem RUT registrado.
Paginação: keyset e offset
Há dois modos de paginação. Escolha um; misturá-los (cursor e offset na mesma
requisição) devolve 400 validation_error.
Keyset (por cursor) — o modo padrão
Estável e sem saltos, para sort=relevance (ou sem sort) e as ordens por data (fechaPresentacion:*
/ recientes / antiguas). Cada resposta leva um nextCursor:
- Para a próxima página, reenvie
nextCursorsem alterações como parâmetrocursor. nextCursor: nullsignifica fim dos resultados — pare.- O cursor é opaco (base64url das chaves de ordenação). Trate-o como uma caixa preta; sua forma interna depende do modo de ordenação vigente e pode mudar sem aviso.
sort=relevance (ou sem sort) pagina apenas por keyset: não aceita offset.
Offset (por deslocamento) — para pular para a página N
Para as ordens fechaActualizacion:*, denominacion:* e estado[:*] — e, se preferir, também as de
data — use offset + limit:
offseté o número de resultados a pular (0–10000);limité o tamanho da página.- No modo offset,
nextCursorénull: para avançar, incrementeoffset(p. ex. página 3 comlimit=20⇒offset=40). UseestimatedTotal(presente em todos os modos) para saber quantas páginas há. - O teto de
offseté 10000 (protege o banco de dados): umoffsetmaior devolve400 validation_error. Para ir mais longe, restrinja com filtros (clase/estado/datas/texto).
Regras (tudo que é inválido é 400 validation_error, nunca ignorado em silêncio):
| Combinação | Resultado |
|---|---|
cursor e offset juntos |
400 validation_error |
offset com sort=relevance (ou sem sort e com texto) |
400 validation_error (relevância é só keyset) |
cursor com fechaActualizacion/denominacion/estado |
400 validation_error (essas ordens são só offset) |
offset > 10000 |
400 validation_error |
Compatibilidade: as chamadas atuais (sem
sortou comrelevancia/recientes/antiguas, e comcursor) continuam funcionando exatamente igual — o modo offset é puramente aditivo.
Veja primeiros passos para o padrão completo de paginação.
Busca em lote (batch)
Quando você já tem uma lista de números de solicitação — uma carteira, uma watchlist — e quer o
resumo de todas de uma vez, use POST /v1/brands/batch em vez de N chamadas a
GET /v1/brands/{nroSolicitud}. Ele resolve até 100 números de solicitação em uma chamada que
consome uma única unidade de cota (não uma por marca). Mesmo scope brands:read.
Corpo (application/json): { "nros": [ … ] }.
curl -sS -X POST "$TARNO_BASE_URL/v1/brands/batch" \
-H "X-API-Key: $TARNO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"nros":["1286842","823470","9999999"]}'
Resposta (200):
{
"items": [
{ "nroSolicitud": "1286842", "denominacion": "…", "estado": "…", "clases": [9, 42], "titular": ["…"], "enrichmentPending": false },
{ "nroSolicitud": "823470", "denominacion": "…", "estado": "…", "clases": [30], "titular": [], "enrichmentPending": false }
],
"notFound": ["9999999"],
"dataAsOf": "2026-07-20T03:17:00.000Z"
}
items— osBrandSummaryencontrados, na mesma ordem em que você pediu osnros. É o mesmo resumo enriquecido (clases,titular,enrichmentPending) que a listagem deGET /v1/brandsdevolve. Como não há texto de busca, eles não levamscore,matchedBynemmatchedFields.notFound— osnrossolicitados que não existem no corpus (na ordem de solicitação). Não é um erro: você pediu uma marca inexistente e nós dizemos isso explicitamente.dataAsOf— o mesmo carimbo de frescor opcional do resto do contrato.
Regras:
nrosrepetidos colapsam em um (um número aparece uma vez, emitemsou emnotFound).- Devolve o resumo, não o detalhe. Para o detalhe completo de uma marca (classes + pessoas +
anotações) use
GET /v1/brands/{nroSolicitud}; o batch é para revisar muitas marcas de uma vez, não o processo de uma. - Limite de 100. Um batch com mais de 100
nrosretorna422 invalid_input. Um corpo comnrosvazio ou malformado retorna400 validation_error. - Uma unidade de cota por chamada, independentemente de quantos
nrosvocê pedir — esse é o ponto: evita o N+1 (e o gasto de N unidades de cota) de N chamadas individuais.
Paridade MCP
REST é equivalente a MCP: mesma chave, mesmos scopes, mesmos schemas Zod, mesma camada de
consultas. A ferramenta MCP search_brands aceita exatamente os mesmos parâmetros por campos
documentados aqui — incluindo sort, a normalização de rut e o erro invalid_rut — com idêntica
semântica. A resposta (incluindo matchedFields[] e a ordenação) é byte-idêntica à do REST, verificado
com um teste de paridade deep-equal contra Postgres real. A busca em lote tem seu próprio gêmeo,
get_brands_batch, com a mesma forma { items, notFound, dataAsOf } e o mesmo limite de 100
(invalid_input).
| Ferramenta MCP | Equivalente REST |
|---|---|
search_brands |
GET /v1/brands |
get_brands_batch |
POST /v1/brands/batch |
Veja o guia de MCP para o endpoint, o transporte e a autenticação.
Erros
Todos os erros usam o envelope uniforme { error: { code, message, requestId } } — descrito em
primeiros passos.
| HTTP | code |
Quando |
|---|---|---|
| 400 | validation_error |
Parâmetros inválidos (por ex. sort fora do enum, clase fora de 1–45, data malformada, corpo de batch com nros vazio). |
| 400 | invalid_rut |
Um rut cuja forma não é a de um RUT (letras no corpo, DV fora de 0–9/K). Um DV que não calcula já não é erro: devolve vazio. |
| 422 | invalid_input |
Um batch (POST /v1/brands/batch) com mais de 100 nros (veja Busca em lote). |
| 401 | unauthorized |
Chave de API ausente, malformada, desconhecida ou revogada. |
| 403 | forbidden |
Falta o scope brands:read requerido. |
| 429 | rate_limited / quota_exceeded |
Limite de rajada ou cota mensal excedidos. |
Veja também
- Primeiros passos — URL base, primeira chamada, paginação por keyset, envelope de erro.
- Autenticação e scopes —
X-API-Key,brands:read, ciclo de vida da chave. - Conecte seu agente de IA (MCP) — a mesma busca via a ferramenta
search_brands. - Exemplos de código — clientes REST e MCP prontos para copiar e colar.
- A referência OpenAPI ao vivo em
/docs— o schema autoritativo por campo.