> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pcnaid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Merchant REST API

> The Enterprise-gated REST API for reading verification records, managed through bearer credentials in the Developer page.

# Merchant REST API

The Enterprise merchant API provides a privacy-minimized, cursor-paginated read endpoint for verification records. Credentials are managed in the Developer page (`/app/developer`) by the Shopify account owner.

## Authentication

Bearers authenticate with `idgp_v1` tokens. Only a SHA-256 digest is stored; the plaintext token is returned once at creation. Tokens are hashed, revocable, and capped at five active credentials per shop.

```http theme={null}
Authorization: Bearer idgp_v1_<token>
```

The API re-checks the shop's Enterprise plan and the Merchant API setting on every request.

## Endpoints

### List verifications

```http theme={null}
GET /api/v1/verifications
```

Returns paginated verification records. Status, limit, and cursor are optional query parameters.

```bash theme={null}
curl -H "Authorization: Bearer idgp_v1_<token>" \
  '<app-api-origin>/api/v1/verifications?limit=50'
```

**Query parameters**

| Parameter | Type | Required | Description |
| - | - | - | - |
| `status` | string | No | Filter by internal status. Must be a recognized verification status value. |
| `limit` | integer | No | Results per page, 1 through 100. Defaults to 50. |
| `starting_after` | string | No | Cursor for the next page. Returned as `next_cursor` in the response. |

**Response fields per record**

* `id` — verification identifier
* `customer_id` — linked customer identifier, if any
* `provider` — provider name (`didit`, `stripe`, `self_certification`)
* `internal_status` — current lifecycle status
* `verification_type` — verification method
* `minimum_age` — minimum age applied for the decision
* `verified_at` — timestamp of verification
* `created_at` — creation timestamp
* `updated_at` — last update timestamp
* `country_code` — coarse country code from metadata
* `state_code` — coarse state or province code from metadata
* `last_error_code` — stable error code (never free-text)

The API never exposes date of birth, contact fields, provider reports, document images, biometrics, or raw provider error text.

**Supported methods**

Only `GET` is supported. Unsupported methods return `405`.

## Rate limits

Rate limiting is enforced per shop. See the app's rate-limit configuration for thresholds.

## Error response format

```json theme={null}
{
  "error": {
    "code": "invalid_limit",
    "message": "limit must be an integer from 1 through 100."
  }
}
```

**Authentication and authorization errors**

Every request re-checks the API key, the shop's plan, and the Merchant API setting. A failed check returns an `error` object with a machine-readable `code` and a `message`:

| Status | `code` | When |
| - | - | - |
| `401` | `invalid_api_key` | The bearer API key is missing, malformed, revoked, or not recognized for the shop. |
| `402` | `api_access_denied` | The shop is not on the Enterprise plan, which Merchant API access requires. |
| `403` | `api_access_denied` | Merchant API access is turned off in the app's settings. |

All responses include `Cache-Control: no-store`.

## Storefront app proxy

Shopify maps the storefront prefix `/apps/age-verification` to the app route `/api/proxy`. Requests are accepted only after the server verifies Shopify's app-proxy signature, timestamp, shop domain, and path prefix.

Use the store-facing path so Shopify generates the signed proxy request:

```bash theme={null}
curl 'https://<store-domain>/apps/age-verification/public-config'
```

| Method and storefront path | Implemented behavior |
| - | - |
| `GET /apps/age-verification/public-config` | Return the server-gated public age-gate configuration |
| `GET /apps/age-verification/settings` | Alias for `public-config` |
| `POST /apps/age-verification/verify` | Record a Simple-mode self-certification event or create/reuse an Enhanced identity session |
| `GET /apps/age-verification/verify?verificationId=...` | Return status, verified, retry eligibility, and the last error reason for a shop-owned verification |
| `GET /apps/age-verification/status?verificationId=...` | Return checkout enforcement, minimum age, expiry, active mode, publishable provider key, signed-in state, and customer verification state; `verificationId` is optional and supports the same-browser guest-to-account claim |

The `POST verify` parser accepts `countryCode`, `stateCode`, `event`, `dob`, and `termsAccepted`. It also parses `customerId` and `email`, but customer linkage uses Shopify's signed `logged_in_customer_id`; a client-supplied customer ID is not trusted.

Simple mode recognizes `event: "simple_verified"` and its compact storefront value `event: "s"`. Enhanced success returns the implemented session fields `verificationId`, `clientSecret`, `redirectUrl`, and `status`; the active provider determines whether `clientSecret` or `redirectUrl` is populated.

Direct route modules also exist at `/api/settings` and `/api/verify`, but they apply the same app-proxy signature check. They are not unsigned alternatives to the storefront proxy.

## Embedded-admin identity routes

These routes require an authenticated Shopify admin session and operate only on the authenticated shop's records.

| Method and route | Implemented behavior |
| - | - |
| `POST /api/identity/sessions` | Enterprise-gated identity-session creation or reuse |
| `GET /api/identity/sessions/:id` | Return a verification's internal/provider status, error, retry eligibility, and redaction state |
| `POST /api/identity/retry/:id` | Retry an eligible verification and return the next client secret or redirect URL |
| `POST /api/identity/redact/:id` | Request redaction of a shop-owned verification session |

Session creation accepts an optional internal `customerId`, normalized email, verification type, selfie requirement, ID-number requirement, and allowed document types. The settings record supplies the minimum age.

## Shopify POS routes

The POS API requires a Shopify POS session token in `Authorization: Bearer ...`. The server derives the shop from the token's signed `dest` claim.

| Method and route | Implemented behavior |
| - | - |
| `POST /api/pos/verify-session` | Auto-skip an already verified attached customer or create a pending verification with a signed POS token and phone URL |
| `GET /api/pos/verify-session?token=...` | Poll the real stored verification state for the signed token |
| `GET /api/pos/verify-qr/:token` | Render the QR image for the signed POS token from the app origin, with no-store caching |
| `GET /pos-verify/:token` | Validate the short-lived token and redirect the customer's phone to the hosted verification URL; invalid and expired tokens render 4xx pages |

The POST body accepts only `attachedCustomerId`. A pending response contains `status`, `autoSkipped`, `token`, `verifyUrl`, `minimumAge`, and `pollIntervalMs`. An auto-skip response contains `status`, `autoSkipped`, and `minimumAge`.

## Incoming webhook routes

| Route | Signature/authentication | Implemented events |
| - | - | - |
| `POST /webhooks` | Shopify webhook authentication | `APP_UNINSTALLED`, `APP_SUBSCRIPTIONS_UPDATE`, `CUSTOMERS_CREATE`, `CUSTOMERS_DATA_REQUEST`, `CUSTOMERS_REDACT`, and `SHOP_REDACT` |
| `POST /api/stripe/webhooks/identity` | Stripe raw-body signature | Identity session created, processing, verified, requires input, canceled, and redacted events handled by the webhook service |
| `POST /api/didit/webhooks` | Didit raw-body HMAC and timestamp | Apply provider decisions to matching non-terminal Didit verification records |

Didit is the production-selected identity provider. The Stripe Identity callback route serves the rollback path.

## Outbound verification-event webhooks

Pro stores can configure one public HTTPS event-webhook endpoint in Settings. Enterprise stores can also configure an API webhook endpoint, which is sent the same events. The implementation rejects credential-bearing URLs, literal IP targets, localhost, and DNS results that resolve only to private addresses.

The app sends JSON with four top-level fields: `event`, `shop`, `occurredAt`, and `data`. The implemented event names are:

* `identity_verification_started`
* `identity_verification_verified`
* `identity_verification_requires_input`
* `identity_verification_failed`

Verify `X-Pcnaid-Hmac-Sha256` as HMAC-SHA256 over the raw request body using the read-only signing secret shown in the app's Settings page. Event names are a fixed set and are not configurable.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.