@authorizedretailers/spec 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -47
- package/dist/constants.d.ts +25 -6
- package/dist/constants.js +41 -7
- package/dist/identity.d.ts +29 -14
- package/dist/identity.js +79 -39
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/jws.d.ts +36 -5
- package/dist/jws.js +48 -14
- package/dist/match.d.ts +21 -0
- package/dist/match.js +89 -0
- package/dist/product.d.ts +34 -0
- package/dist/product.js +75 -0
- package/dist/published.d.ts +14 -8
- package/dist/published.js +32 -10
- package/dist/schemas.d.ts +366 -91
- package/dist/schemas.js +802 -181
- package/dist/types.d.ts +121 -47
- package/dist/types.js +2 -2
- package/dist/validate.d.ts +24 -10
- package/dist/validate.js +300 -107
- package/fixtures/list/invalid/list-all-mixed.json +53 -0
- package/fixtures/list/invalid/list-amazon-missing-seller-id.json +51 -0
- package/fixtures/list/invalid/list-amazon-on-walmart-domain.json +52 -0
- package/fixtures/{file/invalid/full-attached-jws.json → list/invalid/list-attached-jws.json} +14 -12
- package/fixtures/list/invalid/list-bad-asin.json +102 -0
- package/fixtures/list/invalid/list-bad-gtin.json +102 -0
- package/fixtures/list/invalid/list-brand-exclusion-no-catalog.json +57 -0
- package/fixtures/list/invalid/list-duplicate-asin.json +102 -0
- package/fixtures/{file/invalid/full-duplicate-authorization-id.json → list/invalid/list-duplicate-authorization-id.json} +23 -18
- package/fixtures/list/invalid/list-duplicate-gtin.json +102 -0
- package/fixtures/list/invalid/list-duplicate-product-id.json +102 -0
- package/fixtures/{file/invalid/full-empty-territories.json → list/invalid/list-empty-territories.json} +17 -10
- package/fixtures/list/invalid/list-exclude-unknown-product.json +105 -0
- package/fixtures/list/invalid/list-expires-field.json +53 -0
- package/fixtures/list/invalid/list-extra-property.json +53 -0
- package/fixtures/list/invalid/list-generic-web-no-territories.json +39 -0
- package/fixtures/list/invalid/list-gtin-11-digits.json +102 -0
- package/fixtures/list/invalid/list-impossible-date.json +53 -0
- package/fixtures/list/invalid/list-lowercase-territory.json +52 -0
- package/fixtures/{file/invalid/full-signature-missing-kid.json → list/invalid/list-marketplace-field.json} +14 -11
- package/fixtures/list/invalid/list-marketplace-territory-mismatch.json +52 -0
- package/fixtures/list/invalid/list-missing-revocation-id.json +51 -0
- package/fixtures/list/invalid/list-missing-scope.json +44 -0
- package/fixtures/list/invalid/list-missing-stale-until.json +51 -0
- package/fixtures/list/invalid/list-name-only-retailer.json +36 -0
- package/fixtures/list/invalid/list-no-channels.json +37 -0
- package/fixtures/list/invalid/list-non-utc-timestamp.json +52 -0
- package/fixtures/list/invalid/list-not-public-with-authorizations.json +18 -0
- package/fixtures/list/invalid/list-not-public-with-catalog.json +18 -0
- package/fixtures/{file/invalid/full-physical-only.json → list/invalid/list-physical.json} +16 -10
- package/fixtures/list/invalid/list-product-no-identifier.json +98 -0
- package/fixtures/list/invalid/list-scope-no-products.json +49 -0
- package/fixtures/{file/valid/full-signed.json → list/invalid/list-single-domain.json} +12 -12
- package/fixtures/list/invalid/list-stale-over-24-hours.json +52 -0
- package/fixtures/list/invalid/list-territory-uk.json +52 -0
- package/fixtures/{file/invalid/full-unknown-channel-type.json → list/invalid/list-unknown-channel-type.json} +18 -12
- package/fixtures/list/invalid/list-unknown-line.json +102 -0
- package/fixtures/list/invalid/list-unknown-product.json +105 -0
- package/fixtures/{file/invalid/full-missing-authorization-expires.json → list/invalid/list-unsigned.json} +12 -9
- package/fixtures/list/invalid/list-valid-over-15-minutes.json +52 -0
- package/fixtures/{file/invalid/full-web-domain-is-url.json → list/invalid/list-web-domain-is-url.json} +18 -12
- package/fixtures/list/invalid/list-web-on-marketplace-domain.json +52 -0
- package/fixtures/{file/invalid/full-wildcard-mixed.json → list/invalid/list-wildcard-mixed.json} +17 -10
- package/fixtures/list/valid/list-catalog-line-scope.json +102 -0
- package/fixtures/list/valid/list-cctld-web.json +39 -0
- package/fixtures/list/valid/list-fractional-seconds.json +52 -0
- package/fixtures/{file/invalid/full-amazon-missing-seller-id.json → list/valid/list-generic-web-territories.json} +15 -17
- package/fixtures/list/valid/list-marketplace-no-territories.json +40 -0
- package/fixtures/list/valid/list-no-authorizations.json +18 -0
- package/fixtures/list/valid/list-no-catalog.json +52 -0
- package/fixtures/list/valid/list-no-expires.json +52 -0
- package/fixtures/list/valid/list-not-public.json +17 -0
- package/fixtures/list/valid/list-product-scope.json +103 -0
- package/fixtures/list/valid/list-proposed-by.json +55 -0
- package/fixtures/list/valid/list-with-expires.json +53 -0
- package/fixtures/list/valid/list-worldwide.json +52 -0
- package/fixtures/manifest.json +606 -158
- package/fixtures/match/all-scope-excluded-line.json +194 -0
- package/fixtures/match/all-scope-not-in-catalog.json +194 -0
- package/fixtures/match/as-of-before-end-date.json +194 -0
- package/fixtures/match/brand-exclusion-beats-named-product.json +194 -0
- package/fixtures/match/duplicate-asin-authorized-seller.json +192 -0
- package/fixtures/match/duplicate-asin-unauthorized-seller.json +191 -0
- package/fixtures/match/expired-end-date.json +193 -0
- package/fixtures/match/marketplace-territory-default.json +186 -0
- package/fixtures/match/marketplace-territory-mismatch.json +187 -0
- package/fixtures/match/match-by-channel-key.json +186 -0
- package/fixtures/match/product-excluded-by-authorization.json +194 -0
- package/fixtures/match/product-line-request.json +186 -0
- package/fixtures/match/product-not-in-scope.json +194 -0
- package/fixtures/match/seller-authorized-by-gtin.json +194 -0
- package/fixtures/match/seller-authorized-for-line.json +194 -0
- package/fixtures/match/worldwide-web.json +186 -0
- package/fixtures/pointer/invalid/full-form.json +11 -0
- package/fixtures/pointer/invalid/pointer-domain-mismatch.json +10 -0
- package/fixtures/{file → pointer}/invalid/pointer-http-url.json +2 -1
- package/fixtures/pointer/invalid/pointer-missing-verify-token.json +9 -0
- package/fixtures/pointer/invalid/pointer-other-host.json +10 -0
- package/fixtures/{file/valid/pointer.json → pointer/invalid/pointer-wrong-spec.json} +1 -0
- package/fixtures/pointer/invalid/private-form.json +10 -0
- package/fixtures/pointer/invalid/unknown-form.json +10 -0
- package/fixtures/pointer/valid/pointer-fetched-via-www.json +10 -0
- package/fixtures/pointer/valid/pointer.json +10 -0
- package/fixtures/revocation-feed/invalid/feed-hashed-entry.json +17 -0
- package/fixtures/revocation-feed/invalid/feed-valid-over-15-minutes.json +17 -0
- package/fixtures/revocation-feed/valid/feed-empty.json +12 -0
- package/fixtures/revocation-feed/valid/feed.json +17 -0
- package/fixtures/verify-request/invalid/bad-as-of.json +9 -0
- package/fixtures/verify-request/invalid/bad-gtin.json +11 -0
- package/fixtures/verify-request/invalid/domain-with-scheme.json +2 -4
- package/fixtures/verify-request/{valid/web-no-product-line.json → invalid/generic-web-no-territory.json} +1 -2
- package/fixtures/verify-request/invalid/gtin-and-asin.json +12 -0
- package/fixtures/verify-request/{valid/amazon.json → invalid/marketplace-field.json} +1 -3
- package/fixtures/verify-request/invalid/missing-channel.json +1 -3
- package/fixtures/verify-request/invalid/physical-channel.json +1 -3
- package/fixtures/verify-request/invalid/retailer-name-only.json +0 -2
- package/fixtures/verify-request/invalid/territory-uk.json +9 -0
- package/fixtures/verify-request/invalid/wildcard-territory.json +2 -3
- package/fixtures/verify-request/valid/as-of-datetime.json +9 -0
- package/fixtures/verify-request/valid/full.json +13 -0
- package/fixtures/verify-request/valid/gtin.json +11 -0
- package/fixtures/verify-request/valid/minimal-marketplace.json +8 -0
- package/fixtures/verify-request/valid/web-cctld-no-territory.json +7 -0
- package/fixtures/verify-request/valid/web-generic-with-territory.json +9 -0
- package/fixtures/verify-response/invalid/authorized-missing-revocation-id.json +18 -0
- package/fixtures/verify-response/invalid/authorized-with-reason.json +20 -0
- package/fixtures/verify-response/invalid/brand-unverified.json +13 -0
- package/fixtures/verify-response/invalid/expired-without-expires.json +18 -0
- package/fixtures/verify-response/invalid/missing-observed.json +11 -5
- package/fixtures/verify-response/invalid/missing-signature.json +9 -3
- package/fixtures/verify-response/invalid/missing-stale-until.json +18 -0
- package/fixtures/verify-response/invalid/not-in-catalog-with-id.json +17 -0
- package/fixtures/verify-response/invalid/not-registered-with-unlisted-reason.json +13 -0
- package/fixtures/verify-response/invalid/stale-until-over-24-hours.json +19 -0
- package/fixtures/verify-response/invalid/unlisted-observed.json +5 -4
- package/fixtures/verify-response/invalid/unlisted-with-authorization-id.json +5 -4
- package/fixtures/verify-response/invalid/unlisted-with-not-registered-reason.json +13 -0
- package/fixtures/verify-response/invalid/unlisted-with-revocation-id.json +13 -0
- package/fixtures/verify-response/invalid/valid-until-before-checked.json +11 -5
- package/fixtures/verify-response/invalid/valid-until-over-15-minutes.json +19 -0
- package/fixtures/verify-response/invalid/with-stale-flag.json +20 -0
- package/fixtures/verify-response/invalid/with-tier.json +11 -5
- package/fixtures/verify-response/valid/authorized-no-product.json +14 -0
- package/fixtures/verify-response/valid/authorized-not-in-catalog.json +22 -0
- package/fixtures/verify-response/valid/authorized-observed.json +11 -5
- package/fixtures/verify-response/valid/authorized-private-product.json +17 -0
- package/fixtures/verify-response/valid/authorized-with-end-date.json +20 -0
- package/fixtures/verify-response/valid/authorized.json +11 -5
- package/fixtures/verify-response/valid/disputed.json +10 -5
- package/fixtures/verify-response/valid/expired.json +12 -6
- package/fixtures/verify-response/valid/not-registered-no-reason.json +12 -0
- package/fixtures/verify-response/valid/not-registered-reason.json +13 -0
- package/fixtures/verify-response/valid/unlisted-product-excluded.json +18 -0
- package/fixtures/verify-response/valid/unlisted-product-not-in-catalog.json +16 -0
- package/fixtures/verify-response/valid/unlisted-territory-mismatch.json +13 -0
- package/fixtures/verify-response/valid/unlisted.json +5 -4
- package/fixtures/verify-response/valid/with-as-of.json +20 -0
- package/fixtures/verify-response/valid/with-signal.json +11 -5
- package/package.json +5 -4
- package/schemas/0.2/common.json +761 -0
- package/schemas/0.2/list.json +87 -0
- package/schemas/0.2/pointer.json +33 -0
- package/schemas/0.2/revocation-feed.json +55 -0
- package/schemas/0.2/verify-request.json +65 -0
- package/schemas/0.2/verify-response.json +284 -0
- package/spec/spec-v0.2.md +531 -0
- package/fixtures/file/invalid/full-all-mixed.json +0 -47
- package/fixtures/file/invalid/full-authorization-expires-after-file.json +0 -46
- package/fixtures/file/invalid/full-expires-before-issued.json +0 -46
- package/fixtures/file/invalid/full-expiry-over-180-days.json +0 -46
- package/fixtures/file/invalid/full-extra-property.json +0 -47
- package/fixtures/file/invalid/full-impossible-date.json +0 -46
- package/fixtures/file/invalid/full-lowercase-territory.json +0 -45
- package/fixtures/file/invalid/full-missing-file-expires.json +0 -45
- package/fixtures/file/invalid/full-missing-product-lines.json +0 -43
- package/fixtures/file/invalid/full-missing-scope.json +0 -37
- package/fixtures/file/invalid/full-missing-territories.json +0 -42
- package/fixtures/file/invalid/full-name-only-retailer.json +0 -30
- package/fixtures/file/invalid/full-no-channels.json +0 -31
- package/fixtures/file/invalid/full-non-utc-timestamp.json +0 -46
- package/fixtures/file/invalid/full-physical-missing-country.json +0 -50
- package/fixtures/file/invalid/full-web-missing-domain.json +0 -45
- package/fixtures/file/invalid/full-wrong-spec.json +0 -46
- package/fixtures/file/invalid/private-missing-verify.json +0 -8
- package/fixtures/file/invalid/private-with-authorizations.json +0 -45
- package/fixtures/file/invalid/unknown-form.json +0 -9
- package/fixtures/file/valid/full-fractional-seconds.json +0 -46
- package/fixtures/file/valid/full-max-expiry.json +0 -46
- package/fixtures/file/valid/full-no-authorizations.json +0 -11
- package/fixtures/file/valid/full-unsigned-handwritten.json +0 -46
- package/fixtures/file/valid/full-worldwide-physical-proposed.json +0 -82
- package/fixtures/file/valid/private.json +0 -9
- package/fixtures/verify-response/invalid/authorized-missing-authorization-id.json +0 -12
- package/fixtures/verify-response/invalid/missing-valid-until.json +0 -12
- package/fixtures/verify-response/invalid/observed-without-last-seen.json +0 -13
- package/fixtures/verify-response/invalid/unknown-status.json +0 -13
- package/fixtures/verify-response/invalid/unlisted-with-reason.json +0 -12
- package/fixtures/verify-response/invalid/valid-until-over-24h.json +0 -13
- package/fixtures/verify-response/valid/brand-unverified-no-reason.json +0 -11
- package/fixtures/verify-response/valid/brand-unverified-reason.json +0 -12
- /package/schemas/{common.json → 0.1/common.json} +0 -0
- /package/schemas/{file-full.json → 0.1/file-full.json} +0 -0
- /package/schemas/{file-pointer.json → 0.1/file-pointer.json} +0 -0
- /package/schemas/{file-private.json → 0.1/file-private.json} +0 -0
- /package/schemas/{file.json → 0.1/file.json} +0 -0
- /package/schemas/{verify-request.json → 0.1/verify-request.json} +0 -0
- /package/schemas/{verify-response.json → 0.1/verify-response.json} +0 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
# Authorized Retailers Specification v0.2
|
|
2
|
+
|
|
3
|
+
Draft v0.2 · 28 September 2026
|
|
4
|
+
Editor: Ed Jacobs
|
|
5
|
+
|
|
6
|
+
Licensed under [CC BY 4.0](https://github.com/authorizedretailers/spec/blob/main/LICENSE-SPEC). JSON Schemas and reference code: Apache-2.0.
|
|
7
|
+
|
|
8
|
+
## 1. Overview and scope
|
|
9
|
+
|
|
10
|
+
This spec defines how a brand records which retailers it has authorized to sell its products, and how an AI shopping agent verifies that status before recommending or buying. It is an open format with a single registry: anyone may read, cache or relay what the registry publishes, and anyone may build software on the format, but authorizedretailers.ai is the only registry. A list or answer counts only if it verifies against the registry's published key set (section 8).
|
|
11
|
+
|
|
12
|
+
The registry works like a notary. It checks that a brand controls its domain and is independently linked to it (section 4), records the brand's own decisions and signs them. The signature says who published a list, not who is authorized. Only the brand decides that (section 3).
|
|
13
|
+
|
|
14
|
+
Status: draft v0.2, for discussion with brands, retailers and agent developers. Breaking changes are expected before v1.0. Changes are listed in section 14.
|
|
15
|
+
|
|
16
|
+
The spec asserts one thing: whether a brand has authorized a given seller, on a given channel, in a given territory, for a given product, as of a given date.
|
|
17
|
+
|
|
18
|
+
Scope: the spec covers consumer-facing online channels: marketplaces and web stores. Every channel is identified by a domain, because agents buy from domains. Physical storefronts are not channels: an omnichannel retailer is listed by its web domain. Trade, wholesale and practitioner-only channels are out of scope. Sellers there are not listed, so their resale on consumer channels verifies as unlisted. Purchases across borders (for example on amazon.com for delivery to Canada) are out of scope for v0.2.
|
|
19
|
+
|
|
20
|
+
It does not assert:
|
|
21
|
+
|
|
22
|
+
- **Authenticity.** An authorized seller can still sell counterfeit goods or have its account compromised. Authorization and authenticity are separate questions.
|
|
23
|
+
- **Pricing.** The format carries no price, MAP or discount terms.
|
|
24
|
+
- **Legal status.** A seller not listed is unlisted, not unlawful. How an agent or platform acts on an unlisted result is its own decision.
|
|
25
|
+
|
|
26
|
+
The key words MUST, SHOULD and MAY are used as defined in RFC 2119.
|
|
27
|
+
|
|
28
|
+
## 2. Terminology
|
|
29
|
+
|
|
30
|
+
| Term | Meaning |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Brand | The owner of a trademark whose products are sold, identified by one or more verified domains. |
|
|
33
|
+
| Retailer | Any seller authorized to sell the brand's products: a marketplace seller or a web store. |
|
|
34
|
+
| Distributor | A party that supplies retailers. A distributor MAY propose retailers to a brand that has designated it, but cannot authorize them. |
|
|
35
|
+
| Authorization | A brand's statement that a retailer may sell within a stated scope, until the brand revokes it or until an end date the brand sets. |
|
|
36
|
+
| Channel | A place a seller sells, identified by a domain: a marketplace domain and seller ID, or a web store's domain. |
|
|
37
|
+
| Catalog | The brand's list of products on the registry, used only to define what an authorization covers (section 3.1). |
|
|
38
|
+
| Product | One entry in a catalog: a brand-chosen ID, a name and at least one GTIN or ASIN. |
|
|
39
|
+
| Product line | A named group of products, such as "Classic Tee". |
|
|
40
|
+
| Observation | Evidence from outside the brand's list that an authorized seller is actively selling on a channel. |
|
|
41
|
+
| Registry | authorizedretailers.ai: the service that verifies brands, records their decisions, signs lists and answers and responds to verify requests. |
|
|
42
|
+
| List | The registry's signed copy of one brand's authorizations and catalog (section 5.2). |
|
|
43
|
+
| Pointer | An optional file on a brand's domain that names the brand's list and repeats its verification token (section 5.1). |
|
|
44
|
+
| Mirror | A copy of the registry's published keys, public lists and revocation feed, run on independent infrastructure (section 10.2). |
|
|
45
|
+
| Agent | Any automated system that queries authorization status before recommending or buying. |
|
|
46
|
+
|
|
47
|
+
## 3. Authorization model
|
|
48
|
+
|
|
49
|
+
Every authorization MUST come from the brand. No other party can authorize a retailer, including distributors and the registry itself.
|
|
50
|
+
|
|
51
|
+
```mermaid
|
|
52
|
+
flowchart LR
|
|
53
|
+
D[Distributor] -->|proposes retailer| B[Brand]
|
|
54
|
+
R[Retailer] -->|requests listing| B
|
|
55
|
+
B -->|approves + scopes| A[Authorization]
|
|
56
|
+
A -->|signed by registry| L[List]
|
|
57
|
+
L -->|verify query| G[Agent]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Distributors and retailers can request an authorization. Only the brand's approval creates one. Anything the brand has not approved verifies as unlisted.
|
|
61
|
+
|
|
62
|
+
The registry MUST NOT add, remove or change an authorization except on the brand's instruction, or when the brand approves a distributor's proposal. The registry MUST keep a change log for each brand, recording what changed, who changed it and when, and MUST show that log to the brand.
|
|
63
|
+
|
|
64
|
+
The registry SHOULD accept proposals from a distributor only if the brand has designated it and it has proven control of its own domain, by a DNS TXT record as in section 4 or another method the registry offers. A brand MAY limit what a designated distributor can propose: by territory, by product line, by product and to web stores, marketplace sellers or both.
|
|
65
|
+
|
|
66
|
+
A request to a brand that is not yet registered MAY be held by the registry and delivered once the brand registers. A held request has no effect on verify answers.
|
|
67
|
+
|
|
68
|
+
Each authorization MUST carry:
|
|
69
|
+
|
|
70
|
+
- **Channels:** at least one channel identifier (section 6).
|
|
71
|
+
- **Product scope:** product lines (`product_lines`: named lines, or `["all"]`), individual products (`products`: catalog product IDs), or both. Exclusions MAY narrow it (section 3.1).
|
|
72
|
+
|
|
73
|
+
Each authorization MAY carry:
|
|
74
|
+
|
|
75
|
+
- **Territories:** ISO 3166-1 alpha-2 country codes, or `*` for worldwide. Most channels take their territory from their domain, so territories are needed only for web stores on generic domains (section 6.2).
|
|
76
|
+
- **End date:** `expires`, set by the brand. Without one, the authorization lasts until the brand revokes it (section 10).
|
|
77
|
+
- **Proposed by:** the distributor that proposed it, so brands can see their supply chain.
|
|
78
|
+
|
|
79
|
+
### 3.1 Products and catalog
|
|
80
|
+
|
|
81
|
+
A brand MAY keep a catalog on the registry: a list of products. The catalog exists only to define what an authorization covers. It is not a general product database, holds only what the brand chooses to add, and the registry does not offer it as a product lookup service.
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"id": "EB-TEE-BLK-M",
|
|
86
|
+
"name": "Classic Tee, Black, M",
|
|
87
|
+
"product_line": "Classic Tee",
|
|
88
|
+
"attributes": { "color": "Black", "size": "M" },
|
|
89
|
+
"gtin": "00012345678905",
|
|
90
|
+
"asins": ["B0EXAMPLE1"]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Field | Rules |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `id` | Required. Chosen by the brand (a SKU works) and unique within the catalog. |
|
|
97
|
+
| `name` | Required. |
|
|
98
|
+
| `product_line` | Optional. The same product line named in `scope.product_lines`, so a brand can authorize a whole line in one step. |
|
|
99
|
+
| `attributes` | Optional flat string key/value pairs, for display and filtering. Never used for matching. |
|
|
100
|
+
| `gtin` | Optional. 8, 12, 13 or 14 digits with a valid GS1 check digit (a UPC, EAN or GTIN). Compared as GTIN-14: left-padded with zeros to 14 digits. |
|
|
101
|
+
| `asins` | Optional. Amazon ASINs: 10 uppercase letters or digits. An ASIN applies on every Amazon marketplace domain. |
|
|
102
|
+
|
|
103
|
+
Each product MUST have a `gtin` or at least one ASIN. A GTIN or ASIN MUST NOT appear on two products in the same catalog.
|
|
104
|
+
|
|
105
|
+
An authorization's scope can be narrowed with exclusions, and a brand can exclude products from every authorization:
|
|
106
|
+
|
|
107
|
+
- `scope.exclude_product_lines` and `scope.exclude_products`: products this retailer is not authorized for, such as every line except one, or a line except its newest release.
|
|
108
|
+
- `exclusions.product_lines` and `exclusions.products` on the list: products no retailer is authorized for, such as products sold only direct to consumers.
|
|
109
|
+
|
|
110
|
+
A product is covered by an authorization when `product_lines` is `["all"]`, or includes the product's line, or `products` includes its `id`, and it is not excluded by the authorization or by the brand. Exclusions always win over inclusions.
|
|
111
|
+
|
|
112
|
+
Every product line named in a scope or an exclusion MUST exist in the catalog when the brand has a catalog. Every product ID named in `products`, `exclude_products` or `exclusions.products` MUST exist in the catalog. Named products and exclusions therefore need a catalog.
|
|
113
|
+
|
|
114
|
+
Matching a verify request against the catalog is described in section 9.2.
|
|
115
|
+
|
|
116
|
+
## 4. Brand identity and domain verification
|
|
117
|
+
|
|
118
|
+
A brand is identified by its web domain. The registry MUST verify control of a domain before accepting any authorization for it.
|
|
119
|
+
|
|
120
|
+
The registry issues a verification token for each domain. The brand proves control by one of two methods:
|
|
121
|
+
|
|
122
|
+
1. **DNS TXT record** (recommended): `authorizedretailers-verify=<token>` on the brand's domain. This works on every site platform, because every domain's DNS can hold a TXT record.
|
|
123
|
+
2. **Pointer file:** the token in the `verify_token` field of a pointer served at the domain (section 5.1). Use it when the brand can serve files under `/.well-known/` but cannot change DNS.
|
|
124
|
+
|
|
125
|
+
The token is public. It proves control only of the domain it was issued for, found on that domain, and it grants no access to the brand's account.
|
|
126
|
+
|
|
127
|
+
Domain control does not prove brand ownership, because a counterfeiter can register a lookalike domain and verify it. The registry MUST also link the domain to the brand through at least one independent source, such as the domain listed on the brand's trademark filing, official website or marketplace storefront.
|
|
128
|
+
|
|
129
|
+
The registry SHOULD recheck each domain at least weekly. If neither the DNS record nor a pointer with the right token is found, the registry MUST treat the domain as `verification_lapsed` and notify the brand. It MUST NOT keep returning `authorized` on a lapsed domain.
|
|
130
|
+
|
|
131
|
+
A brand is **registered** for a domain when it has an account on the registry, the domain is verified by one of the methods above and the independent link is made. For any domain that is not registered, verify answers are `brand_not_registered` with a reason (section 9):
|
|
132
|
+
|
|
133
|
+
| `reason` | Meaning |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| `not_registered` | The registry holds no account for this domain. A file on the brand's domain alone doesn't count |
|
|
136
|
+
| `domain_unlinked` | Domain control is verified, but the independent brand link has not been made |
|
|
137
|
+
| `verification_lapsed` | Neither the DNS record nor a pointer with the right token is in place any more |
|
|
138
|
+
|
|
139
|
+
### 4.1 Several domains per brand
|
|
140
|
+
|
|
141
|
+
A brand account MAY hold several domains (for example brand.com, brand.co.uk and brandstore.com). Each is verified and linked separately. For each domain it adds, the brand chooses to share an existing domain's list (one list, kept in sync), copy it (a separate list that starts identical and is then edited on its own) or start a new list.
|
|
142
|
+
|
|
143
|
+
A domain is identity, not territory: a brand whose domain is brand.co.uk can authorize US channels.
|
|
144
|
+
|
|
145
|
+
## 5. The pointer file and the signed list
|
|
146
|
+
|
|
147
|
+
### 5.1 The pointer (optional)
|
|
148
|
+
|
|
149
|
+
A brand MAY serve a pointer at `https://<brand-domain>/.well-known/authorized-retailers.json`, as JSON over HTTPS. The pointer is optional: DNS verification (section 4) is enough for a brand's list to count. A pointer helps software that finds brands by domain, and it lets a brand that cannot change DNS verify its domain.
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"spec": "authorized-retailers/0.2",
|
|
154
|
+
"form": "pointer",
|
|
155
|
+
"brand": { "name": "Example Brand", "domain": "brand.example" },
|
|
156
|
+
"verify_token": "<token from the registry>",
|
|
157
|
+
"list": "https://authorizedretailers.ai/v0/brands/brand.example/list"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- `form` is `pointer`. It is the only form in v0.2. The field stays so that later versions can add forms. Readers MUST treat a file with any other `form`, including the v0.1 `full` and `private` forms, as no file.
|
|
162
|
+
- `brand.domain` MUST be the domain the pointer was fetched from. A pointer vouches only for its own domain.
|
|
163
|
+
- `list` MUST be an `https://authorizedretailers.ai/` URL. A pointer naming any other host is treated as no file.
|
|
164
|
+
- `verify_token` is the domain's token from the registry (section 4).
|
|
165
|
+
|
|
166
|
+
The pointer carries no authorization data, so it never goes out of date, and it is not signed. The list it names is signed, and so is every verify answer.
|
|
167
|
+
|
|
168
|
+
Readers MAY follow up to 3 HTTPS redirects when fetching the pointer (for example from the bare domain to `www`), but `brand.domain` MUST still match the domain the reader first asked for, ignoring a leading `www.`. Readers SHOULD accept the file when it is served as `application/json` or `text/plain`.
|
|
169
|
+
|
|
170
|
+
`authorized-retailers.json` is being registered in the IANA Well-Known URIs registry. This section will link to the entry once it is registered.
|
|
171
|
+
|
|
172
|
+
### 5.2 The signed list
|
|
173
|
+
|
|
174
|
+
The registry publishes each brand's list at `https://authorizedretailers.ai/v0/brands/<domain>/list`. Brands never build or sign their own list: they make authorization decisions on the registry, and the registry builds and signs the list.
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"spec": "authorized-retailers/0.2",
|
|
179
|
+
"brand": { "name": "Example Brand", "domains": ["brand.example", "brand.example.co.uk"] },
|
|
180
|
+
"public": true,
|
|
181
|
+
"issued": "2026-09-28T14:00:00Z",
|
|
182
|
+
"valid_until": "2026-09-28T14:15:00Z",
|
|
183
|
+
"stale_until": "2026-09-29T14:00:00Z",
|
|
184
|
+
"catalog": [
|
|
185
|
+
{ "id": "EB-TEE-BLK-M", "name": "Classic Tee, Black, M", "product_line": "Classic Tee", "gtin": "00012345678905", "asins": ["B0EXAMPLE1"] }
|
|
186
|
+
],
|
|
187
|
+
"exclusions": { "product_lines": [], "products": [] },
|
|
188
|
+
"authorizations": [
|
|
189
|
+
{
|
|
190
|
+
"id": "auth_01J9X2",
|
|
191
|
+
"revocation_id": "rv_Q2l8dX4bT0aN5kPw",
|
|
192
|
+
"retailer": { "name": "Example Retail LLC", "entity_id": "ent_7Q4M" },
|
|
193
|
+
"channels": [
|
|
194
|
+
{ "type": "amazon", "domain": "amazon.com", "seller_id": "A1B2C3D4E5F6G7" },
|
|
195
|
+
{ "type": "walmart", "domain": "walmart.com", "seller_id": "101234567" },
|
|
196
|
+
{ "type": "web", "domain": "retailer.example" }
|
|
197
|
+
],
|
|
198
|
+
"scope": { "territories": ["US"], "product_lines": ["all"] },
|
|
199
|
+
"proposed_by": null
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
"signature": { "kid": "ar-20260928-6e37", "jws": "<detached JWS>" }
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- `brand.domains` names every domain the list applies to, so an agent can see that two domains share one list.
|
|
207
|
+
- `issued`, `valid_until` and `stale_until` govern freshness of this signed copy, not the authorizations (section 10).
|
|
208
|
+
- `catalog` and `exclusions` are optional.
|
|
209
|
+
- `revocation_id` is a random value that changes whenever the authorization's coverage shrinks (section 10.3).
|
|
210
|
+
|
|
211
|
+
A list counts only if its signature verifies against the registry's key set and `brand.domains` includes the domain the reader asked about. Readers MUST treat an unsigned list, or one whose signature does not verify, as no list.
|
|
212
|
+
|
|
213
|
+
Whether a brand's list is public is a setting on the registry. The catalog follows the same setting. For a brand whose list is not public, the list URL returns a signed response with `"public": false` and no catalog or authorizations:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"spec": "authorized-retailers/0.2",
|
|
218
|
+
"brand": { "name": "Example Brand", "domains": ["brand.example"] },
|
|
219
|
+
"public": false,
|
|
220
|
+
"issued": "2026-09-28T14:00:00Z",
|
|
221
|
+
"valid_until": "2026-09-28T14:15:00Z",
|
|
222
|
+
"stale_until": "2026-09-29T14:00:00Z",
|
|
223
|
+
"signature": { "kid": "ar-20260928-6e37", "jws": "<detached JWS>" }
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Agents then use the verify endpoint, which answers one question at a time (section 9). A domain that is not registered returns HTTP 404 from the list URL.
|
|
228
|
+
|
|
229
|
+
## 6. Seller identity and channel identifiers
|
|
230
|
+
|
|
231
|
+
Agents see sellers as channel identifiers, not company names, so every authorization MUST name at least one channel identifier. A name alone is not verifiable.
|
|
232
|
+
|
|
233
|
+
| Channel type | Required fields | Identifier |
|
|
234
|
+
| --- | --- | --- |
|
|
235
|
+
| `amazon` | `domain`, `seller_id` | Amazon merchant ID on that marketplace domain |
|
|
236
|
+
| `walmart` | `domain`, `seller_id` | Walmart Marketplace partner ID |
|
|
237
|
+
| `ebay` | `domain`, `seller_id` | eBay user ID |
|
|
238
|
+
| `web` | `domain` | The retailer's store domain |
|
|
239
|
+
|
|
240
|
+
Every `domain` is lowercase, with no scheme, port, path, trailing dot or leading `www.`. For `amazon`, `walmart` and `ebay`, `domain` MUST be one of that type's marketplace domains in the table below. A channel whose type and domain don't match (for example `"type": "amazon"` with `walmart.com`) is invalid, and a `web` channel MUST NOT use a marketplace domain. The v0.1 `marketplace` field and `physical` channel type are removed, and readers MUST reject them.
|
|
241
|
+
|
|
242
|
+
| Marketplace domain | Type | Country |
|
|
243
|
+
| --- | --- | --- |
|
|
244
|
+
| amazon.com | amazon | US |
|
|
245
|
+
| amazon.ca | amazon | CA |
|
|
246
|
+
| amazon.co.uk | amazon | GB |
|
|
247
|
+
| amazon.de | amazon | DE |
|
|
248
|
+
| amazon.fr | amazon | FR |
|
|
249
|
+
| amazon.it | amazon | IT |
|
|
250
|
+
| amazon.es | amazon | ES |
|
|
251
|
+
| amazon.com.au | amazon | AU |
|
|
252
|
+
| walmart.com | walmart | US |
|
|
253
|
+
| walmart.ca | walmart | CA |
|
|
254
|
+
| ebay.com | ebay | US |
|
|
255
|
+
| ebay.co.uk | ebay | GB |
|
|
256
|
+
| ebay.ca | ebay | CA |
|
|
257
|
+
| ebay.de | ebay | DE |
|
|
258
|
+
| ebay.com.au | ebay | AU |
|
|
259
|
+
|
|
260
|
+
New channel types and marketplace domains are added by the registry and published in the changelog.
|
|
261
|
+
|
|
262
|
+
### 6.1 Matching identifiers
|
|
263
|
+
|
|
264
|
+
Agents MUST match on the channel identifier they see, never on the retailer name. Two identifiers are the same when their channel keys are equal:
|
|
265
|
+
|
|
266
|
+
| Channel type | Channel key |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| `amazon` | `amazon:<domain>:<seller_id>`, seller ID trimmed and uppercased |
|
|
269
|
+
| `walmart` | `walmart:<domain>:<seller_id>`, seller ID trimmed, case kept |
|
|
270
|
+
| `ebay` | `ebay:<domain>:<seller_id>`, seller ID trimmed and lowercased |
|
|
271
|
+
| `web` | `web:<domain>` |
|
|
272
|
+
|
|
273
|
+
Domains in keys are normalized as above. A seller ID is 1 to 128 characters with no whitespace.
|
|
274
|
+
|
|
275
|
+
The registry MAY group a retailer's identifiers under one `entity_id`, so one retailer selling on several channels is one record. Registry-proposed links between identifiers MUST be confirmed by the brand before they count as authorized.
|
|
276
|
+
|
|
277
|
+
### 6.2 Territory
|
|
278
|
+
|
|
279
|
+
Each channel's territory comes from its domain:
|
|
280
|
+
|
|
281
|
+
- **Marketplace domain:** the country in the table above. It cannot be changed. If an authorization lists `scope.territories` and they leave out a marketplace channel's country, the authorization is invalid (`territory_mismatch`).
|
|
282
|
+
- **Web store on a country-code domain** (`.ca`, `.co.uk`, `.de` and so on): that country by default. An authorization MAY list other territories. The validator warns (`territory_differs_from_domain`) when they leave out the domain's country.
|
|
283
|
+
- **Web store on a generic domain** (`.com`, `.net`, `.shop` and so on): no default. The authorization MUST list `scope.territories`, which MAY be `["*"]` for worldwide.
|
|
284
|
+
|
|
285
|
+
A channel's effective territories are its domain's country for a marketplace, and `scope.territories` when given, otherwise the domain's country, for a web store.
|
|
286
|
+
|
|
287
|
+
A country-code domain is one whose last label is a two-letter ISO 3166-1 code, with `uk` read as `GB`. These two-letter endings are widely used as generic domains and are treated as generic: `ac`, `ai`, `cc`, `co`, `fm`, `gg`, `io`, `la`, `ly`, `me`, `sh`, `so`, `to`, `tv`, `vc` and `ws`. Adding to this list is a registry change published in the changelog.
|
|
288
|
+
|
|
289
|
+
Country codes are ISO 3166-1 alpha-2: the United Kingdom is `GB`, not `UK`.
|
|
290
|
+
|
|
291
|
+
### 6.3 Proving channel identifiers
|
|
292
|
+
|
|
293
|
+
The registry MAY let retailers prove their own channel identifiers before they request a listing: a `web` domain by a DNS TXT record as in section 4 or another method the registry offers, and a marketplace seller ID by confirming with the marketplace that it exists on that domain. Confirming that a seller ID exists does not prove that the retailer controls it, and the registry MUST NOT present it as proof of control.
|
|
294
|
+
|
|
295
|
+
The registry MAY decline to register a marketplace seller that does not publicly list a seller name, business name and address on its marketplace.
|
|
296
|
+
|
|
297
|
+
## 7. Evidence and observation
|
|
298
|
+
|
|
299
|
+
Every answer about a seller rests on the same evidence: a verified domain independently linked to the brand (section 4), the brand's own approval (section 3), and the registry's signature (section 8). There are no weaker grades of answer. Where that evidence is missing, the answer is `brand_not_registered`, whether or not a file exists on the brand's domain.
|
|
300
|
+
|
|
301
|
+
The registry MAY also observe sellers: evidence from outside the brand's list that an authorized seller is actively selling the brand's products on the stated channel. Every answer MUST carry `observed`. On an `authorized` answer it is `{ "last_seen": <timestamp> }` when the registry has seen this seller selling within the last 30 days, and otherwise `null`. On every other status it MUST be `null`. Observation never changes the status.
|
|
302
|
+
|
|
303
|
+
The registry does not observe sellers yet. Amazon marketplaces are planned first.
|
|
304
|
+
|
|
305
|
+
Observation can also raise flags on an authorized seller: sudden changes in account name, address, catalog or volume that suggest a compromised account. The registry MAY return these as `signals` alongside the answer, without changing the authorization status.
|
|
306
|
+
|
|
307
|
+
## 8. Signing and key management
|
|
308
|
+
|
|
309
|
+
Lists, verify answers and the revocation feed are signed with a detached JWS (RFC 7515) over the JSON canonicalized per RFC 8785, using Ed25519 (`EdDSA`). Anyone can check a signature without trusting the registry's servers.
|
|
310
|
+
|
|
311
|
+
- **Public keys:** published at `https://authorizedretailers.ai/.well-known/jwks.json`. Every signature names the key that made it (`kid`).
|
|
312
|
+
- **Agent behavior:** agents SHOULD cache the key set and refetch it when they see an unknown `kid`. They MUST reject signatures from keys no longer in the set. Mirrors serve a copy of the key set, but an agent MUST NOT trust a key it has not also fetched from authorizedretailers.ai.
|
|
313
|
+
- **Scheduled rotation:** every 90 days. The new key is published at least 7 days before first use. The old key stays published until everything it signed is past its `stale_until` (section 10).
|
|
314
|
+
- **Emergency rotation:** a compromised key is removed from the set immediately, and every current list is re-signed with a new key.
|
|
315
|
+
- **Storage:** the registry SHOULD hold private keys in a managed key service, sign server-side, and never expose keys to application code or staff.
|
|
316
|
+
|
|
317
|
+
Brands do nothing during rotation.
|
|
318
|
+
|
|
319
|
+
## 9. Verify API and MCP interface
|
|
320
|
+
|
|
321
|
+
An agent asks one question: is this seller authorized for this brand, on this channel, in this territory, for this product? The answer is signed and is the same whether the brand's list is public or not.
|
|
322
|
+
|
|
323
|
+
Request: `POST /v0/verify`
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"brand_domain": "brand.example",
|
|
328
|
+
"channel": { "type": "amazon", "domain": "amazon.com", "seller_id": "A1B2C3D4E5F6G7" },
|
|
329
|
+
"territory": "US",
|
|
330
|
+
"product": { "asin": "B0EXAMPLE1" },
|
|
331
|
+
"as_of": "2026-09-28"
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
| Field | Rules |
|
|
336
|
+
| --- | --- |
|
|
337
|
+
| `brand_domain` | Required. Any registered domain of the brand. The answer uses that domain's list. |
|
|
338
|
+
| `channel` | Required. A channel identifier (section 6). |
|
|
339
|
+
| `territory` | Optional. Defaults to the channel domain's country. Required for a web store on a generic domain. |
|
|
340
|
+
| `product_line` | Optional. Asks about a whole product line. |
|
|
341
|
+
| `product` | Optional. `{ "gtin": "..." }` or `{ "asin": "..." }`, exactly one. |
|
|
342
|
+
| `as_of` | Optional. An ISO 8601 date or UTC date-time. The answer reflects status at that time. Defaults to now. |
|
|
343
|
+
|
|
344
|
+
Response:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"status": "authorized",
|
|
349
|
+
"authorization_id": "auth_01J9X2",
|
|
350
|
+
"revocation_id": "rv_Q2l8dX4bT0aN5kPw",
|
|
351
|
+
"checked": "2026-09-28T14:02:11Z",
|
|
352
|
+
"valid_until": "2026-09-28T14:17:11Z",
|
|
353
|
+
"stale_until": "2026-09-29T14:02:11Z",
|
|
354
|
+
"product": { "in_catalog": true, "id": "EB-TEE-BLK-M", "product_line": "Classic Tee" },
|
|
355
|
+
"observed": null,
|
|
356
|
+
"signals": [],
|
|
357
|
+
"signature": { "kid": "ar-20260928-6e37", "jws": "<detached JWS>" }
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
| `status` | Meaning |
|
|
362
|
+
| --- | --- |
|
|
363
|
+
| `authorized` | An authorization covers this seller, channel, territory and product |
|
|
364
|
+
| `unlisted` | The brand is registered and no authorization covers this request |
|
|
365
|
+
| `expired` | An authorization with an end date covered this seller, and the end date has passed |
|
|
366
|
+
| `brand_not_registered` | The brand domain is not registered (section 4) |
|
|
367
|
+
| `disputed` | A dispute on this authorization is open and the brand has flagged it for review (section 11) |
|
|
368
|
+
|
|
369
|
+
- `authorization_id` appears on `authorized`, `expired` and `disputed`. `expires` appears with it when the authorization has an end date.
|
|
370
|
+
- `revocation_id` appears only on `authorized` (section 10.3).
|
|
371
|
+
- `as_of` appears when the request gave one.
|
|
372
|
+
- Every signed answer MUST carry `valid_until`, no later than 15 minutes after `checked`, and `stale_until`, no later than 24 hours after `checked` (section 10).
|
|
373
|
+
- `product` appears when the request gave one: `in_catalog` always, and `id` and `product_line` only for a brand whose list is public.
|
|
374
|
+
|
|
375
|
+
`reason` is optional on `brand_not_registered` (section 4) and `unlisted`, and MUST NOT appear on any other status:
|
|
376
|
+
|
|
377
|
+
| `unlisted` reason | Meaning |
|
|
378
|
+
| --- | --- |
|
|
379
|
+
| `territory_mismatch` | The seller is authorized on this marketplace domain, but the requested territory is not that domain's country |
|
|
380
|
+
| `product_excluded` | The product is in the catalog and excluded, by the authorization or by the brand |
|
|
381
|
+
| `product_not_in_scope` | The product is in the catalog but the authorization doesn't include it |
|
|
382
|
+
| `product_not_in_catalog` | The product is not in the catalog and the authorization is limited to named lines or products |
|
|
383
|
+
|
|
384
|
+
### 9.1 Seller first
|
|
385
|
+
|
|
386
|
+
Authorization is decided for the seller first: channel, seller ID, territory and date. Only authorizations that cover the seller are then checked against a product line or product. A product never makes an uncovered seller authorized. In particular, an unauthorized seller that sets up a duplicate listing under a new ASIN stays unlisted.
|
|
387
|
+
|
|
388
|
+
### 9.2 Matching a product
|
|
389
|
+
|
|
390
|
+
When a request names a product, the registry looks it up in the catalog by GTIN-14 or ASIN, and for the authorizations that cover the seller:
|
|
391
|
+
|
|
392
|
+
1. Product in the catalog and covered by an authorization: `authorized`.
|
|
393
|
+
2. Product in the catalog, covered by none of them, and excluded by the brand or by at least one of them: `unlisted`, reason `product_excluded`.
|
|
394
|
+
3. Product in the catalog and not included by any of them: `unlisted`, reason `product_not_in_scope`.
|
|
395
|
+
4. Product not in the catalog, and every covering authorization is limited to named lines or products: `unlisted`, reason `product_not_in_catalog`.
|
|
396
|
+
5. Product not in the catalog, and a covering authorization has `product_lines: ["all"]`: `authorized`, with the signal `product_not_in_catalog`.
|
|
397
|
+
|
|
398
|
+
A product that isn't in the catalog may be a duplicate listing, which is why every answer about a product says whether it is `in_catalog`.
|
|
399
|
+
|
|
400
|
+
A request with `product_line` is covered when an authorization's `product_lines` is `["all"]` or includes that line, and the line is not excluded.
|
|
401
|
+
|
|
402
|
+
### 9.3 Lists that are not public
|
|
403
|
+
|
|
404
|
+
For a brand whose list is not public, the verify API answers only the question asked. It MUST NOT return the brand's other retailers or catalog, and SHOULD rate-limit queries per caller so the list can't be reconstructed by enumeration.
|
|
405
|
+
|
|
406
|
+
### 9.4 MCP
|
|
407
|
+
|
|
408
|
+
The same capability is exposed as MCP tools: `verify_seller` (the request above, including `product` and `as_of`), `get_brand` (registration status, whether the list is public, last update) and `list_authorized_retailers` (for brands whose list is public).
|
|
409
|
+
|
|
410
|
+
## 10. Expiry, revocation and freshness
|
|
411
|
+
|
|
412
|
+
An authorization lasts until the brand revokes it, unless the brand gives it an end date (`expires`). There is no default end date and no maximum.
|
|
413
|
+
|
|
414
|
+
- **Reminders:** for an authorization with an end date, the registry reminds the brand 30 and 7 days before it. One confirmation renews every authorization the brand selects.
|
|
415
|
+
- **Revocation:** a brand can revoke an authorization at any time. The verify API MUST reflect a revocation within 1 minute.
|
|
416
|
+
- **Retailer notice:** the registry SHOULD notify a retailer when its authorization is revoked or reaches its end date, if the retailer has registered.
|
|
417
|
+
|
|
418
|
+
### 10.1 Freshness
|
|
419
|
+
|
|
420
|
+
Every signed list and answer carries `valid_until`, at most 15 minutes after it was issued, and `stale_until`, at most 24 hours after it was issued. These describe the signed copy, not the authorization. The registry re-signs each list at least every 15 minutes, and immediately on any change. A revoked seller can therefore look authorized in a cached copy for about 16 minutes at most.
|
|
421
|
+
|
|
422
|
+
Agents SHOULD decide from the verify endpoint and MUST NOT rely on a cached list or answer past its `valid_until`, except as section 10.2 allows.
|
|
423
|
+
|
|
424
|
+
### 10.2 When the registry is unreachable
|
|
425
|
+
|
|
426
|
+
A registry outage never makes a seller unauthorized and never blocks a purchase on its own.
|
|
427
|
+
|
|
428
|
+
1. **Fail to unconfirmed, never to authorized.** An agent that can't get a current answer, or a stale one allowed below, treats the seller as unconfirmed. It MAY carry on as it would without this standard, but MUST NOT describe the seller as authorized.
|
|
429
|
+
2. **Stale answers.** Between `valid_until` and `stale_until`, an agent MAY use a list or answer only if the registry and every mirror are unreachable, and only after checking the revocation feed (section 10.3). It MUST mark the result as stale to its user or in its logs. After `stale_until` the list or answer is unusable. This follows the pattern of HTTP `stale-if-error` (RFC 5861).
|
|
430
|
+
3. **Mirrors.** Mirrors run on a different provider and DNS from the registry. They serve the key set, every public list and the revocation feed, all signed by the registry. They never answer verify requests. Mirror base URLs are listed here when mirrors launch. There are none yet.
|
|
431
|
+
4. **Order.** An agent tries the live verify API, then a mirror (a public list, or a stale answer checked against the revocation feed), then treats the seller as unconfirmed.
|
|
432
|
+
|
|
433
|
+
### 10.3 Revocation feed
|
|
434
|
+
|
|
435
|
+
Every authorization carries a `revocation_id`: a random value that reveals nothing about the brand or seller. It appears in the list and on `authorized` answers only. When an authorization stops covering something it covered before (it is revoked, a channel is removed, its territories or products shrink, or its end date moves earlier), the registry publishes its `revocation_id` in the revocation feed and gives the authorization a new one.
|
|
436
|
+
|
|
437
|
+
The feed is a signed, append-only record of the last 24 hours, published every minute at `https://authorizedretailers.ai/v0/revocations` and on every mirror:
|
|
438
|
+
|
|
439
|
+
```json
|
|
440
|
+
{
|
|
441
|
+
"spec": "authorized-retailers/0.2",
|
|
442
|
+
"feed": "revocations",
|
|
443
|
+
"issued": "2026-09-28T14:02:00Z",
|
|
444
|
+
"valid_until": "2026-09-28T14:17:00Z",
|
|
445
|
+
"stale_until": "2026-09-29T14:02:00Z",
|
|
446
|
+
"entries": [{ "revocation_id": "rv_Q2l8dX4bT0aN5kPw", "revoked": "2026-09-28T13:40:12Z" }],
|
|
447
|
+
"signature": { "kid": "ar-20260928-6e37", "jws": "<detached JWS>" }
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
An agent holding a stale answer or list MUST check it against the newest feed it can get, which MUST itself be before its `stale_until`. It MUST drop any authorization whose `revocation_id` is in the feed. Only someone who was told a seller is authorized holds its `revocation_id`, so the feed can be public without revealing who was revoked.
|
|
452
|
+
|
|
453
|
+
## 11. Disputes and unclaimed listings
|
|
454
|
+
|
|
455
|
+
**Disputes.** A retailer can see its own status for any brand and can open a dispute if it believes a listing is wrong, for example a revocation it says was issued in error or an identifier mapped to the wrong entity.
|
|
456
|
+
|
|
457
|
+
1. The retailer submits the dispute with evidence.
|
|
458
|
+
2. The brand is notified and has 14 days to confirm, correct or reject.
|
|
459
|
+
3. While a dispute is open, verify answers return `disputed` only if the brand has flagged the authorization for review. Otherwise the brand's current decision stands.
|
|
460
|
+
4. The registry does not rule on commercial relationships. The brand's decision is final for authorization status. Identifier errors are corrected by the registry.
|
|
461
|
+
|
|
462
|
+
The registry's liability position, dispute timelines and data handling are set out in its terms of use, not in this spec.
|
|
463
|
+
|
|
464
|
+
**Unclaimed listings.** The registry MAY index authorized-retailer or dealer-locator pages that a brand already publishes, as `unclaimed` entries. These MUST be labeled as unverified, MUST NOT verify as `authorized`, and MUST link to the source page. A brand claims its listing by registering (section 4), after which the entries become editable and signable.
|
|
465
|
+
|
|
466
|
+
## 12. Security and privacy considerations
|
|
467
|
+
|
|
468
|
+
- **Brand impersonation:** mitigated by domain verification plus independent brand linkage (section 4). This is the highest-impact attack, because a false brand could authorize diverters.
|
|
469
|
+
- **Account takeover at the registry:** brand accounts MUST use multi-factor authentication. Revocations and new authorizations SHOULD trigger an email to every brand admin.
|
|
470
|
+
- **Registry changes:** the registry cannot change a brand's list on its own, and every change is in a log the brand can see (section 3).
|
|
471
|
+
- **Compromised seller accounts:** an authorized seller's marketplace account can be taken over. Observation signals (section 7) help, but authorization does not guarantee the seller's current conduct.
|
|
472
|
+
- **Mailbox addresses:** a seller whose listed address is a PO box or a commercial mailbox can be hard to trace. The registry MAY warn a brand before it authorizes such a seller. The warning is for the brand only and MUST NOT change verify answers.
|
|
473
|
+
- **List enumeration:** lists that are not public are protected by answering single questions only, with per-caller rate limits and query logging.
|
|
474
|
+
- **Revocation feed:** entries are random `revocation_id` values, not hashes of brand and seller, so nobody can test guesses against the feed.
|
|
475
|
+
- **Personal data:** channel identifiers and business names are business data. The registry MUST NOT publish personal addresses of sole-trader retailers.
|
|
476
|
+
- **Replay:** agents SHOULD check the `checked` timestamp on a signed answer and MUST reject answers past their `valid_until`, except as section 10.2 allows.
|
|
477
|
+
|
|
478
|
+
## 13. Open questions for v0.3
|
|
479
|
+
|
|
480
|
+
- [ ] Multi-brand owners: how a holding company verifies once and manages many brands.
|
|
481
|
+
- [ ] Territory splits where the trademark has different owners by country: one brand record or several?
|
|
482
|
+
- [ ] Observation on channels beyond Amazon: Walmart and retailer web domains first?
|
|
483
|
+
- [ ] Alignment with UCP and ACP: an extension field that points agents to a brand's list.
|
|
484
|
+
- [ ] Brand-declared `unauthorized`: a status for sellers the brand has explicitly said are not authorized, as distinct from `unlisted`, with a dispute path for the seller.
|
|
485
|
+
- [ ] Dispensary and multi-seller platforms (e.g. practitioner dispensaries hosted on one platform domain): how to authorize individual sellers when agents only see the platform domain.
|
|
486
|
+
- [ ] Channel audience: whether a channel needs an `audience` field (consumer, trade, practitioner) or whether consumer-only scope is enough.
|
|
487
|
+
- [ ] Cross-border purchases: buying on one country's marketplace for delivery to another.
|
|
488
|
+
- [ ] Per-marketplace ASINs: products whose ASIN differs between Amazon marketplaces.
|
|
489
|
+
- [ ] Finding the brand: how an agent finds the brand domain from a product page.
|
|
490
|
+
|
|
491
|
+
## 14. Changelog
|
|
492
|
+
|
|
493
|
+
- **2026-09-23**
|
|
494
|
+
- Verify answers carry `valid_until` (at most 24 hours after `checked`). Key retirement is based on it (sections 8, 9, 10, 12).
|
|
495
|
+
- `brand_unverified` answers MAY carry a `reason` (`not_registered`, `domain_unlinked`, `verification_lapsed`) (section 9).
|
|
496
|
+
- List endpoints respond to private-form brands exactly as to unknown brands (section 9).
|
|
497
|
+
- **2026-09-24**
|
|
498
|
+
- Key storage in a managed key service is a SHOULD for every registry, replacing the description of one registry's setup (section 8).
|
|
499
|
+
- Evidence tiers are removed. Every answer about a seller needs a verified, independently linked brand domain, the brand's approval and a registry signature; without them the answer is `brand_unverified` (sections 5, 7, 9).
|
|
500
|
+
- Only registry-signed files count. An unsigned file, or one whose signature doesn't verify, is treated as no file, and a file vouches only for the domain it's fetched from (section 5).
|
|
501
|
+
- Observation is a separate `observed` field (`{ "last_seen" }` or `null`) on every answer, instead of a tier (sections 7, 9).
|
|
502
|
+
- **2026-09-25**
|
|
503
|
+
- Distributors: a registry SHOULD accept proposals only from distributors the brand has designated and that have proven control of their domain. Brands MAY limit what a distributor can propose (sections 2, 3).
|
|
504
|
+
- A registry MAY hold a request to a brand that isn't registered yet and deliver it once the brand verifies (section 3).
|
|
505
|
+
- Retailer-side verification of channel identifiers, an open question, is now a MAY. Confirming a marketplace seller ID exists MUST NOT be presented as proof of control (sections 6, 13).
|
|
506
|
+
- A registry MAY decline marketplace sellers that don't list a seller name, business name and address (section 6).
|
|
507
|
+
- A registry MAY warn brands about sellers at PO boxes or commercial mailboxes. Verify answers don't change (section 12).
|
|
508
|
+
- Corrected: the reference registry doesn't observe sellers yet (section 7).
|
|
509
|
+
- Examples use reserved `.example` domains.
|
|
510
|
+
- **2026-09-26**
|
|
511
|
+
- Scope: the spec covers consumer-facing online channels, including omnichannel retailers identified by their web domain. Trade, wholesale and practitioner-only channels are out of scope (section 1).
|
|
512
|
+
- Every authorization MUST name at least one online channel identifier (`amazon`, `walmart`, `ebay` or `web`). A `physical` identifier alone no longer authorizes anything (section 6).
|
|
513
|
+
- Two open questions: dispensary and multi-seller platforms, and a channel `audience` field (section 13).
|
|
514
|
+
- **2026-09-28 (v0.2, breaking)**
|
|
515
|
+
- One registry. authorizedretailers.ai is the only registry, and its key set is the trust anchor. The format stays open (section 1).
|
|
516
|
+
- The registry MUST NOT change a brand's list on its own, and MUST keep a change log the brand can see (section 3).
|
|
517
|
+
- Domain verification: a DNS TXT record (recommended) or the `verify_token` in a pointer. The hosted `authorized-retailers-verify.txt` file is removed. The token is public (section 4).
|
|
518
|
+
- `brand_unverified` is renamed `brand_not_registered`, with the same reasons (sections 4, 9).
|
|
519
|
+
- A brand account can hold several domains, each verified separately, sharing or copying a list (section 4.1).
|
|
520
|
+
- The `full` and `private` file forms are removed. The only file is an optional, unsigned pointer whose `list` MUST be on authorizedretailers.ai. Readers MAY follow up to 3 redirects (section 5.1).
|
|
521
|
+
- The signed list lives on the registry. It names every brand domain it applies to, carries `issued`, `valid_until` and `stale_until` in place of `expires`, and adds an optional catalog and brand-level exclusions. Whether it is public is a registry setting; a list that isn't public returns a signed `"public": false` response instead of a 404 (section 5.2).
|
|
522
|
+
- Marketplace channels are identified by `domain` (for example `amazon.com`) instead of `marketplace` (`US`). The `physical` channel type is removed. A normative table lists the marketplace domains and their countries (section 6).
|
|
523
|
+
- Channel keys and seller ID normalization are written out (section 6.1).
|
|
524
|
+
- Territory follows each channel's domain. `scope.territories` is optional, needed only for web stores on generic domains, and must not contradict a marketplace domain's country (section 6.2).
|
|
525
|
+
- Brand catalog: authorizations can be scoped by product as well as by product line, with exclusions per authorization and per brand (section 3.1).
|
|
526
|
+
- Verify requests take an optional `product` (GTIN or ASIN) and `as_of`, and `territory` is optional. Answers carry `product`, `stale_until` and, when authorized, `revocation_id`. `unlisted` answers MAY carry a `reason` (section 9).
|
|
527
|
+
- Authorization is decided for the seller first, then for the product (sections 9.1, 9.2).
|
|
528
|
+
- No default or maximum expiry: an authorization lasts until revoked unless the brand sets an end date. Reminders apply only to authorizations with one (section 10).
|
|
529
|
+
- The verify API MUST reflect a revocation within 1 minute (was 15). `valid_until` is at most 15 minutes (was 24 hours) (sections 9, 10).
|
|
530
|
+
- Availability: `stale_until`, mirrors, a revocation feed of random IDs, and the rule that an outage leaves a seller unconfirmed, never authorized or unauthorized (section 10.2, 10.3).
|
|
531
|
+
- Bring-your-own-key signing and SKU-level scope are removed from the open questions. Cross-border purchases, per-marketplace ASINs and finding the brand domain are added (section 13).
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"spec": "authorized-retailers/0.1",
|
|
3
|
-
"form": "full",
|
|
4
|
-
"brand": {
|
|
5
|
-
"name": "Example Brand",
|
|
6
|
-
"domain": "brand.example"
|
|
7
|
-
},
|
|
8
|
-
"issued": "2026-09-23T00:00:00Z",
|
|
9
|
-
"expires": "2026-12-22T00:00:00Z",
|
|
10
|
-
"authorizations": [
|
|
11
|
-
{
|
|
12
|
-
"id": "auth_01J9X2",
|
|
13
|
-
"retailer": {
|
|
14
|
-
"name": "Example Retail LLC",
|
|
15
|
-
"entity_id": "ent_7Q4M"
|
|
16
|
-
},
|
|
17
|
-
"channels": [
|
|
18
|
-
{
|
|
19
|
-
"type": "amazon",
|
|
20
|
-
"marketplace": "US",
|
|
21
|
-
"seller_id": "A1B2C3D4E5F6G7"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"type": "walmart",
|
|
25
|
-
"marketplace": "US",
|
|
26
|
-
"seller_id": "101234567"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"type": "web",
|
|
30
|
-
"domain": "retailer.example"
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"scope": {
|
|
34
|
-
"territories": [
|
|
35
|
-
"US",
|
|
36
|
-
"CA"
|
|
37
|
-
],
|
|
38
|
-
"product_lines": [
|
|
39
|
-
"all",
|
|
40
|
-
"running"
|
|
41
|
-
]
|
|
42
|
-
},
|
|
43
|
-
"proposed_by": null,
|
|
44
|
-
"expires": "2026-12-22T00:00:00Z"
|
|
45
|
-
}
|
|
46
|
-
]
|
|
47
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"spec": "authorized-retailers/0.1",
|
|
3
|
-
"form": "full",
|
|
4
|
-
"brand": {
|
|
5
|
-
"name": "Example Brand",
|
|
6
|
-
"domain": "brand.example"
|
|
7
|
-
},
|
|
8
|
-
"issued": "2026-09-23T00:00:00Z",
|
|
9
|
-
"expires": "2026-12-22T00:00:00Z",
|
|
10
|
-
"authorizations": [
|
|
11
|
-
{
|
|
12
|
-
"id": "auth_01J9X2",
|
|
13
|
-
"retailer": {
|
|
14
|
-
"name": "Example Retail LLC",
|
|
15
|
-
"entity_id": "ent_7Q4M"
|
|
16
|
-
},
|
|
17
|
-
"channels": [
|
|
18
|
-
{
|
|
19
|
-
"type": "amazon",
|
|
20
|
-
"marketplace": "US",
|
|
21
|
-
"seller_id": "A1B2C3D4E5F6G7"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"type": "walmart",
|
|
25
|
-
"marketplace": "US",
|
|
26
|
-
"seller_id": "101234567"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"type": "web",
|
|
30
|
-
"domain": "retailer.example"
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"scope": {
|
|
34
|
-
"territories": [
|
|
35
|
-
"US",
|
|
36
|
-
"CA"
|
|
37
|
-
],
|
|
38
|
-
"product_lines": [
|
|
39
|
-
"all"
|
|
40
|
-
]
|
|
41
|
-
},
|
|
42
|
-
"proposed_by": null,
|
|
43
|
-
"expires": "2026-12-23T00:00:00Z"
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
}
|