Informes y oposiciones
Esta guía cubre las lecturas enfocadas en registrabilidad y oposiciones del contrato de Tarno:
el informe de registrabilidad de una marca, el timeline de oposiciones de una marca, y el
listado de oposiciones abiertas en una clase de Niza rankeadas por similitud. Los tres endpoints
usan el scope brands:read y tienen equivalente MCP.
El esquema completo y siempre actualizado por campo vive en la referencia OpenAPI interactiva en
/docs(Scalar). Esta guía explica el contrato de informes y oposiciones y enlaza a esa referencia — no reformula el esquema. Cuando un campo no está documentado aquí,/docses autoritativo.
A lo largo de la guía la URL base se escribe como $TARNO_BASE_URL y la clave como $TARNO_API_KEY,
y cada llamada lleva el encabezado X-API-Key — igual que en la
guía de primeros pasos y la guía de autenticación.
Qué cubre esta guía
Tres lecturas, todas bajo /v1 y todas con scope brands:read:
- Informe de registrabilidad (
GET /v1/brands/:nroSolicitud/report) — el detalle de una marca como un superset, con campos extra útiles para evaluar registrabilidad. - Timeline de oposiciones (
GET /v1/brands/:nroSolicitud/opposition) — la secuencia cronológica de eventos de oposición de una marca, más la ventana de oposición derivada (si aplica). - Oposiciones abiertas (
GET /v1/oppositions/open) — marcas con ventana de oposición abierta en una clase Niza, rankeadas por similitud a un portafolio.
Estas rutas son de solo lectura y honestas por diseño: nunca fabrican fechas ni plazos que el corpus no puede sustentar. Cuando un dato no existe, el campo se omite (ver los avisos de honestidad en cada sección).
Informe de registrabilidad
GET /v1/brands/:nroSolicitud/report — scope brands:read. MCP: get_brand_report.
Devuelve un BrandReport, que es un superset del detalle de marca: incluye todo lo que
devuelve GET /v1/brands/:nroSolicitud (clases, personas, anotaciones del Estado-Diario) más los
campos extra útiles para evaluar registrabilidad. Un número de solicitud desconocido devuelve
404 not_found.
Campos extra sobre el detalle base:
| Campo | Tipo | Notas |
|---|---|---|
nroRegistro |
string | omitido | Número de registro. Se omite cuando la marca no tiene registro — nunca llega como null. |
estado |
string | Estado actual de la marca. |
tipoSigno |
string | Tipo de signo (denominativa, mixta, figurativa, …). |
fechaPresentacion |
YYYY-MM-DD |
Fecha de presentación. |
fechaVencimiento |
YYYY-MM-DD | null |
Fecha de vencimiento del registro. |
fechaPublicacion |
YYYY-MM-DD | null |
Fecha de publicación en el Estado-Diario. |
fechaRegistro |
YYYY-MM-DD | null |
Fecha de registro. |
tipoNombre |
string | Nombre del tipo de marca. |
subtipoNombre |
string | null | Nombre del subtipo, si aplica. |
traduccion |
string | null | Traducción declarada del signo, si aplica. |
descripcionEtiqueta |
string | null | Descripción de la etiqueta. |
protectionDescription |
string | null | Descripción del alcance de protección. |
imagenUrl |
string | null | URL de la imagen del signo (marcas mixtas/figurativas). |
renovadaDe |
string | null | Número de solicitud de la marca de la que ésta es renovación. |
renovadaPor |
string | null | Número de solicitud de la marca que renueva a ésta. |
titulares[] |
array\<Persona> | Titulares, separados por rol. Cada persona trae su clasificación, comuna y region (ver abajo). |
representantes[] |
array\<Persona> | Representantes, separados por rol. |
completeness |
objeto | Bloque de honestidad. SIEMPRE presente (ver aviso). |
coverageThrough |
objeto | omitido | Frontera de cobertura. Se omite si no hay instancia enriquecida (ver aviso). |
estadoDerivado |
string | omitido | Estado que el corpus sí puede justificar, del mismo catálogo controlado que estado. Se omite cuando no hay nada que derivar. |
estadoDesactualizado |
boolean | SIEMPRE presente. Cierto cuando el corpus ya tiene la prueba de la concesión que estado todavía no refleja. |
estadono cambia: sigue siendo el valor EN BRUTO de INAPI.estadoDerivadoyestadoDesactualizadoson campos añadidos, nunca una reescritura deestado. Existen porque INAPI asigna el número y la fecha de registro antes de virar el estado: medido, vira al día ~4-6 tras la fecha de registro y sólo llega al 100 % el día 9, mientras la anotación de concesión del Estado-Diario entra el mismo día que se declara. En esa ventana la ficha decía «en trámite, sin registro» sobre un expediente ya titulado.estadoDerivadopublica lo que la evidencia del corpus prueba yestadoDesactualizadoavisa de que los dos no coinciden.
nroRegistrose omite también cuando INAPI manda su centinela de cero. INAPI publica «sin número de registro» como un cero (0numérico en el detalle del Buscador, el texto"0"en el CSV de open-data). Un cero no es un número de registro, así que el informe lo trata igual que a la ausencia: omite el campo. Antes se emitíanroRegistro: "0", que incumplía la promesa de omisión del propio contrato y podía aparecer junto aestadoDerivado: "registrada"en la misma respuesta. Un consumidor que ya comprobaba la presencia del campo no necesita cambiar nada: sólo deja de recibir el cero. El identificador del expediente esnroSolicitud, un campo distinto y siempre presente, que esto no toca.
titulares[] y representantes[] están separados por rol. Una persona cuyo rol es 'ambas'
(titular y representante) aparece en ambos arrays.
Dos bloques de honestidad.
completenessestá siempre presente y declara qué tan completo está el registro devuelto.coverageThroughes la frontera de cobertura de los datos y se omite cuando la marca no tiene una instancia enriquecida — su ausencia significa "sin frontera conocida", nunca se rellena con un valor inventado.
Titularidad actual, e histórico
personas[], titulares[] y representantes[] traen sólo a quien lo es hoy. Una marca que
cambió de manos ya no nombra al dueño anterior entre los actuales — antes lo hacía, y eran dos
titulares igual de vigentes con uno falso.
Los anteriores viven en personasHistoricas[], con dos campos más:
| campo | qué es |
|---|---|
retiradoEn |
la fecha en que INAPI dice que se movió la titularidad, no cuándo lo procesamos |
retiradoPor |
la anotación que lo prueba: A07T transferencia total, A07P parcial, MSO08 cesión, A01 cambio de nombre |
Va siempre presente, y casi siempre vacío. Que NO lo esté es en sí la señal de que la marca cambió de manos.
La copropiedad no es histórico. Dos titulares simultáneos son legítimos y los dos siguen en
personas[]: sólo se retira a alguien cuando existe una anotación que nombra al adquirente.
Clases que cubren la marca, y las que ya no
Una marca se registra en clases, y no viven ni mueren juntas. clases[] trae sólo las que la
cubren hoy; las demás pasan a clasesHistoricas[], con el motivo que da INAPI en estado.
Medido: 10.232 marcas tienen clases en estados mezclados, y 8.220 de ellas figuran
registrada — la marca está viva mientras parte de sus clases fueron rechazadas o no se
renovaron. FARMACIAS AHUMADA (solicitud 1113943) consta registrada con 34 clases: 7 la cubren
y 27 no se renovaron. Antes se listaban las 34 por igual.
| cubre hoy | ya no cubre |
|---|---|
(C) Concedida · (X) En Trámite · (1) Para conceder · (2) Para rechazar |
(N) Rechazada · (A) Abandonada · (D) Desistida · (V) Vencida · (3) No renovada · (O) Cancelada voluntariamente · (U) Anulada · (I) Dividida · (T) Transferida · (P) Transferida Parcialmente |
(2) Para rechazar sigue cubriendo. Es una resolución propuesta, no firme: darla por caída sería
adelantar un desenlace que no ha ocurrido.
(T), (P) e (I) no murieron: se MOVIERON. La clase se transfirió o se dividió y sigue
protegiendo a alguien, pero ya no bajo esta marca — que es lo que aquí se responde.
Sin estado ⇒ sigue en clases[]. No saber no prueba que se haya caído. El estado viaja tal
cual lo publica INAPI, así que podéis juzgar por vuestra cuenta.
Cargas sobre la marca
gravamenes[] responde a qué cargas siguen ahí, no a si alguna vez hubo alguna. Eso último ya
estaba en anotaciones[], pero para leerlo había que saber que A05I inscribe una prenda y A05A
la levanta. Aquí cada inscripción va ya emparejada con el alzamiento que la cerró.
| campo | qué es |
|---|---|
tipo |
prenda, precautoria, embargo, prohibicion u otros |
estado |
activo (inscrita y sin levantar) o alzado |
inscritoEn / alzadoEn |
las dos fechas de INAPI |
aFavorDe |
el acreedor, tomado sólo de la inscripción |
seccionInscripcion / seccionAlzamiento |
los códigos que lo prueban |
alzamientoObservable |
si INAPI publica algún código capaz de levantar esta clase de carga |
Las activas van primero, y de ahí las más recientes.
aFavorDe es el acreedor, nunca el titular. La frase "A Favor de:" cambia de significado entre
los dos lados: en la inscripción nombra a quien recibe la garantía y en el alzamiento a quien
recupera la marca libre. Por eso sólo se lee del lado de la inscripción. Medido: en las prendas, el
"A Favor de:" del alzamiento es un titular de la propia marca el 81,6 % de las veces, y el de la
inscripción sólo el 0,7 %.
⚠ alzamientoObservable: false significa que ese activo no es afirmable. Para prohibición y
otros, INAPI no publica ningún código que las levante: constan sin alzar porque no tenemos por
dónde enterarnos, no porque sigan vivas. No las useis para afirmar que una marca está gravada hoy.
Un alzamiento puede venir sin su inscripción (inscritoEn: null): la carga se levantó y su
inscripción es anterior a lo que el histórico alcanza. Cuenta como alzado, nunca como activa.
Va siempre presente, y casi siempre vacío: de todo el corpus, 2.790 marcas tienen alguna carga registrada y 2.048 alguna activa.
Una licencia no es una carga. A03I/A03A forman una pareja idéntica en forma y quedan fuera a
propósito: es un derecho concedido sobre la marca, no un gravamen contra ella.
Ámbito territorial de las personas
Cada persona de personas[], titulares[] y representantes[] trae dos campos más:
| Campo | Tipo | Notas |
|---|---|---|
comuna |
string | null | Código CUT de comuna, sin cero a la izquierda: '13114' (Las Condes), '5109' (Viña del Mar) |
region |
string | null | Código CUT de región, sin cero a la izquierda: '13', '5' |
comunaNombre |
string | null | El nombre de esa comuna ('Las Condes'), resuelto del catálogo |
regionNombre |
string | null | El nombre de esa región ('Región Metropolitana') |
Los códigos vienen acompañados de su nombre desde 2026-08-25. Siguen sin ser el domicilio —
el domicilio no se expone. Los nombres son los que publica INAPI, con sus propias grafías
(Valparaiso sin tilde); null cuando el código no está en el catálogo, nunca inventados.
Cobertura: 100 % de las regiones y 99,82 % de las comunas.
- La provincia sale del propio código. Rellenad la comuna a 5 dígitos con ceros y quedaos los
3 primeros:
05109→051(provincia de Valparaíso),03301→033(Huasco). No hace falta ninguna tabla para acotar por provincia. 99/99999/999es el centinela de desconocido o extranjero, no una región real.- Van siempre juntas: o las dos nulas, o las dos con valor.
- Cobertura medida (2026-08-24): 70,0 % de los titulares y 96,4 % de los representantes.
Qué clase de parte es cada persona
Desde 2026-09-08 cada persona de personas[], titulares[] y representantes[] trae cinco campos
más, idénticos a los que devuelven el detalle de marca y la búsqueda de personas.
Son aditivos: ningún campo anterior se quitó, se renombró ni cambió de tipo, y no cambió ningún
scope —se leen bajo el mismo brands:read—, así que un consumidor anterior sigue parseando el
informe sin tocar nada.
| Campo | Tipo | Notas |
|---|---|---|
tipo |
natural | juridica | desconocido |
Qué clase de parte es. desconocido es un valor, no un hueco: un tercio del corpus no lleva ni RUT ni sufijo societario, y el contrato lo dice en vez de deducirlo. |
tipoOrigen |
rut | sufijo | manual | omitido |
Qué señal produjo tipo. Cuando no hubo ninguna, la clave desaparece — nunca llega como null. |
observacion |
string | null | La cláusula de jurisdicción que INAPI escribe pegada al nombre («…, SOCIEDAD ORGANIZADA BAJO LAS LEYES DE…»), sacada fuera de nombre. Es prosa de INAPI, con 681 grafías distintas de la misma cláusula: no es vocabulario controlado. |
nombrePublicado |
string | null | La grafía tal como la publicó INAPI. |
revision |
boolean | El corpus marcó la fila para que la mire una persona. El motivo no se publica. |
- Un filtro
tipo = 'natural'no vale para «lo que no es una empresa». Metería unas 130.000 partes que nadie clasificó. Si necesitas certeza, exigetipoOrigen: 'rut'. nombrees el campo con el que se BUSCA;nombrePublicadoes el que se MUESTRA en el informe o se COTEJA contra el documento de INAPI.nombrees la forma canónica (MAYÚSCULAS con tildes); cotejar contranombrePublicadoreintroduce el ruido de mayúsculas y tildes que la canónica quita.- El corpus de personas se reescribió el 2026-09-08: 396.444 nombres pasaron a la forma canónica (~240.000 cambiaron de grafía) y se fusionaron 179 grupos de duplicados sin que ninguna marca cambiara de dueño. Comparar un informe cacheado de antes con uno de hoy mostrará diferencias de mayúsculas y tildes que no son cambios de dato.
Identidad de cada anotación
Cada elemento de anotaciones[] trae un id (string opaco). Es estable: la escritura del
corpus es solo-anexado, así que una anotación conserva su id mientras exista.
!!! warning "Lo que el id NO arregla"
La clave que decide si una anotación llega a escribirse es
(nro_solicitud, tipo, fecha, md5(observacion)) y no incluye la sección. Para un código
fuera de M1..M14 sin observación, eso deja como máximo una anotación por marca y día: una
segunda sección del mismo día no se escribe y, por tanto, tampoco tiene id.
Medido: 5,37 % de lo que la fuente ofrece. Ver el esquema.
!!! danger "historyIncomplete: false no garantiza que no falte ninguna anotación"
Es un negativo estrecho: significa «no hay un diff de Sheets pendiente de confirmar por el
Buscador». No se levanta por un movimiento que no cambia estado, ni por el retraso del
Buscador respecto al Estado Diario, ni por la colisión de clave de arriba.
coverageTier (denso/residual) es la era del corpus, no una afirmación sobre la marca.
Para acotar de verdad lo que se puede afirmar, usad dataAsOf, coverageThrough y
enrichmentPending.
curl
curl -sS "$TARNO_BASE_URL/v1/brands/123456/report" \
-H "X-API-Key: $TARNO_API_KEY"
TypeScript (fetch)
const res = await fetch(`${process.env.TARNO_BASE_URL}/v1/brands/123456/report`, {
headers: { "X-API-Key": process.env.TARNO_API_KEY! },
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.code}: ${error.message} (requestId=${error.requestId})`);
}
const report = await res.json(); // BrandReport — superset del detalle de marca
console.log(report.estado, report.completeness);
// coverageThrough puede venir omitido si no hay instancia enriquecida.
Timeline de oposiciones de una marca
GET /v1/brands/:nroSolicitud/opposition — scope brands:read. MCP: get_brand_opposition.
Devuelve un OppositionTimeline: una lista plana y cronológica (fecha ascendente) de las
anotaciones del Estado-Diario cuyo tipo de sección es 'oposicion', más la ventana de oposición
derivada cuando la marca tiene fecha de publicación. Un número de solicitud desconocido devuelve
404 not_found.
| Campo | Tipo | Notas |
|---|---|---|
nroSolicitud |
string | Número de solicitud de la marca. |
estado |
string | omitido | Estado en bruto (raw). Se omite si es NULL. |
eventos[] |
array\<Evento> | Anotaciones de sección 'oposicion', en orden cronológico (fecha ASC). |
ventanaOposicion |
objeto | omitido | La única fecha derivada. Presente solo si hay fecha de publicación (ver aviso). |
freshnessNote |
string | SIEMPRE presente. Declara honestamente el gap de frescura del Estado-Diario. |
dataAsOf |
string | omitido | Marca temporal de los datos, si aplica. |
coverageThrough |
objeto | omitido | Frontera de cobertura. |
Cada elemento de eventos[]:
| Subcampo | Tipo | Notas |
|---|---|---|
fecha |
YYYY-MM-DD |
Fecha de la anotación. |
seccion |
string | Código de la sección del Estado-Diario. |
seccionNombre |
string | Nombre legible de la sección. |
seccionTipo |
'oposicion' |
Tipo de sección — siempre 'oposicion' en este timeline. |
observacion |
string | Texto de la anotación. |
Y ventanaOposicion (cuando está presente):
| Subcampo | Tipo | Notas |
|---|---|---|
fechaPublicacion |
YYYY-MM-DD |
Fecha de publicación desde la que se cuenta la ventana. |
fechaLimite |
YYYY-MM-DD |
Fecha límite = publicación + 30 días hábiles. |
diasHabilesRestantes |
integer | Días hábiles restantes hasta la fecha límite. |
vencida |
bool | true si la ventana ya venció. |
Honestidad — plazos. Los
eventosno llevan un deadline por-evento: corren desde fechas de notificación que el corpus no captura con precisión, así que no se calcula ninguna fecha límite por evento. La única fecha derivada segura esventanaOposicion= fecha de publicación + 30 días hábiles, y está presente solo si la marca tiene fecha de publicación — nunca se fabrica. Además,freshnessNoteestá siempre presente y declara el gap de frescura del Estado-Diario, para que sepas hasta qué punto el timeline puede estar rezagado respecto de la realidad.
curl
curl -sS "$TARNO_BASE_URL/v1/brands/123456/opposition" \
-H "X-API-Key: $TARNO_API_KEY"
TypeScript (fetch)
const res = await fetch(`${process.env.TARNO_BASE_URL}/v1/brands/123456/opposition`, {
headers: { "X-API-Key": process.env.TARNO_API_KEY! },
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.code}: ${error.message} (requestId=${error.requestId})`);
}
const timeline = await res.json(); // OppositionTimeline
console.log(timeline.freshnessNote); // siempre presente
for (const ev of timeline.eventos) {
console.log(ev.fecha, ev.seccionNombre, ev.observacion);
}
// ventanaOposicion solo existe si la marca tiene fechaPublicacion.
if (timeline.ventanaOposicion) {
console.log("Vence:", timeline.ventanaOposicion.fechaLimite, "vencida:", timeline.ventanaOposicion.vencida);
}
Oposiciones abiertas en una clase
GET /v1/oppositions/open?clase=X&(q=…|nroSolicitud=…) — scope brands:read. MCP: get_open_oppositions.
Lista marcas con ventana de oposición abierta en una clase de Niza, rankeadas por similitud a
un portafolio. El caso de uso: vigilar amenazas de registro en tu clase — marcas recién publicadas
cuya ventana de oposición sigue abierta y que se parecen a algo que quieres proteger. El ranking usa el
mismo modelo de similitud que search / find-similar (fonético + léxico + semántico).
Query:
| Param | Tipo | Notas |
|---|---|---|
clase |
integer | REQUERIDO. Clase de Niza, entero 1–45. |
q |
string | Texto libre. Exactamente uno de q o nroSolicitud (ver aviso). |
nroSolicitud |
string | Marca existente como referencia. Exactamente uno de q o nroSolicitud (ver aviso). |
El xor
q/nroSolicitud. Debes enviarclasey exactamente uno deq(texto libre) onroSolicitud(una marca existente que actúa como referencia de similitud). Enviar ambos o ninguno devuelve400 validation_error.
Respuesta: BrandSummaryPage = { items: BrandSummary[], nextCursor: string | null, dataAsOf? }.
La paginación es por keyset: para la próxima página, reenvía nextCursor sin cambios como cursor;
nextCursor: null = fin — misma semántica de keyset que primeros pasos.
Cada elemento de items es la misma forma BrandSummary documentada en
primeros pasos y en /docs.
curl
curl -sS "$TARNO_BASE_URL/v1/oppositions/open?clase=43&q=cafe%20example" \
-H "X-API-Key: $TARNO_API_KEY"
TypeScript (fetch) — paginando
const baseUrl = process.env.TARNO_BASE_URL!;
const apiKey = process.env.TARNO_API_KEY!;
let cursor: string | null = null;
do {
const url = new URL(`${baseUrl}/v1/oppositions/open`);
url.searchParams.set("clase", "43");
url.searchParams.set("q", "cafe example"); // exactamente uno de q | nroSolicitud
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { "X-API-Key": apiKey } });
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.code}: ${error.message} (requestId=${error.requestId})`);
}
const page = await res.json(); // BrandSummaryPage
for (const brand of page.items) console.log(brand.nroSolicitud, brand.denominacion, brand.score);
cursor = page.nextCursor; // null = fin
} while (cursor);
Scopes
Los tres endpoints requieren el scope brands:read.
Un arreglo de scopes vacío no tiene restricción (misma regla que en el resto del contrato): una
clave sin scopes recibe todos los scopes de lectura, incluido brands:read. Una clave con scopes no
vacíos recibe solo los listados; llamar a una de estas rutas sin brands:read se rechaza con
403 forbidden nombrando el scope faltante.
Consulta la guía de autenticación para el ciclo de vida de la clave y la semántica de scopes.
MCP
REST es equivalente a MCP: misma clave, mismos scopes, mismos esquemas de Zod. El servidor MCP expone estas herramientas:
| Herramienta MCP | Equivalente REST |
|---|---|
get_brand_report |
GET /v1/brands/:nroSolicitud/report |
get_brand_opposition |
GET /v1/brands/:nroSolicitud/opposition |
get_open_oppositions |
GET /v1/oppositions/open |
Consulta la guía de MCP para el endpoint, el transporte y la autenticación.
Errores
Todos los errores usan el sobre uniforme { error: { code, message, requestId } } — descrito en
primeros pasos.
| HTTP | code |
Cuándo |
|---|---|---|
| 401 | unauthorized |
Clave ausente, malformada, desconocida o revocada. |
| 403 | forbidden |
Falta el scope brands:read requerido. |
| 404 | not_found |
Marca inexistente (en report y opposition). |
| 400 | validation_error |
En oppositions/open: falta clase, o no se cumple el xor q / nroSolicitud (ambos o ninguno). |
Ver también
- Autenticación y scopes —
X-API-Key,brands:read, ciclo de vida de la clave. - Conecta tu agente de IA (MCP) — las mismas herramientas de informes y oposiciones vía MCP.
- Primeros pasos — URL base, paginación por keyset, sobre de error,
BrandSummary. - La referencia OpenAPI en vivo en
/docs— el esquema autoritativo por campo.