Skip to main content

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.

The meta-skill. If your agent has never spoken to Closient before, run this end-to-end and you’ll have a working authenticated call against a real endpoint in under five minutes.

When to use

  • An agent (MCP host, partner SDK, internal automation, third-party integration) is being onboarded against Closient for the first time.
  • A developer wants the canonical “hello world” path before reading any per-app API docs.
  • Token, scope, or rate-limit confusion needs to be reset to a known-good baseline.
Once the quickstart works, jump to closient-openapi-tour for a breadth-first map of the 22 per-app APIs and individual skills like local-product-search for depth on a specific workflow.

Environments

EnvironmentSite URLUse for
Productionhttps://www.closient.comReal traffic, real customers.
Testinghttps://www.closient.devIntegration testing — same code, isolated data.
Localhttp://localhost:8000Local dev only.
All endpoints in this doc are path-relative; prefix them with the env URL of your choice. Examples use https://www.closient.com.

Step 1 — Discover what’s available

Closient publishes machine-readable discovery surfaces under /.well-known/. Hit them first to enumerate APIs and capabilities without grepping documentation:
GET /.well-known/api-catalog
Accept: application/linkset+json
The response is an RFC 9727 linkset — one entry per per-app NinjaAPI, each with a service-desc pointing at that app’s OpenAPI document:
{
  "linkset": [
    {
      "anchor": "https://www.closient.com/search/api/v1/",
      "service-desc": [{
        "href": "https://www.closient.com/search/api/v1/openapi.json",
        "type": "application/openapi+json"
      }]
    }
  ]
}
Sibling discovery endpoints worth knowing:
EndpointWhat it gives you
/.well-known/agent-skills/index.jsonIndex of every Agent Skill (this is one).
/.well-known/mcp.jsonMCP server card — connect via the MCP host of your choice.
/.well-known/openid-configurationOIDC / OAuth 2.0 discovery (token + auth endpoints, JWKS URI, supported scopes, RFC 7591 registration_endpoint).
/.well-known/oauth-authorization-serverPlain OAuth 2.0 AS metadata (RFC 8414).

Step 2 — Pick a credential type

Closient accepts two authentication shapes on every per-app API:
MechanismWhen to use
X-API-Key header (csb_<token>_<crc32>)Server-to-server jobs, ETL, internal automation. One key per service. Tier rate limits apply (default 300/min, 10k/day).
OAuth 2.0 access token (Authorization: Bearer ...)Agents acting on behalf of a Closient user. Supports per-scope authorization, refresh, revocation, and RFC 7591 self-registration.
Browser-based clients can also use the Django session cookie, but that isn’t a useful agent path. Quick rule: if your agent’s caller is a person who has a Closient account, use OAuth. If your caller is another machine you own, use an API key. Don’t share OAuth client credentials between operators.

2a — Get an API key (fastest path)

Sign in to Closient, open Settings → API Keys, and create a new key. The key is shown once; copy it then. Format: csb_<body>_<crc32>. Verify by hitting the catalog search endpoint with no scope required:
GET /search/api/v1/search?q=trail+mix
X-API-Key: csb_<body>_<crc32>
If you get 200 OK with a JSON body containing items and total, the key is alive and you can skip to Step 4. (Catalog search is one of the few unauthenticated endpoints, so this also works without a key — a 401 here means the key string is malformed.)

2b — Register an OAuth client (RFC 7591 Dynamic Client Registration)

For agents that need to act on behalf of users, register a client dynamically — no human admin required:
POST /oauth/register/
Content-Type: application/json

{
  "client_name": "Acme Shopping Agent",
  "redirect_uris": ["https://agent.example.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "scope": "search:read products:read resolver:read",
  "token_endpoint_auth_method": "client_secret_basic",
  "software_id": "com.example.acme-shopping-agent",
  "software_version": "1.2.3"
}
Response (RFC 7591 §3.2.1):
{
  "client_id": "...",
  "client_secret": "...",
  "client_id_issued_at": 1734567890,
  "client_secret_expires_at": 0,
  "redirect_uris": ["https://agent.example.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "scope": "search:read products:read resolver:read",
  "token_endpoint_auth_method": "client_secret_basic"
}
Notes:
  • The endpoint is rate-limited per IP (10 registrations/hour). Use a stable software_id so repeat runs deduplicate cleanly.
  • client_secret_expires_at: 0 means the secret never expires (RFC 7591 §3.2.1) — but you can rotate at any time via the connected-apps dashboard.
  • The redirect URI must use HTTPS in production; http://localhost redirects are accepted in testing/local environments.
  • For a confidential server-to-server agent, use client_credentials in the grant_types array; you’ll get tokens without a user in the loop.

Step 3 — Run the authorization_code + PKCE flow

Public clients (mobile, SPA, CLI tools) MUST use PKCE — Closient’s AS returns invalid_request if the code-verifier is missing.

3a — Mint the PKCE pair

import base64
import hashlib
import secrets

code_verifier = secrets.token_urlsafe(64)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()
Hang onto code_verifier — you need it at the token exchange step.

3b — Redirect the user to the authorize endpoint

GET /oauth/authorize/?
  response_type=code&
  client_id=<your_client_id>&
  redirect_uri=https://agent.example.com/oauth/callback&
  scope=search:read+products:read&
  state=<random_anti_csrf_token>&
  code_challenge=<code_challenge>&
  code_challenge_method=S256
The user sees a Closient-branded consent screen listing the agent name, logo, and the human-readable description of each requested scope. They click Allow; Closient redirects to your redirect_uri with code and state in the query string. If they click Deny, you’ll receive ?error=access_denied — surface it as “the user declined” rather than retrying.

3c — Exchange the code for tokens

POST /oauth/token/
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=<code_from_redirect>&
redirect_uri=https://agent.example.com/oauth/callback&
code_verifier=<the_verifier_from_step_3a>
Response:
{
  "access_token": "...",
  "expires_in": 3600,
  "token_type": "Bearer",
  "refresh_token": "...",
  "scope": "search:read products:read",
  "id_token": "..."
}
The id_token is a signed OIDC JWT — verify against the JWKS URI from /.well-known/openid-configuration before trusting the claims.

3d — Refresh when the access token expires

POST /oauth/token/
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=<refresh_token>

Step 4 — Make your first authenticated call

Pick any per-app endpoint. The search session endpoint is the canonical “hello world” for an agent because it returns local-first product results that exercise the geo-distance ranking pipeline:
POST /search/api/v1/search/session
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "query": "organic peanut butter",
  "latitude": 48.4284,
  "longitude": -123.3656,
  "radius_km": 25
}
A successful response includes a session ID you can use to refine the query (subsequent POSTs to /search/session/{id}/refine) and an array of items, each annotated with store, offer, and distance_km. For something even simpler that doesn’t require any auth at all:
GET /search/api/v1/search?q=trail+mix
If both of those work, you’re done. The first row of data is the deliverable.

Step 5 — Debug the common failures

401 Unauthorized

CauseWhat to check
Missing headerX-API-Key or Authorization header not sent — check your HTTP client’s middleware.
Malformed API keyFormat is csb_<body>_<crc32>. Three components, two underscores.
Expired access tokenUse the refresh token; OAuth access tokens are 1 h by default.
Wrong environmentA testing-env API key won’t work against production, and vice versa.
Wrong client_secret on token exchangeRe-derive Authorization: Basic ... — base64 of client_id:client_secret, no padding tricks.

403 Forbidden

CauseWhat to check
Scope mismatchOAuth token doesn’t carry the scope the endpoint requires. Re-run authorize with scope= including the right per-app scope (e.g. products:write).
Role mismatchThe user behind the token is a VIEWER on the org but the endpoint requires MANAGER or OWNER.
Wrong organizationCross-org access — the token’s user isn’t a member of the target organization.
API key disabledThe key may have been revoked; create a new one.

429 Too Many Requests

HeaderWhat it says
RateLimit-Policy: 300;w=60, 10000;w=86400Two active windows: 300/minute and 10k/day.
RateLimit-Limit: 300Cap for the most-restrictive currently-active window.
RateLimit-Remaining: 12What’s left in that window.
RateLimit-Reset: 7Seconds (relative) until that window resets.
These are standard IETF RateLimit-* headers (informed-governor). Use RateLimit-Reset to schedule the next retry — don’t poll on 429 with no backoff. Legacy X-RateLimit-* aliases are also emitted; X-RateLimit-Reset keeps the absolute Unix-timestamp shape for back-compat.

400 with error: invalid_grant on token exchange

  • code was already redeemed (one-time-use).
  • code_verifier doesn’t match the original code_challenge.
  • redirect_uri differs from what you sent at /oauth/authorize/.

400 with RFC 7591 error: invalid_redirect_uri on registration

  • HTTP redirect URI on production (HTTPS-only outside localhost).
  • Loopback URI uses a hostname instead of 127.0.0.1 / [::1].

Scopes you’ll actually use

Closient’s OAuth scope set is per-app — every NinjaAPI gets a :read and a :write. The most common starter set:
ScopeWhat it grants
search:readCatalog and session search.
products:readProduct lookups, attributes, GTIN resolution.
resolver:readGS1 Digital Link resolution + linkset queries.
brands:read / brands:writeBrand records (write needed for claim-brand flow).
retailers:read / retailers:writeOffers + inventory (write needed for POS-driven onboarding).
account:readwhoami, org membership, API-key listing.
qr:generateThe QR-URL MCP tool (not REST-fronted).
The full canonical list is the scopes_supported array in /.well-known/openid-configuration. Always request the narrowest scope set that lets you complete the workflow — agents asking for write scope when they only read are flagged as suspicious in the consent UI.
  • closient-openapi-tour — bird’s-eye map of the 22 per-app APIs.
  • decode-gs1-ai — once authenticated, parse GS1 AI strings into structured queries against /products/api/v1/.
  • build-gs1-digital-link — inverse of decode-gs1-ai.
  • local-product-search, resolve-gtin, check-product-availability — the most common workflows for an agent’s first useful call.
  • claim-brand, onboard-retailer — onboarding-shaped workflows for authenticated org members.