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.
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
| Environment | Site URL | Use for |
|---|---|---|
| Production | https://www.closient.com | Real traffic, real customers. |
| Testing | https://www.closient.dev | Integration testing — same code, isolated data. |
| Local | http://localhost:8000 | Local dev only. |
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:
service-desc pointing at that app’s OpenAPI document:
| Endpoint | What it gives you |
|---|---|
/.well-known/agent-skills/index.json | Index of every Agent Skill (this is one). |
/.well-known/mcp.json | MCP server card — connect via the MCP host of your choice. |
/.well-known/openid-configuration | OIDC / OAuth 2.0 discovery (token + auth endpoints, JWKS URI, supported scopes, RFC 7591 registration_endpoint). |
/.well-known/oauth-authorization-server | Plain OAuth 2.0 AS metadata (RFC 8414). |
Step 2 — Pick a credential type
Closient accepts two authentication shapes on every per-app API:| Mechanism | When 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. |
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:
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:- The endpoint is rate-limited per IP (10 registrations/hour). Use a
stable
software_idso repeat runs deduplicate cleanly. client_secret_expires_at: 0means 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://localhostredirects are accepted in testing/local environments. - For a confidential server-to-server agent, use
client_credentialsin thegrant_typesarray; 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 returnsinvalid_request if the code-verifier is missing.
3a — Mint the PKCE pair
code_verifier — you need it at the token exchange step.
3b — Redirect the user to the authorize endpoint
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
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
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:/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:
Step 5 — Debug the common failures
401 Unauthorized
| Cause | What to check |
|---|---|
| Missing header | X-API-Key or Authorization header not sent — check your HTTP client’s middleware. |
| Malformed API key | Format is csb_<body>_<crc32>. Three components, two underscores. |
| Expired access token | Use the refresh token; OAuth access tokens are 1 h by default. |
| Wrong environment | A testing-env API key won’t work against production, and vice versa. |
| Wrong client_secret on token exchange | Re-derive Authorization: Basic ... — base64 of client_id:client_secret, no padding tricks. |
403 Forbidden
| Cause | What to check |
|---|---|
| Scope mismatch | OAuth 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 mismatch | The user behind the token is a VIEWER on the org but the endpoint requires MANAGER or OWNER. |
| Wrong organization | Cross-org access — the token’s user isn’t a member of the target organization. |
| API key disabled | The key may have been revoked; create a new one. |
429 Too Many Requests
| Header | What it says |
|---|---|
RateLimit-Policy: 300;w=60, 10000;w=86400 | Two active windows: 300/minute and 10k/day. |
RateLimit-Limit: 300 | Cap for the most-restrictive currently-active window. |
RateLimit-Remaining: 12 | What’s left in that window. |
RateLimit-Reset: 7 | Seconds (relative) until that window resets. |
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
codewas already redeemed (one-time-use).code_verifierdoesn’t match the originalcode_challenge.redirect_uridiffers 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:
| Scope | What it grants |
|---|---|
search:read | Catalog and session search. |
products:read | Product lookups, attributes, GTIN resolution. |
resolver:read | GS1 Digital Link resolution + linkset queries. |
brands:read / brands:write | Brand records (write needed for claim-brand flow). |
retailers:read / retailers:write | Offers + inventory (write needed for POS-driven onboarding). |
account:read | whoami, org membership, API-key listing. |
qr:generate | The QR-URL MCP tool (not REST-fronted). |
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.
Related skills
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 ofdecode-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.