---
title: "Verify a compliance certificate"
method: POST
path: "/api/v1/productions/{productionId}/verify-certificate"
tags: ["Productions"]
---

# Verify a compliance certificate

`POST /api/v1/productions/{productionId}/verify-certificate`

## Request

- Base URL: `https://easy2257.com`
- URL: `https://easy2257.com/api/v1/productions/{productionId}/verify-certificate`
- Auth: HTTP bearer

Validates a stored JWT compliance certificate against Easy2257's records: RS256 signature, expiry, revocation, and that the certificate belongs to the production in the URL. Use it for a nightly sweep or a dashboard check. For real-time signals prefer the `production.takedown_requested` webhook and the daily revocation list. For an offline check with no API call at all, verify the JWT against the public JWKS at `https://easy2257.com/.well-known/jwks.json`.

## Path parameters

- `productionId` string, required

## Request body

- VerifyCertificateBody
  - `certificate` string, required: The RS256 JWT compliance certificate Easy2257 issued, as delivered in the `production.documented` webhook.

## Response `200`

The check ran. Read `valid`: this status is returned for good and bad certificates alike.

- VerifyCertificateResponse
  - `valid` boolean, required: The only field you should branch on. A 200 response says the check ran, not that the certificate is good.
  - `productionId` string
  - `issuedAt` string, date-time: Present when valid is true.
  - `expiresAt` string, date-time: Present when valid is true.
  - `currentStatus` string: Current production documentation status. Present when valid is true. `unknown` if the production row has since been removed.
  - `reason` 'certificate_revoked' | 'certificate_superseded' | 'certificate_expired' | 'certificate_not_found' | 'signature_mismatch' | 'invalid_certificate' | 'no_public_key' | 'public_key_not_configured' | 'production_id_mismatch': Machine-readable failure reason, present when valid is false. `certificate_superseded` means a newer certificate replaced this one, either at annual renewal or when the producer reissued the sealed package after a correction: swap in the replacement (from the `production.certificate_renewed` webhook, the newer `production.documented` delivery, or `GET /api/v1/productions/{productionId}/certificates`); the content stays up. `certificate_revoked` means the certified record was pulled: take the content down. `production_id_mismatch` arrives with HTTP `400`: the certificate verified, but it belongs to a different production than the one in the URL. `no_public_key` and `public_key_not_configured` are Easy2257-side signing key problems, so retry rather than treating the certificate as bad.

## Other responses

- `400`: `certificate` missing (`missing_field`), or the certificate verified but belongs to a different production (`production_id_mismatch`, returned as `{ valid: false, reason }`).
- `401`: No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.
- `403`: Rejected before the handler: a test-mode key (`TEST_MODE_UNAVAILABLE`) or a partner account that is not ACTIVE. Never used for cross-partner access, which returns 404.
- `429`: Rate limit exceeded: 300 requests per 60 seconds per API key, shared across all `/api/v1` endpoints (requests without a `Bearer` key fall back to 60 per 60 seconds per client IP, and a per-address flood ceiling of 1200 per 60 seconds also applies). Honor `Retry-After`.
- `500`: Server error. Retry with exponential backoff.

---

[API](https://skmtc.dev/easy2257/apis/easy2257-partner-api.md) · [All operations](https://skmtc.dev/easy2257/apis/easy2257-partner-api/llms.txt) · [OpenAPI document](https://skmtc.dev/easy2257/apis/easy2257-partner-api/revisions/f880026c1f04?raw)
