Skip to main content
POST /v2/attestations/brand/:shopSlug/request and GET /v2/attestations/brand/:shopSlug/request-status are the merchant-facing CTA + polling endpoints behind the trust-fabric dashboard widget’s Request brand attestation modal. Together they queue an operator-reviewed request and let the storefront flip its UI between the four lifecycle states without leaving the dashboard.
Both endpoints are public — no JWT, no IP-allowlist. The :shopSlug is the URL-safe brand slug (same value the on-chain BrandAttestation.brandSlug carries — NOT the human-display name).

Lifecycle

A request walks four states. The on-chain Schema A mint is gated behind operator review — clicking the CTA does NOT directly mint: The status endpoint also reports a synthetic NONE state when no request row exists for the slug (used by the widget to render the initial CTA).

POST — submit a request

Idempotent on (shopSlug, status: PENDING). Re-submitting while a PENDING row exists returns the same requestId instead of creating a duplicate — the widget always renders a success toast and never has to handle a “duplicate” 4xx.

Request

Curl example

Response (200)

The response is intentionally minimal — operator-only fields (decidedBy, decisionReason, merchantId, internal notes) never cross the public surface. status is hard-cast to PENDING on this endpoint: an APPROVED / MINTED / REJECTED row can never be returned by the idempotency check (only PENDING matches the dedupe index).

Error responses

GET — poll the status

The widget polls this to walk through NONE → PENDING → APPROVED → MINTED (or → REJECTED) without leaving the merchant dashboard. Resolution rule: any PENDING row wins (there is at most one by index); otherwise the newest terminal row (createdAt desc) is returned.

Curl example

Response (200, history exists)

When status === "MINTED" the attestationUid resolves on EAS Base via the standard easscan link scheme — e.g. https://base.easscan.org/attestation/view/0x9c4f7a3e… — so the widget can link the merchant directly to the on-chain record without a second round-trip.

Response (200, no history)

The status endpoint always returns 200 — even when no request exists. The synthetic NONE discriminator lets the widget render the initial Request brand attestation CTA without 404-handling. Storefronts should treat request === null as the “Not requested” UI state.

Field reference

Operator-private fields (decidedBy, decisionReason, mintError, mintAttempts, merchantId, free-text notes) are SCRUBBED from the public projection — they only surface on the SUPER_ADMIN admin route.

TypeScript flow example

A merchant dashboard widget driving the four-state UI: