Skip to content

Getting started

IRIS exposes a neutral read contract over the INAPI trademark corpus: a versioned REST API plus an MCP server, both backed by the same query layer and the same data. This guide gets you from zero to a first authenticated call against the REST API.

The full, always-current per-field schema lives in the interactive OpenAPI reference at /docs (Scalar). This guide explains how to onboard and links to that reference — it does not restate the schema. When a field is not documented here, /docs is authoritative.

1. Base URL and the /v1 prefix

The REST API is served by your operator over HTTPS. Every endpoint of the read contract lives under the /v1 version prefix, e.g. https://<iris-host>/v1/brands. Throughout this guide the base URL is written as the env var IRIS_BASE_URL — substitute the host your operator gave you.

export IRIS_BASE_URL="https://<iris-host>"

A handful of paths are intentionally public (no key required):

Path Purpose
/health Liveness / readiness probe (200 ok / 503 degraded)
/docs Interactive OpenAPI (Scalar) reference
/metrics Prometheus metrics (internal network only)

Everything under /v1 is protected and requires an API key (see below and the auth guide).

2. Get an API key

API keys are issued by the operator, out of band, via the admin CLI. There is no self-service signup endpoint — you do not create your own key. Ask your operator for a reader key; they will hand you an opaque key string and tell you which scopes it carries (brands:read, insights:read, or unrestricted). See the auth guide for scopes, lifecycle (rotate / revoke / expiry), and the exact error semantics.

Store the key as an env var and never commit it to source control:

export IRIS_API_KEY="iris_xxx"   # the opaque key string your operator issued

All examples in this portal use the obvious placeholder iris_xxx. Replace it with your real key at runtime; never hard-code a key into a committed file.

3. Your first authenticated call: GET /v1/brands

Send the key in the X-API-Key request header. GET /v1/brands searches or lists brand summaries.

curl

curl -sS "$IRIS_BASE_URL/v1/brands?q=cafe&limit=5" \
  -H "X-API-Key: $IRIS_API_KEY"

TypeScript (fetch)

const baseUrl = process.env.IRIS_BASE_URL!;
const apiKey = process.env.IRIS_API_KEY!; // "iris_xxx"

const res = await fetch(`${baseUrl}/v1/brands?q=cafe&limit=5`, {
  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(); // { items: BrandSummary[], nextCursor: string | null }
console.log(page.items.length, "results; nextCursor =", page.nextCursor);

A successful response is a keyset page:

{
  "items": [
    {
      "nroSolicitud": "123456",
      "nroRegistro": null,
      "denominacion": "CAFE EXAMPLE",
      "estado": "...",
      "tipoSigno": "...",
      "fechaPresentacion": "2023-01-15",
      "fechaVencimiento": null,
      "score": 0.0163
    }
  ],
  "nextCursor": "b2Zmc2V0OjUw"
}

score is the fusion (RRF) relevance score and is present only on the free-text search path (q=); it is omitted on a plain filtered listing. For the exact field set of BrandSummary and BrandDetail, see the /docs reference.

4. Filtering and keyset pagination

GET /v1/brands accepts these query parameters:

Param Type Notes
q string Free-text search (full-text + fuzzy). When present, results are ranked and carry score. Omit it for a plain filtered listing.
clase integer Niza class filter, 145.
estado string Status code (controlled catalog).
tipoSigno string Sign-type code (controlled catalog).
fechaDesde YYYY-MM-DD Lower bound on the filing date (fecha_presentacion >=).
fechaHasta YYYY-MM-DD Upper bound on the filing date (fecha_presentacion <=).
limit integer Page size. Default 20, maximum 100.
cursor string Opaque keyset cursor (see below). Treat it as a black box.

Pagination is keyset-based, not offset-based. Each response carries a nextCursor:

  • To fetch the next page, pass it back unchanged as the cursor query param.
  • nextCursor: null means end of results — stop.
# page 1
curl -sS "$IRIS_BASE_URL/v1/brands?q=cafe&limit=20" -H "X-API-Key: $IRIS_API_KEY"
# page 2 — pass the nextCursor from page 1
curl -sS "$IRIS_BASE_URL/v1/brands?q=cafe&limit=20&cursor=<nextCursor>" -H "X-API-Key: $IRIS_API_KEY"

To fetch one brand in full (classes + persons + Estado-Diario annotations), call GET /v1/brands/:nroSolicitud:

curl -sS "$IRIS_BASE_URL/v1/brands/123456" -H "X-API-Key: $IRIS_API_KEY"

An unknown application number returns 404 not_found.

The insights:read family (GET /v1/freshness, GET /v1/sync-runs) is documented in the auth guide and the /docs reference.

5. The uniform error envelope

Every error — validation, auth, not-found, rate-limit, and unexpected server errors — is returned as one uniform shape:

{ "error": { "code": "unauthorized", "message": "Missing or invalid API key", "requestId": "..." } }
  • code — a stable, neutral token you can switch on (see the table below).
  • message — a human-readable description. 5xx responses use a generic message and never leak internal details.
  • requestId — correlates your report with the server log. It is also echoed on the x-request-id response header; you may supply your own via the X-Request-Id request header.
HTTP code When
400 validation_error Invalid query / params (failed Zod validation).
401 unauthorized Missing, malformed, unknown, or revoked API key.
403 forbidden Valid key, but it lacks the scope the route requires.
404 not_found Unknown brand application number, or an unknown route.
429 rate_limited Short-term burst limit exceeded (see below).
429 quota_exceeded Monthly quota exhausted (see below). Carries Retry-After.
500 internal_error Unexpected server error (no internal details are leaked).

6. Rate limits and quota

Two independent limits protect the read contract:

  1. Short-term burst limit — a per-key request rate cap (default 100 requests / minute). Exceeding it returns HTTP 429.
  2. Monthly quota — a per-key monthly request budget. When you exhaust it the API returns HTTP 429 with code: "quota_exceeded" and a Retry-After header (seconds until the quota window resets at month end). This same quota applies identically on REST and on the MCP — see the MCP guide.
HTTP/1.1 429 Too Many Requests
Retry-After: 1209600
Content-Type: application/json

{ "error": { "code": "quota_exceeded", "message": "Monthly quota exceeded", "requestId": "..." } }

Honour Retry-After: wait the indicated number of seconds before retrying.

Next steps

  • Authentication and scopesX-API-Key, brands:read / insights:read, key lifecycle, and the full 401 / 403 / 429 semantics.
  • Connect your AI agent (MCP) — the mcp.obviouy.com endpoint and the search_brands / get_brand_detail tools.
  • Code samples — copy-paste REST and MCP clients.
  • The live OpenAPI reference at /docs — the authoritative per-field schema.