API reference
Two calls and an MCP server, all with an API key. Send JSON, get a signed JSON answer. Every example uses test.authorizedretailers.ai, a demo brand the registry runs, so you can try it as written.
Overview
| Base URL | https://authorizedretailers.ai. Paths carry no version: a request without one gets the current spec. The older /v0/ form of every endpoint still answers, the same way. |
|---|---|
| Authentication | An API key, sent as Authorization: Bearer <key>, never in a URL. Get one. A missing or invalid key is a 401. Also send a User-Agent that names your agent (see limits). |
| Format | JSON in, JSON out. Requests up to 8 KB. |
| Answers | Signed by the registry (Ed25519), fresh for 15 minutes. See trusting an answer. |
| Endpoints | POST /verify: is this seller authorized for this product? POST /resolve: which brand is this product? Plus the MCP server at /mcp. |
API keys
The verify and resolve calls and the MCP server need an API key, so we know who is using the registry and can reach you about it. Get an API key, and your console in the app opens with your keys, their usage and your settings. Come back with Sign in, then Agent developer. We ask for:
- your work email. If you don't have one, say so on the form and tick the box to be contacted, and we'll get in touch;
- your name and organization, as the agent's operator;
- the agent you're building and what it's for.
- You sign in with an emailed link and an authenticator app, as brand accounts do. Lost your phone? Email hello@authorizedretailers.ai and we'll reset it.
- Send the key as
Authorization: Bearer <key>on every call, and never in a URL or the request body. - A key is shown once, when you create it. Keep it in your agent's secrets, not in code or a repository.
- Make as many keys as you need and revoke any of them. A revoked key stops working within a minute.
- No key, or one that isn't valid, gets a
401(api_key_requiredorinvalid_api_key) and no answer. Treat the seller as unconfirmed, and check the key: the registry is up. - A key never changes an answer. Everyone who asks the same question gets the same signed answer.
- The signing keys, public lists and the revocation feed (below) need no key.
- The rules for keys are in spec section 9.6.
Verify a seller
On a product page you see a product and a seller. Send both. The answer says whether the brand has authorized that seller on that channel, in that territory, for that product, and flags anything you should know about the product.
https://authorizedretailers.ai/verifyParameters
A JSON body. Send a product, a brand_domain or both; the registry finds the brand from the product in the catalogs of verified brands, so a lookalike brand can't claim another brand's products.
| Parameter | Required | Description |
|---|---|---|
channel | required | Where the seller sells. A marketplace seller is its domain and seller ID: { "domain": "amazon.com", "seller_id": "A1B2C3D4E5F6G7" }. A web store is its domain alone: { "domain": "shop.example" }. Match sellers by these identifiers, never by a store name; if a platform shows only a name, that channel isn't supported yet, so treat the seller as unconfirmed. |
channel.type | optional | amazon, walmart, ebay or web. Inferred from the domain, so you can leave it out. |
product | product or brand_domain | The product on the page, by one or more of the identifiers below. |
product.gtin | one of these | The barcode. A UPC-A (12 digits), EAN-13 and GTIN-14 are the same number at different widths, so send whichever the page shows; the registry pads it to 14 digits and validates the check digit. Matched against the brand's catalog. |
product.asin | one of these | The Amazon ASIN. On its own it works only on an Amazon channel. |
product.walmart_item_id | one of these | The number at the end of a walmart.com product URL. On its own it works only on walmart.com. |
product.wpid | one of these | The Walmart Seller Center product ID. On its own it works only on walmart.com. |
product.mpn | optional | The model number the listing shows. It only confirms a match (product.confirmed / mismatched); it never identifies a product on its own. |
product.style_code | optional | The style code the listing shows. Same as mpn. |
territory | sometimes | The buyer's country (ISO 3166-1, US, GB). Leave it out for a marketplace: amazon.com is the US and amazon.de is Germany. Required for a web store on a generic domain like .com, which can ship anywhere. |
brand_domain | product or brand_domain | The brand's domain, if you know it. Without it the registry finds the brand from the product. |
as_of | optional | A date or UTC time the answer should reflect. Defaults to now. |
price | optional | The price you're looking at for this seller and product: {"amount": "34.50", "currency": "USD"}, a decimal string and an ISO 4217 code. Read only with a product. It never changes the answer and never reveals a brand's minimum advertised price; the brand sees, in aggregate, how often agents saw its products below it. |
Answers
Every answer carries status. Answers about a brand also carry brand (its domain and name) and resolved_by (brand_domain, gtin, asin, walmart_item_id or wpid), so you know how the registry found it.
| status | Means | What to do |
|---|---|---|
| authorized | The brand has authorized this seller, on this channel, in this territory, for this product. | Go ahead, after checking product.status below. |
| unlisted | The brand is registered and hasn't authorized this seller for this product on this channel. A reason may say why: territory_mismatch, product_excluded, product_not_in_scope, product_not_in_catalog or product_not_for_region (the product isn't made for this market, often an import). | The brand hasn't listed this seller. Prefer an authorized seller if one has the product; if you pick this one, say so to the shopper. No confirmation needed. |
| unauthorized | The brand has declared this seller unauthorized, for all of its products or for the ones it names. declaration_id and declared say which declaration and when; a product outside a scoped declaration answers as if it didn't exist, with the signal unauthorized_for_other_products. | Never complete a purchase here on your own. Tell the shopper the brand has declared the seller unauthorized, that the goods may be materially different or counterfeit, that the brand's warranty and support may not apply and that the registry can't say whether the goods are genuine; buy only if they confirm that purchase, or have told you to buy from such sellers (an enforcement test buy, say). Keep the signed answer with the order. |
| expired | The seller was authorized until an end date the brand set, and it has passed. | Treat as unlisted. |
| disputed | The seller is disputing its status: either its listing is under the brand's review (authorization_id), or it disputes being declared unauthorized and the brand hasn't answered yet (declaration_id). | Treat as unconfirmed. |
| brand_not_registered | The brand domain you sent isn't registered. A reason may say why: not_registered (no account for the domain), domain_unlinked (verified, but the registry hasn't approved the brand yet) or verification_lapsed (the DNS record is gone). | Carry on as you would without the registry. |
| product_not_found | You sent no brand domain, and no registered brand lists the product. | Carry on as you would without the registry. |
| brand_ambiguous | More than one registered brand lists the product. candidates names them. | Never treat as authorized. Ask again with the right brand_domain, or show the shopper the candidates. |
| brand_mismatch | The product belongs to a different brand than the domain you sent. candidates names it. | Never treat as authorized. Check which brand the listing is really for. |
New statuses can be added. Treat any status you don't recognize as unconfirmed, never as authorized.
The product
| Field | Description |
|---|---|
product.status | Present when the product isn't active. recalled comes with recall_url: tell the shopper, link the notice and don't complete the purchase, whatever the seller's status. discontinued is information for the shopper: it can still be legitimate clearance stock. |
product.in_catalog | Whether the brand lists the product. A product that isn't in the catalog may be a duplicate listing. If the brand has authorized the seller for all its products, the answer is still authorized, with the signal product_not_in_catalog. |
product.confirmed, product.mismatched | Which of the mpn and style_code you sent match the brand's catalog. A mismatch also adds the signal product_details_mismatch: the listing may have been changed to show another product, so look closer before buying. |
signals | Never change the status. authorization_scoped on an authorized answer: you asked without a product and the brand authorizes this seller at product level, so ask again with the product before relying on it. unauthorized_for_other_products on an unlisted answer: the brand has declared this seller unauthorized for other products than the one you asked about. |
product.regions | The markets the product is made for, when the brand has set them. |
product.id, name, product_line, mpns, style_code | Only for brands with a public list. Private brands return the status fields only. |
Trusting an answer
Every answer is signed by the registry: a detached JWS (RFC 7515) with Ed25519 (EdDSA) over the answer's JSON, canonicalized per RFC 8785 without its signature. Check it against the registry's keys at /.well-known/jwks.json, using the kid the signature names. Cache the key set for no longer than the 5 minutes it is served for, since a compromised key is removed from it at once. If you don't recognize the kid, fetch the keys again once. Details are in spec section 8.
- An answer stays fresh for 15 minutes (
valid_until), so an answer you saved earlier can be used for 15 minutes at most. This is about the answer, not the authorization, which lasts until the brand revokes it or it reaches its own end date (expires). A brand's revocation reaches new answers within a minute. - If the verify API can't be reached, you may use an answer you already hold until its
stale_until(24 hours), but only after checking it against a revocation feed issued at or after the answer, and you must mark it as stale. A feed issued before the answer says nothing about later revocations. - An answer that carries
expires, the authorization's own end date, stops being usable as authorized at that instant, even if itsvalid_untilis later. observed: nullonly means the registry has no sighting of the seller. Don't treat it as a warning, and don't downgrade anauthorizedanswer because of it.- If you can't get a current answer, treat the seller as unconfirmed. An outage never makes a seller authorized or unauthorized.
Examples
The first demo case, an Amazon seller the demo brand has authorized, as cURL, Node.js and Python. The response is what the registry returned (signature shortened).
curl -s https://authorizedretailers.ai/verify \
-H "Authorization: Bearer $AR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"channel": {
"domain": "amazon.com",
"seller_id": "ADEMOSELLER001"
},
"product": {
"asin": "B0DEMO0001"
}
}'const res = await fetch("https://authorizedretailers.ai/verify", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + process.env.AR_API_KEY },
body: JSON.stringify({
"channel": {
"domain": "amazon.com",
"seller_id": "ADEMOSELLER001"
},
"product": {
"asin": "B0DEMO0001"
}
}),
});
const answer = await res.json();
if (answer.status === "authorized") { /* go ahead, after checking answer.product?.status */ }import os
import requests
r = requests.post("https://authorizedretailers.ai/verify", headers={"Authorization": f"Bearer {os.environ['AR_API_KEY']}"}, json={
"channel": {
"domain": "amazon.com",
"seller_id": "ADEMOSELLER001"
},
"product": {
"asin": "B0DEMO0001"
}
})
answer = r.json()
if answer["status"] == "authorized":
pass # go ahead, after checking answer.get("product", {}).get("status")Every case the demo brand covers, with the exact body to send. Pick one in the live check below, or press Run.
curl -s https://authorizedretailers.ai/verify \
-H "Authorization: Bearer $AR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"channel": {
"domain": "amazon.com",
"seller_id": "ADEMOSELLER001"
},
"product": {
"asin": "B0DEMO0001"
}
}'{
"status": "authorized",
"brand": { "domain": "test.authorizedretailers.ai", "name": "Authorized Retailers Demo" },
"resolved_by": "asin",
"authorization_id": "auth_demo000000000000000000001",
"revocation_id": "rv_5069f69c213b49aee224b948",
"checked": "2026-10-07T19:35:35.707Z",
"valid_until": "2026-10-07T19:50:35.707Z",
"stale_until": "2026-10-08T19:35:35.707Z",
"product": {
"in_catalog": true, "id": "DEMO-TEE", "product_line": "Demo Tees", "name": "Demo Tee",
"mpns": ["DT-100"], "style_code": "DEMO-TEE"
},
"observed": null,
"signals": [],
"signature": { "kid": "ar-20260924-6e37", "jws": "eyJhbGciOiJFZERTQSIs..." }
}An answer from the demo brand, signature shortened. Verify it against the keys at /.well-known/jwks.json.
| Ask about | Send (the request body) | You get | |
|---|---|---|---|
| An Amazon seller | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER001"},"product":{"asin":"B0DEMO0001"}} | authorized, found by ASIN | |
| A Walmart seller, by UPC | {"channel":{"domain":"walmart.com","seller_id":"900000001"},"product":{"gtin":"200000001018"}} | authorized, found by UPC | |
| A web store, for a buyer in the US | {"channel":{"domain":"demo-store.authorizedretailers.ai"},"territory":"US","product":{"gtin":"200000001018"}} | authorized for Demo Tees | |
| An eBay seller | {"channel":{"domain":"ebay.com","seller_id":"demo-seasonal-seller"},"product":{"gtin":"200000001018"}} | authorized, with an end date | |
| An Amazon seller the brand hasn't listed | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER999"},"product":{"asin":"B0DEMO0001"}} | unlisted | |
| An Amazon seller the brand has declared unauthorized | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER666"},"product":{"asin":"B0DEMO0001"}} | unauthorized, with declaration_id: don't buy | |
| A product the brand sells only itself | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER001"},"product":{"asin":"B0DEMO0002"}} | unlisted, product_excluded | |
| A recalled product | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER001"},"product":{"asin":"B0DEMO0003"}} | authorized, with product.status recalled: tell the shopper, don't buy | |
| A product made for the EU, on amazon.com | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER001"},"product":{"asin":"B0DEMO0004"}} | unlisted, product_not_for_region | |
| A listing showing another model number | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER001"},"product":{"asin":"B0DEMO0001","mpn":"DT-999"}} | authorized, with product.mismatched and a warning signal | |
| A product no registered brand lists | {"channel":{"domain":"amazon.com","seller_id":"ADEMOSELLER001"},"product":{"gtin":"200000009991"}} | product_not_found |
The demo brand's signed list: /brands/test.authorizedretailers.ai/list.
Resolve a product
Which brand does a product belong to, without asking about a seller.
https://authorizedretailers.ai/resolve| Parameter | Required | Description |
|---|---|---|
product | required | The product, by gtin, asin, walmart_item_id or wpid, as for verify. |
domain | sometimes | The marketplace: the Amazon domain for an ASIN, walmart.com for a Walmart item ID or WPID. |
curl -s https://authorizedretailers.ai/resolve \
-H "Authorization: Bearer $AR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{ "product": { "gtin": "200000001018" } }'The signed answer is resolved (with brand), product_not_found or brand_ambiguous (with candidates). It names the brand and nothing else.
Errors and limits
| Status | Means |
|---|---|
400 invalid_request | details names each problem. The ones you're most likely to see: no brand domain or product (brand_or_product_required), an ASIN alone on a channel that isn't Amazon (asin_needs_amazon_channel), a Walmart item ID or WPID alone off walmart.com (walmart_id_needs_walmart_channel), a model number or style code without a product identifier (product_identifier_required), a web store on a generic domain with no territory (territory_required) and a GTIN with a bad check digit (invalid_gtin). |
401 | No API key, or one that isn't valid (api_key_required, invalid_api_key). You get no answer: treat the seller as unconfirmed and check the key. The registry is up, so a saved answer isn't a substitute. |
413 | The request body is over 8 KB. |
429 | Too many requests. Limits are per caller, and per caller and brand, to stop anyone downloading a private list one question at a time. Wait a minute and try again. |
Say who you are. Send a User-Agent that names your agent and its operator and a way to reach you, for example ExampleShopper/2.1 (+https://example.com/agent; agents@example.com). We record it with the name of the network owner to see which agents use the registry and, where you name yourself, to get in touch. It never changes an answer, and sending none is fine. See the privacy policy.
Where to check in a checkout flow
Protocols for agents that buy, such as UCP and ACP, say how to find a merchant's products and check out with it. They don't say whether a brand authorized that merchant, and a merchant can't vouch for itself. Check between choosing a seller and starting checkout, using the seller's channel: its store domain, or its marketplace and seller ID. Start checkout only with a seller the brand has authorized, or one the shopper has chosen knowing it's unconfirmed. Never start it with a seller the brand has declared unauthorized unless the shopper has confirmed that purchase knowing what the declaration means (see unauthorized above).
MCP server
Prefer tools? Connect the remote MCP server at https://authorizedretailers.ai/mcp (Streamable HTTP; send your key as Authorization: Bearer <key>). Four read-only tools:
| Tool | Does |
|---|---|
verify_seller | The verify request above: a seller and a product in, a signed answer out. |
resolve_brand | A product in, its brand out. |
get_brand | A brand's registration and list details. |
list_authorized_retailers | A brand's authorized retailers, for brands whose list is public. |
Connect a client
Every client sends the same thing: your key in an Authorization: Bearer <key> header, on every request. Put the key in an environment variable or the client's secret store, not in a file you commit.
| Client | How |
|---|---|
| Claude Code | claude mcp add --transport http authorizedretailers https://authorizedretailers.ai/mcp --header "Authorization: Bearer $AR_API_KEY" |
| Claude (claude.ai and Claude Desktop) | Customize > Connectors > Add custom connector. Enter https://authorizedretailers.ai/mcp, set Authentication to No sign-in, and under Request headers add the header authorization with the value Bearer followed by your key, marked required. Claude sends the value exactly as typed, so include the word Bearer and the space. Request headers are in beta and not yet on every organization: if you don't see the section, your organization doesn't have it. |
| The Anthropic API (MCP connector) | Add the server to mcp_servers with "type": "url", the URL above and "authorization_token" set to your key. The API sends it as the bearer token. |
| Cursor and other clients that take headers | Set the server's URL to https://authorizedretailers.ai/mcp and its headers to { "Authorization": "Bearer <key>" }, reading the key from an environment variable if the client allows it. |
Some hosted chat apps connect to a server only by signing in with OAuth and have no place to put a key. They can't use the MCP server yet. Your agent can still call the REST API, which does the same thing.
A call without a valid key gets HTTP 401 with WWW-Authenticate: Bearer realm="authorizedretailers.ai". Listing the tools needs no key, so a client can connect and show them before you add one.
Schemas and spec
- JSON Schemas for every request and answer: verify request, verify response, resolve request and resolve response.
- The rules behind all of this: spec section 9 and finding the brand from a product and buying from a seller the brand hasn't authorized. The spec is published as @authorizedretailers/spec 0.8.0, with schemas, fixtures and reference code.
- Signing keys: /.well-known/jwks.json. Revocations: /revocations.