> ## Documentation Index
> Fetch the complete documentation index at: https://docs.closient.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Closient OpenAPI Tour

> Bird's-eye tour of the 22 per-app OpenAPI surfaces under Closient — what each app owns, where to find a specific endpoint via the RFC 9727 API catalog, and the cross-cutting conventions (auth, pagination, error envelope, rate-limit headers, idempotency) that apply uniformly.

Closient is API-first and per-app. Each Django app owns its own
`NinjaAPI` instance, mounted at `/{app}/api/v1/`, with its own OpenAPI
3.1 document at `/{app}/api/v1/openapi.json`. This skill is the map
that gets an agent from "which app owns this concept?" to "this is
the endpoint I want to hit" without grepping templates.

## When to use

* An agent knows *what data* it needs but not which Closient app owns
  that data.
* A developer is writing an SDK or MCP server and needs to enumerate
  every API surface.
* The agent hit a `404 Not Found` and wants to confirm the endpoint
  path before assuming the API is broken.

For zero-to-first-call setup (auth, scopes, PKCE), run
`closient-api-quickstart` first.

## Discover everything from the API catalog

The canonical enumeration is the RFC 9727 linkset at
`/.well-known/api-catalog`:

```
GET /.well-known/api-catalog
Accept: application/linkset+json
```

Each entry has:

* `anchor` — the API's base URL (e.g. `https://www.closient.com/products/api/v1/`).
* `service-desc[0].href` — the OpenAPI document URL.
* `service-desc[0].type` — `application/openapi+json`.

Programmatically:

```python theme={null}
import httpx
catalog = httpx.get("https://www.closient.com/.well-known/api-catalog").json()
for entry in catalog["linkset"]:
    print(entry["anchor"], entry["service-desc"][0]["href"])
```

The catalog is generated from `APP_API_REGISTRY` in
`backend/apps/core/api/factory.py` — that's the source of truth if you're
inside the monorepo.

## App-by-app — what each API owns

| Mount                     | Label          | Owns                                                                                                   |
| ------------------------- | -------------- | ------------------------------------------------------------------------------------------------------ |
| `/account/api/v1/`        | Account        | Users, organizations, API keys, RBAC, OAuth-connected apps, whoami. Start here to identify the caller. |
| `/agent/api/v1/`          | Agent          | LLM-powered conversational endpoints (chat, search refinement, tool-use orchestration).                |
| `/analytics/api/v1/`      | Analytics      | Scan events, search performance snapshots, brand engagement summaries. Read-heavy.                     |
| `/billing/api/v1/`        | Billing        | Stripe subscription state, usage metrics, plan + tier endpoints. Read-only for non-owners.             |
| `/brands/api/v1/`         | Brands         | Brand records, ownership/claim flow, brand-level analytics. Write requires `brands:write`.             |
| `/byos/`                  | BYOS           | "Bring your own search" — embedded search widget endpoints (not v1 prefixed by convention).            |
| `/certifications/api/v1/` | Certifications | Product certifications (organic, gluten-free, Fair Trade, …).                                          |
| `/compliance/api/v1/`     | Compliance     | Recalls, safety notices, FSMA 204 traceability (sibling to `/epcis/api/2.0/`).                         |
| `/content/api/v1/`        | Content        | Wagtail-backed pages, blog posts, feeds, legal pages.                                                  |
| `/dashboard/api/v1/`      | Dashboard      | Aggregated views consumed by the Closient web UI. Not a public-facing API.                             |
| `/epcis/api/2.0/`         | EPCIS 2.0      | GS1 EPCIS 2.0 event capture + query. Path mounted at `/api/2.0/` per spec, not `/v1/`.                 |
| `/integrations/api/v1/`   | Integrations   | POS connections (Shopify, Square, Clover, Lightspeed), webhooks.                                       |
| `/locations/api/v1/`      | Locations      | Physical store/service locations with PostGIS geometry.                                                |
| `/notifications/api/v1/`  | Notifications  | Multi-channel notification delivery (email, Slack, SMS).                                               |
| `/products/api/v1/`       | Products       | Products (GTINs), images, validators, catalog API, categories, QR rendering.                           |
| `/resolver/api/v1/`       | Resolver       | GS1 Digital Link resolution + linkset queries + serial verification.                                   |
| `/retailers/api/v1/`      | Retailers      | Offers (in-store + online), inventory sources, online-retailer integrations.                           |
| `/scanner/api/v1/`        | Scanner        | Barcode + product scanning, vision-extracted attributes.                                               |
| `/search/api/v1/`         | Search         | Catalog search, session-based local-first search, refinement.                                          |
| `/sustainability/api/v1/` | Sustainability | Sustainability certifications + impact tracking.                                                       |
| `/voice-feedback/api/v1/` | QA Feedback    | Speech-to-text user feedback + reproducer playback.                                                    |

The list is canonical at `backend/apps/core/api/factory.py:APP_API_REGISTRY`.
When that list changes, the catalog updates automatically; you don't
need to re-grep.

## Find an endpoint across the 22 APIs

Three strategies, fastest first:

### 1. Browse Mintlify docs

Interactive HTML docs aggregate every per-app OpenAPI spec at
`https://docs.closient.com`. Search the sidebar by resource name —
"product", "brand", "offer", "recall" — and you'll get the canonical
endpoint URL with auth, parameters, and response schemas.

### 2. Fetch the OpenAPI doc directly

For machine consumption (SDK generation, MCP tool registration), grab
the per-app spec straight from the catalog and parse it:

```python theme={null}
spec = httpx.get(
    "https://www.closient.com/products/api/v1/openapi.json"
).json()
for path, methods in spec["paths"].items():
    for verb, op in methods.items():
        print(verb.upper(), path, "—", op.get("summary"))
```

Every per-app spec follows the same shape (Django Ninja → OpenAPI 3.1).

### 3. Search the linkset

The catalog itself is linkset-shaped, so you can grep through
`anchor` and `service-desc.href` to find candidate apps before fetching
their specs. Useful in an MCP / tool-use context where dropping every
spec into a prompt is wasteful.

## Cross-cutting conventions (apply to every per-app API)

### Authentication

Every API accepts the same three auth mechanisms; pick one:

| Mechanism              | Header / cookie                 |
| ---------------------- | ------------------------------- |
| API key                | `X-API-Key: csb_<body>_<crc32>` |
| OAuth 2.0 access token | `Authorization: Bearer <token>` |
| Session (browser only) | Django `sessionid` cookie       |

Endpoints that need no auth (catalog search, public resolver, well-known
endpoints) declare `auth=None` in the OpenAPI spec and accept anonymous
calls subject to a tighter IP-based throttle.

### Pagination

List endpoints use **limit/offset** pagination (cursor pagination is
NOT in use):

```
GET /products/api/v1/products?limit=50&offset=100
```

Response envelope:

```json theme={null}
{
  "items": [...],
  "count": 50,
  "total": 1234,
  "limit": 50,
  "offset": 100
}
```

Defaults: `limit=25`, max `limit=100`. Implemented by the shared
`apps.core.api.pagination.LimitOffsetPagination`.

### Error envelope (RFC 9457 Problem Details)

All errors return `application/problem+json`:

```json theme={null}
{
  "type": "https://www.closient.com/problems/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "User does not have brands:write scope on this token.",
  "instance": "/brands/api/v1/brands/abc123"
}
```

`type` is a stable URI (you can build clients that switch on it);
`title` is human-readable; `detail` may include user-context
information. Wired by `apps.core.api.exceptions.register_error_handlers`.

### Rate-limit headers

Every response carries IETF `RateLimit-*` headers:

| Header                | Value                                                         |
| --------------------- | ------------------------------------------------------------- |
| `RateLimit-Policy`    | All active windows, e.g. `300;w=60, 10000;w=86400`.           |
| `RateLimit-Limit`     | Quota for the most-restrictive currently-active window.       |
| `RateLimit-Remaining` | Requests left in that window.                                 |
| `RateLimit-Reset`     | Seconds until that window resets (relative; clock-skew safe). |

Legacy `X-RateLimit-*` aliases are also emitted; `X-RateLimit-Reset`
keeps the absolute Unix-timestamp shape for back-compat. Implemented in
`apps.core.api.throttling`. Default: 300/min, 10k/day per key.

### Idempotency

Mutating endpoints accept `Idempotency-Key` headers where the operation
is naturally idempotent (POSTs that look like upserts, batch ingest
endpoints). The key is opaque to Closient; same key + same body returns
the original response. Check the per-endpoint OpenAPI description for
the explicit `Idempotency-Key` parameter declaration before relying on
it — not every POST is idempotent.

### Empathy contract

Every `Field()` in every per-app schema carries a `description=` (or
auto-pipes from the model's `help_text` via `ModelDocumentedSchema`),
and enum-shaped names are typed as `StrEnum`s with the values declared
on the schema. SDK generators get a real `enum` constraint instead of a
bare string. See `backend/apps/core/CLAUDE.md` "API Empathy" for the
internal contract.

### OpenAPI specs are versioned per app

When an app evolves a breaking change, expect a `/api/v2/` mount
alongside `/api/v1/`. Today only EPCIS 2.0 lives outside the `v1`
convention (it sits at `/api/2.0/` to match the GS1 EPCIS spec naming).
There is no global `/v1/`.

## Cross-app patterns to recognise

### "List then detail"

For most resources, list endpoints return a slim representation; detail
endpoints return the full one:

```
GET /products/api/v1/products       → ProductListItem (slim)
GET /products/api/v1/products/{id}  → ProductDetail   (full)
```

Don't filter the list response client-side hoping to find a field —
fetch the detail endpoint or use the documented query parameters.

### Short IDs in URLs

Public-facing resources use a 22-character `ShortUUIDField` for both
PKs and URL paths (see `apps.core.fields.ShortUUIDField`):

```
GET /brands/api/v1/brands/4Vh87cAnSyrSt5KQ8s9hkF
```

Short IDs are accepted in both shortuuid and full-UUID form; the URL
converter normalizes. New code emits the short form.

### Scoped to organization

Resources owned by an organization (Products, Brands, Offers, Locations,
EPCIS events, …) are filtered by the caller's organization membership.
Cross-org reads return `404 Not Found` rather than `403`, to avoid
leaking existence.

### Webhooks vs polling

Closient prefers webhooks over polling for state changes. Subscribe via
`/notifications/api/v1/webhooks` (signed with HMAC-SHA256). Endpoints
that emit events are documented in each per-app OpenAPI spec; common
ones: scanner events, recall notices, offer changes, EPCIS captures.

## Putting it all together

A typical agent boot sequence:

1. Hit `/.well-known/api-catalog` to learn what's available.
2. Hit `/.well-known/openid-configuration` to learn how to auth.
3. Run `closient-api-quickstart` to register / authenticate.
4. `GET /account/api/v1/whoami` to confirm the token's identity.
5. Walk per-app OpenAPI specs from the catalog to bind tools.

Once tools are bound, the per-workflow skills (`local-product-search`,
`resolve-gtin`, `claim-brand`, `capture-epcis-event`, …) tell the
agent *what to do* with them.

## Related skills

* `closient-api-quickstart` — auth, scopes, first call.
* `decode-gs1-ai`, `build-gs1-digital-link` — GS1 identifier mechanics.
* `local-product-search`, `resolve-gtin`, `check-product-availability` —
  search + product depth.
* `claim-brand`, `onboard-retailer` — onboarding workflows.
* `capture-epcis-event`, `query-epcis-events`, `manage-recall`,
  `check-fsma-204-readiness` — supply-chain and compliance.
* `brand-scan-summary`, `brand-search-demand`, `brand-retail-footprint`
  — brand analytics.

## Authoritative references

* RFC 9727 — API Catalog / Linkset. `https://datatracker.ietf.org/doc/html/rfc9727`
* RFC 9457 — Problem Details for HTTP APIs.
* IETF `RateLimit-*` headers (informed governor draft).
* OpenAPI 3.1 spec — generated docs at `https://docs.closient.com`.
* Closient internal: `backend/apps/core/api/factory.py`,
  `backend/apps/core/api/pagination.py`,
  `backend/apps/core/api/throttling.py`,
  `backend/apps/core/api/exceptions.py`.
