Saltar a contenido

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í, /docs es 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.

estado no cambia: sigue siendo el valor EN BRUTO de INAPI. estadoDerivado y estadoDesactualizado son campos añadidos, nunca una reescritura de estado. 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. estadoDerivado publica lo que la evidencia del corpus prueba y estadoDesactualizado avisa de que los dos no coinciden.

nroRegistro se omite también cuando INAPI manda su centinela de cero. INAPI publica «sin número de registro» como un cero (0 numé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ía nroRegistro: "0", que incumplía la promesa de omisión del propio contrato y podía aparecer junto a estadoDerivado: "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 es nroSolicitud, 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. completeness está siempre presente y declara qué tan completo está el registro devuelto. coverageThrough es 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: 05109051 (provincia de Valparaíso), 03301033 (Huasco). No hace falta ninguna tabla para acotar por provincia.
  • 99 / 99999 / 999 es 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, exige tipoOrigen: 'rut'.
  • nombre es el campo con el que se BUSCA; nombrePublicado es el que se MUESTRA en el informe o se COTEJA contra el documento de INAPI. nombre es la forma canónica (MAYÚSCULAS con tildes); cotejar contra nombrePublicado reintroduce 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 eventos no 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 es ventanaOposicion = 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, freshnessNote está 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 145.
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 enviar clase y exactamente uno de q (texto libre) o nroSolicitud (una marca existente que actúa como referencia de similitud). Enviar ambos o ninguno devuelve 400 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 scopesX-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.