{
  "openapi": "3.1.0",
  "info": {
    "title": "Easy2257 Partner API",
    "version": "2026-07-27",
    "description": "The Easy2257 Partner API lets your platform outsource 18 U.S.C. § 2257 compliance in a single integration: performer ID verification, per-depiction content logging, and 7-year record retention, with Easy2257 named Custodian of Records. No endpoint in this API returns a performer's legal name, date of birth, government ID image, or Easy2257 user id. A platform integrating it never touches a government ID, so it cannot leak one. There is no sandbox: only `ez_live_` keys are accepted, and every call writes a production record.",
    "contact": {
      "name": "Easy2257 Developer Support",
      "email": "api@easy2257.com",
      "url": "https://docs.easy2257.com"
    }
  },
  "servers": [
    {
      "url": "https://easy2257.com",
      "description": "Production, and the only environment. There is no sandbox host."
    }
  ],
  "tags": [
    {
      "name": "Solo Accounts",
      "description": "Provision creators, log their content, and record takedowns."
    },
    {
      "name": "Talent",
      "description": "Check whether a creator you provisioned is verified."
    },
    {
      "name": "Collab Scenes",
      "description": "Multi-performer compliance: consent collection, releases, and cross-platform syndication."
    },
    {
      "name": "Hosted Attestation",
      "description": "Hand the attestation form to Easy2257 and build no compliance UI."
    },
    {
      "name": "Productions",
      "description": "Query documentation status and verify RS256 compliance certificates for professionally produced content."
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Partner API key from partner.easy2257.com/api-keys. Only `ez_live_` keys are accepted. A key beginning with `ez_test_` is rejected with `403` and code `TEST_MODE_UNAVAILABLE` before any handler runs: there is no sandbox, and every handler writes production records, provisions real users, and sends real email. New partner accounts start with a test key, so create a live key before your first call."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message. Switch on HTTP status and on `code`, never on this string: it is not part of the API contract.",
            "example": "email, externalUserId, and callbackUrl are required"
          },
          "code": {
            "type": "string",
            "description": "Machine-readable error code. Present on the test-key rejection (`TEST_MODE_UNAVAILABLE`), consent conflicts, and most collab-scene and upload-session validation errors. Never present on `401` authentication rejections, and absent on the inactive-partner `403` and on some older solo-account validation errors, which is why status is the primary signal.",
            "example": "missing_parameter"
          },
          "field": {
            "type": "string",
            "description": "The offending request field. Present on collab-scene and upload-session validation errors.",
            "example": "contentType"
          }
        },
        "required": [
          "error"
        ]
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "Too many requests. Please slow down."
          },
          "retryAfter": {
            "type": "integer",
            "description": "Seconds until the window frees up. Mirrors the `Retry-After` header.",
            "example": 14
          },
          "limit": {
            "type": "integer",
            "description": "Requests allowed per window.",
            "example": 60
          },
          "remaining": {
            "type": "integer",
            "example": 0
          },
          "reset": {
            "type": "integer",
            "description": "Epoch milliseconds at which the sliding window releases the oldest request.",
            "example": 1714000060000
          }
        },
        "required": [
          "error",
          "limit",
          "remaining",
          "reset"
        ]
      },
      "CreateSoloAccountBody": {
        "type": "object",
        "properties": {
          "externalUserId": {
            "type": "string",
            "description": "Your own user id for this creator. Half of the natural idempotency key: a repeat POST with the same `externalUserId` returns the existing account with `200` instead of `201`. Store the `accountId` you get back; every later call needs it.",
            "example": "user_12345"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "The creator's email address. The second idempotency scope: one account per (partner, exact address), so a creator's consent decision survives a change to your own user id for them. If the address already belongs to an Easy2257 user, Easy2257 emails that user for approval instead of linking them to you.",
            "example": "creator@example.com"
          },
          "callbackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Required. Stored on the account record for your own reference. It does NOT subscribe you to anything: webhook deliveries go only to endpoints you register in the partner portal at partner.easy2257.com/webhooks.",
            "example": "https://your-platform.com/webhooks/ez2257"
          },
          "firstName": {
            "type": "string",
            "description": "Optional. Used in the email Easy2257 sends the creator. Sanitized and truncated to 100 characters.",
            "example": "Jane"
          },
          "lastName": {
            "type": "string",
            "description": "Optional. Sanitized and truncated to 100 characters.",
            "example": "Doe"
          },
          "redirectUrl": {
            "type": "string",
            "format": "uri",
            "description": "Optional. Where Easy2257 sends the creator after they finish onboarding.",
            "example": "https://your-platform.com/dashboard"
          }
        },
        "required": [
          "externalUserId",
          "email",
          "callbackUrl"
        ]
      },
      "ProvisionSoloAccountResponse": {
        "type": "object",
        "properties": {
          "accountId": {
            "type": "string",
            "description": "Easy2257's id for this account. Opaque cuid. Store it: every later call is addressed by it.",
            "example": "cmd4k2x9p0001qz8h3v7ftg2a"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_id_verification",
              "subscribed_pending_id",
              "active",
              "suspended"
            ],
            "description": "The four values a partner can observe. `pending_id_verification`: not finished. This one value covers every pre-active state, including an emailed link request that has not been answered, so account existence and consent decisions are never observable through the API. `subscribed_pending_id`: paid, ID not yet verified. Still not uploadable. `active`: paid and ID verified. This is the only value that unlocks uploads. `suspended`: not in custody right now. Revoke upload access. `suspendReason` says why: the creator's own subscription lapsed, your platform fee lapsed, Easy2257 suspended them, or you deactivated them through `POST .../deactivate`."
          },
          "onboardingUrl": {
            "type": "string",
            "format": "uri",
            "description": "Present on every not-yet-active response, absent when status is `active`. Send the creator here in top-level navigation, not an iframe, because ID verification needs camera access. Tokens last 7 days and are refreshed automatically on later reads, so redirect to whatever the API just returned rather than storing the link.",
            "example": "https://easy2257.com/partner-onboard/9f2c..."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-04-22T12:00:00Z"
          }
        },
        "required": [
          "accountId",
          "status",
          "createdAt"
        ]
      },
      "SoloAccountResponse": {
        "type": "object",
        "properties": {
          "accountId": {
            "type": "string",
            "example": "cmd4k2x9p0001qz8h3v7ftg2a"
          },
          "externalUserId": {
            "type": "string",
            "description": "The id you supplied at provisioning. Returned on reads so you can reconcile without a local join.",
            "example": "user_12345"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_id_verification",
              "subscribed_pending_id",
              "active",
              "suspended"
            ],
            "description": "The four values a partner can observe. `pending_id_verification`: not finished. This one value covers every pre-active state, including an emailed link request that has not been answered, so account existence and consent decisions are never observable through the API. `subscribed_pending_id`: paid, ID not yet verified. Still not uploadable. `active`: paid and ID verified. This is the only value that unlocks uploads. `suspended`: not in custody right now. Revoke upload access. `suspendReason` says why: the creator's own subscription lapsed, your platform fee lapsed, Easy2257 suspended them, or you deactivated them through `POST .../deactivate`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-04-22T11:00:00Z"
          },
          "onboardingUrl": {
            "type": "string",
            "format": "uri",
            "description": "Present while the account is still pending, and always currently valid: an expired token is rotated on read.",
            "example": "https://easy2257.com/partner-onboard/9f2c..."
          },
          "verifiedAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-04-22T13:00:00Z"
          },
          "suspendedAt": {
            "type": "string",
            "format": "date-time"
          },
          "suspendReason": {
            "type": "string",
            "description": "Why the account is `suspended`, absent otherwise. `subscription_lapsed`: the creator's own subscription ended; it clears when they pay again. `platform_lapsed`: your platform fee lapsed; it clears when the fee is paid. `partner_deactivated`: you deactivated the creator through `POST .../deactivate` or the portal roster; only `POST .../reactivate` clears it. `platform_deactivated` and `manual_suspension`: Easy2257 suspended the account; contact Easy2257.",
            "example": "subscription_lapsed"
          },
          "complianceStatementUrl": {
            "type": "string",
            "format": "uri",
            "description": "The creator's public 2257 statement. Present once the account is `active` and the creator has published the statement, absent otherwise, so the value is always a live page rather than a link that 404s. This is the page the platforms guide asks you to link from every content page carrying their content, which is also the only point at which you should be hosting that content.",
            "example": "https://easy2257.com/producer/acme-media/2257-statement"
          }
        },
        "required": [
          "accountId",
          "externalUserId",
          "status",
          "createdAt"
        ]
      },
      "DeactivateSoloAccountResponse": {
        "type": "object",
        "properties": {
          "deactivated": {
            "type": "boolean",
            "description": "true if this call deactivated the creator. false if you had already deactivated them, which is a successful no-op, not an error.",
            "example": true
          },
          "accountId": {
            "type": "string",
            "example": "cmd4k2x9p0001qz8h3v7ftg2a"
          },
          "externalUserId": {
            "type": "string",
            "example": "user_12345"
          },
          "status": {
            "type": "string",
            "enum": [
              "suspended"
            ],
            "example": "suspended"
          },
          "suspendedAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-09-05T12:00:00Z"
          },
          "suspendReason": {
            "type": "string",
            "enum": [
              "partner_deactivated"
            ],
            "description": "Always `partner_deactivated` on this response. It is the only reason your platform writes, and the only one `POST .../reactivate` clears.",
            "example": "partner_deactivated"
          }
        },
        "required": [
          "deactivated",
          "accountId",
          "externalUserId",
          "status",
          "suspendReason"
        ]
      },
      "ReactivateSoloAccountResponse": {
        "type": "object",
        "properties": {
          "reactivated": {
            "type": "boolean",
            "description": "true if this call cleared your deactivation. false if the creator was already active, which is a successful no-op, not an error.",
            "example": true
          },
          "accountId": {
            "type": "string",
            "example": "cmd4k2x9p0001qz8h3v7ftg2a"
          },
          "externalUserId": {
            "type": "string",
            "example": "user_12345"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "subscribed_pending_id"
            ],
            "description": "`active`: back in custody, and `solo_account.verified` fires again. `subscribed_pending_id`: your deactivation is cleared but the creator never finished ID verification. `GET /api/v1/solo-accounts/{accountId}` returns their continue URL, and they become `active` when their verification is approved.",
            "example": "active"
          },
          "verifiedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Present when `status` is `active`. The original verification date: no new verification was run.",
            "example": "2026-04-22T13:00:00Z"
          }
        },
        "required": [
          "reactivated",
          "accountId",
          "externalUserId",
          "status"
        ]
      },
      "ComplianceCopyDeclaration": {
        "type": "object",
        "properties": {
          "mimeType": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp",
              "video/mp4",
              "video/webm",
              "video/quicktime"
            ],
            "description": "Type of the reduced rendition you will upload.",
            "example": "video/mp4"
          },
          "sizeBytes": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "description": "Exact byte length of that rendition. Images up to 5 MB, video up to 500 MB. The upload URL is bound to this length; a PUT of a different length is rejected by storage.",
            "example": 48213377
          }
        },
        "required": [
          "mimeType",
          "sizeBytes"
        ]
      },
      "ComplianceCopyUpload": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Presigned PUT URL, valid 15 minutes."
          },
          "method": {
            "type": "string",
            "enum": [
              "PUT"
            ]
          },
          "headers": {
            "type": "object",
            "properties": {
              "Content-Type": {
                "type": "string",
                "example": "video/mp4"
              }
            },
            "required": [
              "Content-Type"
            ],
            "description": "Send exactly these headers with the PUT."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-08-21T18:15:00Z"
          },
          "maxBytes": {
            "type": "integer",
            "description": "The declared length.",
            "example": 48213377
          }
        },
        "required": [
          "url",
          "method",
          "headers",
          "expiresAt",
          "maxBytes"
        ]
      },
      "ComplianceCopyState": {
        "type": "string",
        "enum": [
          "stored",
          "pending",
          "missing"
        ],
        "description": "`stored`: verified and hashed by Easy2257. `pending`: declared, upload not yet confirmed. `missing`: not declared (JuicyStarz pipeline only; platform-plan partners must declare)."
      },
      "ComplianceCopyConfirmResponse": {
        "type": "object",
        "properties": {
          "depictionId": {
            "type": "string",
            "example": "cmd4k7b2r0004qz8h0m2c8t1v"
          },
          "contentLogId": {
            "type": "string",
            "example": "cmd4k7b2r0003qz8hd1n5wq9e"
          },
          "complianceCopy": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "stored"
                ]
              },
              "sha256": {
                "type": "string",
                "description": "Computed by Easy2257 from the stored copy; not the `fileHash` you sent on the create call.",
                "example": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
              },
              "bytes": {
                "type": "integer",
                "example": 48213377
              },
              "mimeType": {
                "type": "string",
                "example": "video/mp4"
              },
              "storedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2026-08-21T18:04:11Z"
              }
            },
            "required": [
              "state",
              "sha256",
              "bytes",
              "mimeType",
              "storedAt"
            ]
          }
        },
        "required": [
          "depictionId",
          "contentLogId",
          "complianceCopy"
        ]
      },
      "ComplianceCopyConfirmError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "The compliance copy has not been uploaded yet. PUT the file to the upload URL, then confirm."
          },
          "code": {
            "type": "string",
            "description": "`NOT_DECLARED`, `NOT_UPLOADED`, `SIZE_MISMATCH`, `TYPE_MISMATCH`, or `CONTENT_REMOVED`.",
            "example": "NOT_UPLOADED"
          },
          "complianceCopyUpload": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ComplianceCopyUpload"
              },
              {
                "description": "Present on `NOT_UPLOADED`, and a fresh presigned PUT URL: the one from the create call lasts 15 minutes, and this is how you finish a record whose upload failed or expired. Retry the PUT against this URL, then call confirm again. Do NOT log the content a second time. `null` on every other code."
              }
            ]
          }
        },
        "required": [
          "error",
          "code",
          "complianceCopyUpload"
        ]
      },
      "LogContentBody": {
        "type": "object",
        "properties": {
          "contentUrl": {
            "type": "string",
            "format": "uri",
            "description": "Required. Canonical URL where the content is hosted. Stored as the per-depiction audit trail.",
            "example": "https://your-cdn.com/videos/abc123.mp4"
          },
          "title": {
            "type": "string",
            "description": "Required. Human-readable title. Appears in the Easy2257 records view and the compliance archive.",
            "example": "April 2026 Scene"
          },
          "contentType": {
            "type": "string",
            "default": "video",
            "description": "Media type of the depiction, stored as a label on the record. Defaults to `video`. Use `video`, `image`, `audio`, or `other`: those are the four values Easy2257's own views and reports expect. This field is not validated against that list today, so a value outside it is stored exactly as you sent it rather than rejected, and it will read as an unknown type wherever the record is displayed. Treat the four as a contract with your own future self.",
            "example": "video"
          },
          "externalContentId": {
            "type": "string",
            "description": "Your own content id, and the idempotency key: re-logging the same id returns the existing record. Always send it. It is the only handle you can later address for a takedown, and a depiction logged without one cannot be removed through the API.",
            "example": "video_abc123"
          },
          "platform": {
            "type": "string",
            "description": "Optional label. Defaults to your partner company name.",
            "example": "YourPlatform"
          },
          "productionDate": {
            "type": "string",
            "format": "date-time",
            "description": "Optional. Defaults to now. This date starts the 7-year retention clock for the record.",
            "example": "2026-04-22T00:00:00Z"
          },
          "fileHash": {
            "type": "string",
            "description": "Optional but recommended. SHA-256 hex digest of the file, which is what satisfies 28 CFR 75.2(f) record integrity.",
            "example": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
          },
          "complianceCopy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ComplianceCopyDeclaration"
              },
              {
                "description": "The reduced copy Easy2257 keeps as the copy of the depiction. Required for platform-plan partners (400 `COMPLIANCE_COPY_REQUIRED` without it). After the create call, PUT the file to `complianceCopyUpload.url` with the given `Content-Type`, then call the compliance-copy confirm endpoint."
              }
            ]
          }
        },
        "required": [
          "contentUrl",
          "title"
        ]
      },
      "LogContentResponse": {
        "type": "object",
        "properties": {
          "contentLogId": {
            "type": "string",
            "example": "cmd4k7b2r0003qz8hd1n5wq9e"
          },
          "depictionId": {
            "type": "string",
            "example": "cmd4k7b2r0004qz8h0m2c8t1v"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-04-22T12:00:00Z"
          },
          "complianceCopy": {
            "$ref": "#/components/schemas/ComplianceCopyState"
          },
          "complianceCopyUpload": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ComplianceCopyUpload"
              },
              {
                "description": "Where to PUT the compliance copy. `null` when no copy was declared, or when the existing record's copy is already stored."
              }
            ]
          }
        },
        "required": [
          "contentLogId",
          "depictionId",
          "createdAt",
          "complianceCopy",
          "complianceCopyUpload"
        ]
      },
      "LogContentDuplicateResponse": {
        "type": "object",
        "properties": {
          "contentLogId": {
            "type": "string",
            "example": "cmd4k7b2r0003qz8hd1n5wq9e"
          },
          "depictionId": {
            "type": "string",
            "example": "cmd4k7b2r0004qz8h0m2c8t1v"
          },
          "duplicate": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Always true on this response. The existing record was returned and nothing new was created."
          },
          "complianceCopy": {
            "$ref": "#/components/schemas/ComplianceCopyState"
          },
          "complianceCopyUpload": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ComplianceCopyUpload"
              },
              {
                "description": "A fresh upload URL while the existing record's copy is still `pending` (the first one lasts 15 minutes); `null` otherwise."
              }
            ]
          }
        },
        "required": [
          "contentLogId",
          "depictionId",
          "duplicate",
          "complianceCopy",
          "complianceCopyUpload"
        ]
      },
      "DeleteContentResponse": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean",
            "description": "true if a depiction was removed. false if nothing matched that `externalContentId`, which is a successful no-op, not an error.",
            "example": true
          },
          "depictionId": {
            "type": "string",
            "description": "Present only when `deleted` is true.",
            "example": "cmd4k7b2r0004qz8h0m2c8t1v"
          },
          "contentLogId": {
            "type": "string",
            "description": "Present only when `deleted` is true.",
            "example": "cmd4k7b2r0003qz8hd1n5wq9e"
          }
        },
        "required": [
          "deleted"
        ]
      },
      "TalentCheckBody": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Matched case-insensitively against the exact address you provisioned. Plus-tags and dots are not folded here.",
            "example": "creator@example.com"
          }
        },
        "required": [
          "email"
        ]
      },
      "TalentCheckResponse": {
        "type": "object",
        "properties": {
          "found": {
            "type": "boolean",
            "description": "Whether this address matches an account you provisioned. Always false for anyone you did not provision, including creators verified through another platform."
          },
          "verified": {
            "type": "boolean",
            "description": "True only when that account's status is `active`."
          },
          "lastVerifiedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Date of the most recent approved verification as `YYYY-MM-DD`, or null. Date only: no time, and no verification detail.",
            "example": "2026-04-01"
          }
        },
        "required": [
          "found",
          "verified",
          "lastVerifiedAt"
        ]
      },
      "CreateCollabSceneBody": {
        "type": "object",
        "properties": {
          "uploaderAccountId": {
            "type": "string",
            "description": "The `accountId` Easy2257 issued when you provisioned the uploader's solo account. Send exactly one of `uploaderAccountId` or `uploaderExternalUserId`. Either must resolve to an active solo account under your partner account, otherwise the call returns `400` with code `uploader_not_solo_active`.",
            "example": "cmd4k9f7t0002qz8hw5r1jd8n"
          },
          "uploaderExternalUserId": {
            "type": "string",
            "description": "Your own user id for the uploader, exactly as you supplied it as `externalUserId` at provisioning. Alternative to `uploaderAccountId`; send one or the other, never both.",
            "example": "user_12345"
          },
          "title": {
            "type": "string",
            "maxLength": 500,
            "example": "April 2026 Scene"
          },
          "shootDate": {
            "type": "string",
            "format": "date-time",
            "example": "2026-04-20T00:00:00Z"
          },
          "performers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email",
                  "example": "performer@example.com"
                }
              },
              "required": [
                "email"
              ]
            },
            "minItems": 1,
            "maxItems": 10,
            "description": "1 to 10 performers, addresses unique within the request. Easy2257 emails each one a magic link. A performer who already has an active Easy2257 solo account signs and is done at no extra charge; anyone else completes registration and ID verification inside the same link at 2500 cents. The response never says which path an individual performer is on."
          },
          "uploaderAttestation": {
            "type": "object",
            "properties": {
              "ip": {
                "type": "string",
                "description": "Uploader's IP at the moment they clicked.",
                "example": "203.0.113.7"
              },
              "ua": {
                "type": "string",
                "example": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."
              },
              "clickedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2026-04-24T18:00:00Z"
              },
              "attestationText": {
                "type": "string",
                "description": "The exact statement the uploader confirmed. Stored verbatim with IP and user agent as the 18 U.S.C. § 2257(b)(1) producer attestation.",
                "example": "I coordinated this scene, all performers were 18+ and consented on set"
              }
            },
            "required": [
              "ip",
              "ua",
              "clickedAt",
              "attestationText"
            ],
            "description": "All four fields required. Capture them from a real click event in your UI: this is the producer attestation, and a synthesized one is worth nothing in an inspection."
          },
          "callbackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Required, and stored for your reference only. Scene events go to the endpoints registered in the partner portal.",
            "example": "https://your-platform.com/webhooks/ez2257"
          }
        },
        "required": [
          "title",
          "performers",
          "uploaderAttestation",
          "callbackUrl"
        ],
        "description": "Identify the uploader with exactly one of `uploaderAccountId` or `uploaderExternalUserId`: zero is `400` / `missing_field`, more than one is `400` / `invalid_field`. Both are values you already hold; no endpoint ever returns an Easy2257 user id, so there is nothing else to send. (A legacy `uploaderUserId` field exists for Easy2257-internal callers only and is not usable by partners.)"
      },
      "CollabSceneResponse": {
        "type": "object",
        "properties": {
          "sceneId": {
            "type": "string",
            "example": "cmd5r9a4t0001qz8hb6k2m3xd"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_signatures",
              "greenlit",
              "blocked",
              "cancelled"
            ],
            "description": "`pending_signatures`: waiting on performers. `greenlit`: everyone signed and every new performer passed ID verification. This is the only status that permits publication. `blocked`: a performer refused, or the 14-day window lapsed. `cancelled`: cancelled through the API. No refund."
          },
          "performers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email",
                  "example": "performer@example.com"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending_consent",
                    "consented",
                    "refused"
                  ],
                  "description": "`pending_consent`: invited, has not yet signed. This one value covers every unsigned performer, whether or not the address already had an Easy2257 account: registration and ID verification, when needed, happen inside the magic-link flow and are never visible here, so performer status cannot be used to probe whether an email is already on Easy2257. `consented`: release signed and consent recorded. `refused`: performer declined, which blocks the scene. When the signing window lapses, the scene flips to `blocked` while unsigned performers stay `pending_consent`."
                }
              },
              "required": [
                "email",
                "status"
              ],
              "description": "Email and status, nothing else. There is deliberately no per-performer flag saying whether the address already had an Easy2257 account: that would be an account-existence oracle on arbitrary emails."
            }
          },
          "fees": {
            "type": "object",
            "properties": {
              "sceneFeeCents": {
                "type": "integer",
                "description": "Base scene record fee, 500 cents.",
                "example": 500
              },
              "onboardingFeeCents": {
                "type": "integer",
                "description": "2500 cents for each performer who did not already have an active Easy2257 solo account. Performers who already hold one add nothing. The split is an aggregate for the whole scene; it is not itemized per performer.",
                "example": 2500
              },
              "totalCents": {
                "type": "integer",
                "example": 3000
              },
              "charged": {
                "type": "boolean",
                "description": "true once a Stripe charge id is on the record. The scene is created either way: a billing failure does not block consent collection, so check this rather than assuming payment succeeded. On `billedTo: platform_invoice` it stays false: the fees are collected with your monthly platform invoice."
              },
              "stripeChargeId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Despite the field name this holds the Stripe PaymentIntent id, charged off-session against the uploader's saved payment method. null when `billedTo` is `platform_invoice`.",
                "example": "pi_3NabcDEfghIJklmn"
              },
              "billedTo": {
                "type": "string",
                "enum": [
                  "uploader_card",
                  "platform_invoice"
                ],
                "description": "Who pays the fees. `uploader_card`: charged off-session to the uploading creator's saved card. `platform_invoice`: a platform-funded account with collab scenes switched on; the fees are added to your platform's next monthly invoice, so `charged` stays false and `stripeChargeId` null.",
                "example": "uploader_card"
              },
              "chargeError": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "null when the immediate off-session charge succeeded or nothing was due. Otherwise a free-form failure code, not a fixed enum: for example `no_payment_method`, `card_declined`, or `payment_requires_action`. The scene is still created and consent collection proceeds; the fee is settled on the billing side. On `billedTo: platform_invoice` it reports a failure to add the fees to your invoice: the scene still stands and Easy2257 is alerted to add them by hand.",
                "example": null
              }
            },
            "required": [
              "sceneFeeCents",
              "onboardingFeeCents",
              "totalCents",
              "charged",
              "stripeChargeId",
              "billedTo",
              "chargeError"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "14 days from creation. Unsigned at that point and the scene is blocked.",
            "example": "2026-05-08T18:00:00Z"
          }
        },
        "required": [
          "sceneId",
          "status",
          "performers",
          "fees",
          "expiresAt"
        ]
      },
      "CollabSceneDetailResponse": {
        "type": "object",
        "properties": {
          "sceneId": {
            "type": "string",
            "example": "cmd5r9a4t0001qz8hb6k2m3xd"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_signatures",
              "greenlit",
              "blocked",
              "cancelled"
            ],
            "description": "`pending_signatures`: waiting on performers. `greenlit`: everyone signed and every new performer passed ID verification. This is the only status that permits publication. `blocked`: a performer refused, or the 14-day window lapsed. `cancelled`: cancelled through the API. No refund."
          },
          "performerCount": {
            "type": "integer",
            "example": 3
          },
          "consentedCount": {
            "type": "integer",
            "example": 2
          },
          "releaseSignedCount": {
            "type": "integer",
            "description": "Same field POST /extend returns, so release progress reads the same way on both endpoints.",
            "example": 1
          },
          "refusedCount": {
            "type": "integer",
            "example": 0
          },
          "performers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "format": "email",
                  "example": "performer@example.com"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending_consent",
                    "consented",
                    "refused"
                  ],
                  "description": "`pending_consent`: invited, has not yet signed. This one value covers every unsigned performer, whether or not the address already had an Easy2257 account: registration and ID verification, when needed, happen inside the magic-link flow and are never visible here, so performer status cannot be used to probe whether an email is already on Easy2257. `consented`: release signed and consent recorded. `refused`: performer declined, which blocks the scene. When the signing window lapses, the scene flips to `blocked` while unsigned performers stay `pending_consent`."
                }
              },
              "required": [
                "email",
                "status"
              ],
              "description": "Email and status, nothing else. There is deliberately no per-performer flag saying whether the address already had an Easy2257 account: that would be an account-existence oracle on arbitrary emails."
            },
            "description": "Present only for the partner that CREATED the scene, which is the partner that supplied these addresses. A partner holding only a publication on somebody else's scene receives the counts above and no roster."
          },
          "fees": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "sceneFeeCents": {
                "type": "integer",
                "description": "Base scene record fee, 500 cents.",
                "example": 500
              },
              "onboardingFeeCents": {
                "type": "integer",
                "description": "2500 cents for each performer who did not already have an active Easy2257 solo account. Performers who already hold one add nothing. The split is an aggregate for the whole scene; it is not itemized per performer.",
                "example": 2500
              },
              "totalCents": {
                "type": "integer",
                "example": 3000
              },
              "charged": {
                "type": "boolean",
                "description": "true once a Stripe charge id is on the record. The scene is created either way: a billing failure does not block consent collection, so check this rather than assuming payment succeeded. On `billedTo: platform_invoice` it stays false: the fees are collected with your monthly platform invoice."
              },
              "stripeChargeId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Despite the field name this holds the Stripe PaymentIntent id, charged off-session against the uploader's saved payment method. null when `billedTo` is `platform_invoice`.",
                "example": "pi_3NabcDEfghIJklmn"
              },
              "billedTo": {
                "type": "string",
                "enum": [
                  "uploader_card",
                  "platform_invoice"
                ],
                "description": "Who pays the fees. `uploader_card`: charged off-session to the uploading creator's saved card. `platform_invoice`: a platform-funded account with collab scenes switched on; the fees are added to your platform's next monthly invoice, so `charged` stays false and `stripeChargeId` null.",
                "example": "uploader_card"
              }
            },
            "required": [
              "sceneFeeCents",
              "onboardingFeeCents",
              "totalCents",
              "charged",
              "stripeChargeId",
              "billedTo"
            ],
            "description": "The creating partner's charge for the scene; null when no fee row exists. Absent entirely for a partner holding only a publication, since it is another partner's billing record."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "publications": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "example": "cmd5rc1v90007qz8h4p8w2n6y"
                },
                "contentUrl": {
                  "type": "string",
                  "format": "uri",
                  "example": "https://your-platform.com/post/12345"
                },
                "publishedAt": {
                  "type": "string",
                  "format": "date-time"
                },
                "removedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              },
              "required": [
                "id",
                "contentUrl",
                "publishedAt",
                "removedAt"
              ]
            },
            "description": "Only the publications you registered. Other platforms' URLs for the same scene are never visible to you, and yours are never visible to them."
          }
        },
        "required": [
          "sceneId",
          "status",
          "performerCount",
          "consentedCount",
          "releaseSignedCount",
          "refusedCount",
          "expiresAt",
          "publications"
        ]
      },
      "RegisterPublicationBody": {
        "type": "object",
        "properties": {
          "contentUrl": {
            "type": "string",
            "format": "uri",
            "description": "Canonical URL where you published the content. Unique per partner account: re-registering a URL you already used returns `409`.",
            "example": "https://your-platform.com/post/12345"
          },
          "depictionSha256": {
            "type": "string",
            "minLength": 64,
            "maxLength": 64,
            "description": "Required. SHA-256 hex digest of the content file, 64 hex characters, for 28 CFR 75.2(f) integrity verification.",
            "example": "a3f5b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1"
          }
        },
        "required": [
          "contentUrl",
          "depictionSha256"
        ]
      },
      "PublicationResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "cmd5rc1v90007qz8h4p8w2n6y"
          },
          "collabSceneId": {
            "type": "string",
            "example": "cmd5r9a4t0001qz8hb6k2m3xd"
          },
          "contentUrl": {
            "type": "string",
            "format": "uri"
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "collabSceneId",
          "contentUrl",
          "publishedAt"
        ]
      },
      "RemovePublicationBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "description": "Optional, truncated to 500 characters. Defaults to `Partner removal`. Recorded on the publication and forwarded to the scene owner.",
            "example": "Removal request #1234"
          }
        }
      },
      "RemovePublicationResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "cmd5rc1v90007qz8h4p8w2n6y"
          },
          "removedAt": {
            "type": "string",
            "format": "date-time"
          },
          "message": {
            "type": "string",
            "description": "Present only on a repeat call, where the original `removedAt` is returned unchanged.",
            "example": "Already removed"
          }
        },
        "required": [
          "id",
          "removedAt"
        ]
      },
      "ExtendSceneResponse": {
        "type": "object",
        "properties": {
          "sceneId": {
            "type": "string",
            "example": "cmd5r9a4t0001qz8hb6k2m3xd"
          },
          "status": {
            "type": "string",
            "enum": [
              "greenlit"
            ]
          },
          "extendedTo": {
            "type": "string",
            "description": "Your partner id, confirming the extension was recorded against your account.",
            "example": "cmd2n6t0h0001qz8h1c9x4b7m"
          },
          "performerCount": {
            "type": "integer",
            "description": "Number of performers on the scene.",
            "example": 3
          },
          "consentedCount": {
            "type": "integer",
            "description": "How many of those performers have signed consent.",
            "example": 3
          },
          "releaseSignedCount": {
            "type": "integer",
            "description": "How many of those performers have a signed release on file.",
            "example": 3
          },
          "fee": {
            "type": "object",
            "properties": {
              "extensionFeeCents": {
                "type": "integer",
                "example": 200
              },
              "charged": {
                "type": "boolean",
                "description": "true once the extension fee has a Stripe charge on record. The extension itself is recorded either way."
              },
              "stripeChargeId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Stripe PaymentIntent id of the extension charge, or null while the fee is unpaid.",
                "example": "pi_3NabcDEfghIJklmn"
              },
              "chargeError": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "null when the charge succeeded. Otherwise a free-form failure code, not a fixed enum: for example `no_payment_method` or `card_declined`. The fee stays unpaid and visible, and a re-POST of this endpoint retries it without stacking a second fee.",
                "example": null
              }
            },
            "required": [
              "extensionFeeCents",
              "charged",
              "stripeChargeId",
              "chargeError"
            ],
            "description": "Outcome of the $2 extension charge against your partner owner user's saved payment method."
          },
          "nextStep": {
            "type": "string",
            "example": "Call POST /api/v1/collab-scenes/{sceneId}/publications with your content URL and depiction SHA-256."
          }
        },
        "required": [
          "sceneId",
          "status",
          "extendedTo",
          "performerCount",
          "consentedCount",
          "releaseSignedCount",
          "fee",
          "nextStep"
        ]
      },
      "CreateUploadSessionBody": {
        "type": "object",
        "properties": {
          "contentId": {
            "type": "string",
            "maxLength": 255,
            "description": "Your content id. Echoed back to you on the completion webhook so you can find the row again.",
            "example": "video_abc123"
          },
          "contentType": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "gallery"
            ]
          },
          "contentTitle": {
            "type": "string",
            "maxLength": 500,
            "example": "April 2026 Scene"
          },
          "detectedFaceCount": {
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "description": "How many people your pipeline detected in the upload. This alone chooses the path: 1 collects a solo attestation, 2 or more opens a collab scene and emails every collaborator the uploader names.",
            "example": 2
          },
          "uploaderExternalUserId": {
            "type": "string",
            "maxLength": 450,
            "description": "Your user id for the uploader. Must already map to an active Easy2257 solo account under your partner account.",
            "example": "user_12345"
          },
          "uploaderEmail": {
            "type": "string",
            "format": "email",
            "example": "creator@example.com"
          },
          "uploaderFirstName": {
            "type": "string"
          },
          "uploaderLastName": {
            "type": "string"
          },
          "callbackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Required, stored for your reference. Events go to your portal-registered endpoints.",
            "example": "https://your-platform.com/webhooks/ez2257"
          },
          "redirectUrl": {
            "type": "string",
            "format": "uri",
            "description": "Required. Where the hosted form returns the uploader when they are done.",
            "example": "https://your-platform.com/uploads/video_abc123"
          }
        },
        "required": [
          "contentId",
          "contentType",
          "contentTitle",
          "detectedFaceCount",
          "uploaderExternalUserId",
          "uploaderEmail",
          "callbackUrl",
          "redirectUrl"
        ]
      },
      "UploadSessionResponse": {
        "type": "object",
        "properties": {
          "sessionId": {
            "type": "string",
            "example": "cmd6t2p5x0001qz8h7f3d9k4w"
          },
          "token": {
            "type": "string",
            "description": "64-character session token. Treat it as a secret: anyone holding it can complete the attestation.",
            "example": "3f9c1d..."
          },
          "hostedUrl": {
            "type": "string",
            "format": "uri",
            "description": "Send the uploader here in top-level navigation. Easy2257 hosts the attestation form, so you build no compliance UI at all.",
            "example": "https://easy2257.com/upload-attestation/3f9c1d..."
          },
          "status": {
            "type": "string",
            "enum": [
              "created"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "24 hours from creation. After that the uploader needs a new session.",
            "example": "2026-04-25T18:00:00Z"
          }
        },
        "required": [
          "sessionId",
          "token",
          "hostedUrl",
          "status",
          "expiresAt"
        ]
      },
      "ProductionResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "cmd3p8w1k0002qz8h9j4r7v5s"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_documentation",
              "documented",
              "revoked",
              "takedown_pending",
              "takedown_complete"
            ],
            "description": "`pending_documentation`: records not yet complete. `documented`: records on file and a certificate issued. `revoked`: certificate revoked by an actioned takedown. `takedown_pending`: removal request received, under review. `takedown_complete`: content removed, records sealed."
          },
          "performerCount": {
            "type": "integer",
            "description": "Number of talent participants across the production's scenes.",
            "example": 3
          },
          "documentedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-04-24T18:00:00Z"
          },
          "custodianName": {
            "type": "string",
            "description": "Custodian of Records legal entity, exactly as published on the Easy2257 records statement. Render it, do not parse it."
          },
          "custodianAddress": {
            "type": "string",
            "description": "Custodian mailing address on one line. Empty string when no address is on file."
          },
          "takedownContact": {
            "type": "string",
            "format": "email",
            "example": "takedown@easy2257.com"
          },
          "lastVerifiedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of this lookup, not of a stored verification event. Useful as an audit breadcrumb for when you last checked."
          }
        },
        "required": [
          "id",
          "status",
          "performerCount",
          "documentedAt",
          "custodianName",
          "custodianAddress",
          "takedownContact",
          "lastVerifiedAt"
        ]
      },
      "ProductionListResponse": {
        "type": "object",
        "properties": {
          "productions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "example": "cmd3p8w1k0002qz8h9j4r7v5s"
                },
                "sceneTitle": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The production title.",
                  "example": "April 2026 Scene"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "pending_documentation",
                    "documented",
                    "revoked",
                    "takedown_pending",
                    "takedown_complete"
                  ],
                  "description": "`pending_documentation`: records not yet complete. `documented`: records on file and a certificate issued. `revoked`: certificate revoked by an actioned takedown. `takedown_pending`: removal request received, under review. `takedown_complete`: content removed, records sealed."
                },
                "documentedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              },
              "required": [
                "id",
                "sceneTitle",
                "status",
                "documentedAt"
              ]
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass back verbatim as `cursor`. null on the last page.",
            "example": "cmd3p8w1k0002qz8h9j4r7v5s"
          }
        },
        "required": [
          "productions",
          "nextCursor"
        ]
      },
      "VerifyCertificateBody": {
        "type": "object",
        "properties": {
          "certificate": {
            "type": "string",
            "description": "The RS256 JWT compliance certificate Easy2257 issued, as delivered in the `production.documented` webhook.",
            "example": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImV6MjI1Ny1hZDg4ODUyYyJ9..."
          }
        },
        "required": [
          "certificate"
        ]
      },
      "VerifyCertificateResponse": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "The only field you should branch on. A 200 response says the check ran, not that the certificate is good."
          },
          "productionId": {
            "type": "string",
            "example": "cmd3p8w1k0002qz8h9j4r7v5s"
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Present when valid is true."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Present when valid is true."
          },
          "currentStatus": {
            "type": "string",
            "description": "Current production documentation status. Present when valid is true. `unknown` if the production row has since been removed.",
            "example": "documented"
          },
          "reason": {
            "type": "string",
            "enum": [
              "certificate_revoked",
              "certificate_superseded",
              "certificate_expired",
              "certificate_not_found",
              "signature_mismatch",
              "invalid_certificate",
              "no_public_key",
              "public_key_not_configured",
              "production_id_mismatch"
            ],
            "description": "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."
          }
        },
        "required": [
          "valid"
        ]
      },
      "CrlResponse": {
        "type": "object",
        "properties": {
          "retrievedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Server time at which this response was built. This is the watermark: pass it as `since` on your next sync. There is no top-level `revokedAt`, and the per-item `revokedAt` values belong to individual certificates, so advancing a watermark off one of those will make your sync stall or skip.",
            "example": "2026-04-24T18:00:00Z"
          },
          "revocations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "productionId": {
                  "type": "string",
                  "example": "cmd3p8w1k0002qz8h9j4r7v5s"
                },
                "revokedAt": {
                  "type": "string",
                  "format": "date-time",
                  "description": "When this certificate was revoked.",
                  "example": "2026-04-24T18:00:00Z"
                },
                "reason": {
                  "type": "string",
                  "description": "Free-form string, not a fixed enum. `removal_request:<id>` means an admin actioned a removal request, so pull the content. Match that one on the `removal_request:` prefix, since the id varies. `unspecified` means no reason was recorded. `annual_renewal` appears only on entries recorded before renewals were split out of this feed; the content stays up for those.",
                  "example": "removal_request:cmremreq0000000000000001"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "revocation",
                    "supersession"
                  ],
                  "description": "What the entry is. `revocation` means the certified record was pulled: take the content down. `supersession` means an annual renewal replaced the certificate and the content stays up; it appears only on entries recorded before renewals stopped being written to this feed. New renewals never enter the list — they arrive as `production.certificate_renewed` webhooks and in `GET /api/v1/productions/{productionId}/certificates`.",
                  "example": "revocation"
                }
              },
              "required": [
                "productionId",
                "revokedAt",
                "reason",
                "type"
              ]
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque page cursor derived from the certificate record's `jwtId`. Pass it back verbatim as `cursor`. It is not a `productionId`, and ordering is stable on (revokedAt, jwtId) so pages never skip or repeat. null on the last page.",
            "example": "0b4b8f4e-6a7c-4a1e-9f0e-2f1d7c3a5b9e"
          }
        },
        "required": [
          "retrievedAt",
          "revocations",
          "nextCursor"
        ]
      },
      "CertificatesResponse": {
        "type": "object",
        "properties": {
          "productionId": {
            "type": "string",
            "example": "cmd3p8w1k0002qz8h9j4r7v5s"
          },
          "retrievedAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-08-15T18:00:00Z"
          },
          "certificates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "jwtId": {
                  "type": "string",
                  "description": "The certificate's `jti` claim — its stable identifier across this API.",
                  "example": "0b4b8f4e-6a7c-4a1e-9f0e-2f1d7c3a5b9e"
                },
                "kid": {
                  "type": "string",
                  "description": "Signing key id, resolvable against the public JWKS.",
                  "example": "ez2257-ad88852c"
                },
                "issuedAt": {
                  "type": "string",
                  "format": "date-time",
                  "example": "2026-08-15T18:00:00Z"
                },
                "expiresAt": {
                  "type": "string",
                  "format": "date-time",
                  "example": "2027-08-15T18:00:00Z"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "CURRENT",
                    "SUPERSEDED",
                    "REVOKED"
                  ],
                  "description": "Lifecycle state. `CURRENT` is the one live certificate. `SUPERSEDED` means an annual renewal replaced it; the content stays up. `REVOKED` means the certified record was pulled; take the content down."
                },
                "revokedAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "description": "Set on revoked certificates. Also present on certificates recorded as superseded before supersession was split out of revocation; `status` is the field to branch on."
                },
                "supersededAt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time",
                  "description": "Set when an annual renewal replaced this certificate. Null on records that predate the supersession split; `status` is the field to branch on."
                },
                "supersededByJwtId": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "`jwtId` of the replacing certificate, when superseded."
                },
                "jwt": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The signed certificate itself. `null` on certificates issued before certificates were stored; those were delivered once in their webhook and arrive again with the next annual renewal."
                }
              },
              "required": [
                "jwtId",
                "kid",
                "issuedAt",
                "expiresAt",
                "status",
                "revokedAt",
                "supersededAt",
                "supersededByJwtId",
                "jwt"
              ]
            },
            "description": "Every certificate minted for the production, newest first."
          }
        },
        "required": [
          "productionId",
          "retrievedAt",
          "certificates"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/api/v1/solo-accounts": {
      "post": {
        "summary": "Provision a solo creator account",
        "description": "Creates a solo creator account for one of your users, and returns either `active` (grant uploads now) or the pending shape with an `onboardingUrl` to redirect them to. Idempotent on two natural keys, `(partner, externalUserId)` and `(partner, email)`, so a retry after a timeout cannot produce a second account, a second charge, or a second email. There is no idempotency header to send. If the address already belongs to an Easy2257 user, Easy2257 emails that person for approval instead of handing their identity records to your platform. The response is deliberately identical either way, so your integration cannot be used to probe whether one of your users already has an Easy2257 account.",
        "tags": [
          "Solo Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSoloAccountBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing account returned (replay, or an account already provisioned for this address).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ProvisionSoloAccountResponse"
                    },
                    {
                      "description": "Provisioning returns exactly two shapes: `{ accountId, status: \"active\", createdAt }`, or the pending shape with `onboardingUrl`. Every not-yet-active outcome returns the identical shape and the identical status string by design, whatever happened internally."
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Account created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ProvisionSoloAccountResponse"
                    },
                    {
                      "description": "Provisioning returns exactly two shapes: `{ accountId, status: \"active\", createdAt }`, or the pending shape with `onboardingUrl`. Every not-yet-active outcome returns the identical shape and the identical status string by design, whatever happened internally."
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`email`, `externalUserId`, or `callbackUrl` missing, or email longer than 255 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/solo-accounts/{accountId}": {
      "get": {
        "summary": "Get a solo creator account",
        "description": "The source of truth for account state. Webhooks are the fast path; this is what you use to render a \"finish setting up\" prompt or to reconcile after an outage. While the account is pending it returns a currently valid `onboardingUrl`, rotated automatically if the old link expired.",
        "tags": [
          "Solo Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd4k2x9p0001qz8h3v7ftg2a"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Account found.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SoloAccountResponse"
                    },
                    {
                      "description": "The only creator-identifying value here is the public statement URL, which the creator published themselves. No legal name, no date of birth, no ID image, no Easy2257 user id, not even the email you provisioned with."
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account, or it belongs to another partner. The two cases are indistinguishable on purpose, so the API cannot be used to enumerate accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/solo-accounts/{accountId}/deactivate": {
      "post": {
        "summary": "Deactivate a creator your platform funds",
        "description": "Takes a creator your platform pays for off your roster. They come off your next invoice (a platform is invoiced for the creators in custody at the moment its billing period closes), their records stay in custody for the seven-year retention window, and `POST .../reactivate` brings them back with no new verification. Fires `solo_account.suspended` with `reason: partner_deactivated`; revoke upload access on it as you would for any suspension. Idempotent: deactivating a creator you already deactivated returns `200` with `{ \"deactivated\": false }`. Only for creators your platform funds. A creator who pays Easy2257 directly is not on your invoice and returns `409 CREATOR_PAYS_OWN_SUBSCRIPTION`. Never relabels a suspension you did not make: a lapse clears when the fee is paid and an Easy2257 suspension clears when Easy2257 lifts it (`409 SUSPENDED_FOR_OTHER_REASON`).",
        "tags": [
          "Solo Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd4k2x9p0001qz8h3v7ftg2a"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Deactivated, or already deactivated by you.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeactivateSoloAccountResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account, or it belongs to another partner. The two cases are indistinguishable on purpose, so the API cannot be used to enumerate accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Nothing changed. Three codes: `NOT_IN_CUSTODY`: the creator has not finished onboarding, so there is no coverage to end and no invoice line to stop (`status` carries their current value). `CREATOR_PAYS_OWN_SUBSCRIPTION`: the creator holds their own Easy2257 subscription; your platform is not paying for them. `SUSPENDED_FOR_OTHER_REASON`: already suspended for a reason your platform does not control (`suspendReason` says which); deactivate them once they are back.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/solo-accounts/{accountId}/reactivate": {
      "post": {
        "summary": "Reactivate a creator you deactivated",
        "description": "Brings back a creator you deactivated. No new verification and no second onboarding trip: they return on the record Easy2257 already holds, and they are on your next invoice again from this moment. If their ID verification is on file the account is `active` at once and `solo_account.verified` fires again, exactly as after a lapse. If they never finished verification the account returns at `subscribed_pending_id`, and `GET /api/v1/solo-accounts/{accountId}` has their continue URL. Idempotent: reactivating an active creator returns `200` with `{ \"reactivated\": false }`. Clears only a `partner_deactivated` suspension. A lapse clears when the fee is paid; an Easy2257 suspension clears when Easy2257 lifts it.",
        "tags": [
          "Solo Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd4k2x9p0001qz8h3v7ftg2a"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Reactivated, or already active.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReactivateSoloAccountResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account, or it belongs to another partner. The two cases are indistinguishable on purpose, so the API cannot be used to enumerate accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Nothing changed. Four codes: `NOT_DEACTIVATED`: the creator is not suspended (`status` carries their current value). `SUSPENDED_FOR_OTHER_REASON`: suspended for a reason your platform does not control (`suspendReason` says which). `PLATFORM_NOT_FUNDING`: your platform is no longer on an active platform-funded agreement, so nobody would be paying for the restored creator; contact Easy2257. `CREATOR_PAYS_OWN_SUBSCRIPTION`: the creator has since taken over payment themselves; contact Easy2257.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/solo-accounts/{accountId}/content": {
      "post": {
        "summary": "Log a content depiction",
        "description": "Registers a piece of content against a verified performer's Easy2257 identity record, creating the cross-reference index required by 28 CFR 75.2 and the per-depiction trail behind 18 U.S.C. § 2257(b)(1). Instrument this call and alert on failure: a silent non-2xx means a published depiction has no record of custody. Idempotent on `externalContentId`, falling back to `contentUrl` when you do not send one. Declare the compliance copy (`complianceCopy`) on this call: Easy2257 keeps a reduced copy of every depiction it records (28 CFR 75.2(a)(1)(iii)). PUT the file to `complianceCopyUpload.url`, then confirm with `POST .../content/{externalContentId}/compliance-copy`.",
        "tags": [
          "Solo Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd4k2x9p0001qz8h3v7ftg2a"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LogContentBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already logged. Existing record returned, nothing created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/LogContentDuplicateResponse"
                    },
                    {
                      "description": "Replay of an already-logged depiction. Note there is no `createdAt` on this shape."
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Depiction logged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LogContentResponse"
                }
              }
            }
          },
          "400": {
            "description": "`contentUrl` or `title` missing, `contentUrl` not a valid URL, or `complianceCopy` missing for a platform-plan partner or malformed (`code` `COMPLIANCE_COPY_REQUIRED`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The copy would take the creator's account past its depiction-copy storage allowance (`code` `STORAGE_ALLOWANCE_EXCEEDED`). Send a smaller rendition.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account, or it belongs to another partner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Three codes, and they do not share a remedy, so switch on `code` rather than on the status. `CONSENT_PENDING` (the creator has been asked to approve the link to your platform and has not answered) and `CONSENT_DECLINED` (they refused): treat both as \"not publishable yet\" and hold the content. `uploader_not_solo_active`: the creator's own Easy2257 account is no longer active, so there is no verified identity record to attach the depiction to. Retrying will not clear this one. Send the creator back through onboarding, then log the content.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The account exists but has no linked Easy2257 user yet, so there is no identity record to attach the depiction to.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/solo-accounts/{accountId}/content/{externalContentId}": {
      "delete": {
        "summary": "Remove a content depiction (takedown)",
        "description": "Records that a depiction came down on your platform, whether through DMCA, a performer request, or moderation. Idempotent, and it never 404s on the content id: an unknown `externalContentId` returns `200` with `{ \"deleted\": false }`. A `404` means the `accountId` is not one of yours. Inside the 7-year retention window the depiction is marked removed with who removed it and why, rather than erased, which is exactly what an inspector needs to see. Outside the window it is deleted outright. Fires `content_log.deleted` on an actual removal. Under the TAKE IT DOWN Act, pull the file from your CDN within 48 hours of that event even when Easy2257 initiated it.",
        "tags": [
          "Solo Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd4k2x9p0001qz8h3v7ftg2a"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "video_abc123"
            },
            "required": true,
            "name": "externalContentId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed, or a no-op if nothing matched.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteContentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account, or it belongs to another partner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`CONSENT_PENDING` or `CONSENT_DECLINED`: the creator has not approved the link to your platform, so nothing was ever loggable against the account and there is nothing to remove. These two only. The POST route has a third `409` code for an inactive creator account, and this route deliberately does not: a takedown must keep working on a lapsed account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The account has no linked Easy2257 user, so nothing was ever loggable against it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/solo-accounts/{accountId}/content/{externalContentId}/compliance-copy": {
      "post": {
        "summary": "Confirm the compliance copy",
        "description": "After you PUT the rendition to `complianceCopyUpload.url`, call this. Easy2257 verifies the object against the declared size and type, hashes it and records it, then fires `content_log.copy_stored`. Idempotent: confirming a stored copy returns the recorded facts. The path segment is your `externalContentId`; the `depictionId` from the create response is accepted in its place. No bytes travel through this endpoint and the Partner API never returns a copy: you hold your own original.",
        "tags": [
          "Solo Accounts"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd4k2x9p0001qz8h3v7ftg2a"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "video_abc123"
            },
            "required": true,
            "name": "externalContentId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Copy stored, or already stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComplianceCopyConfirmResponse"
                }
              }
            }
          },
          "400": {
            "description": "No copy was declared for this depiction (`code` `NOT_DECLARED`). Declare it on the create call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComplianceCopyConfirmError"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such account for this partner, or no content under that key for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Three different situations, and only one of them wants a new create call. `NOT_UPLOADED`: the PUT has not landed, or its URL expired. The response carries a fresh `complianceCopyUpload`. Retry the PUT against that URL and call this endpoint again. Do NOT log the content a second time: the depiction already exists and re-logging it puts a duplicate in the custody record. `SIZE_MISMATCH` or `TYPE_MISMATCH`: the object did not match what you declared and was discarded. These two are the ones that need a new create call, declaring the real size and type, followed by a fresh upload. `CONTENT_REMOVED`: the depiction was taken down, and its copy can no longer be confirmed. Nothing to retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComplianceCopyConfirmError"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/talent/check": {
      "post": {
        "summary": "Check performer verification status",
        "description": "Answers one question about one of your own creators: are they verified right now. Scoped to accounts you provisioned, so it returns `found: false` for every address you did not, including creators verified through another platform. It cannot be used to discover whether an arbitrary email has an Easy2257 account, and it never returns a name, a date of birth, or an ID.",
        "tags": [
          "Talent"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TalentCheckBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lookup result. A miss is a 200 with `found: false`, not a 404.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TalentCheckResponse"
                }
              }
            }
          },
          "400": {
            "description": "`email` missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/collab-scenes": {
      "post": {
        "summary": "Create a collab scene",
        "description": "Opens a multi-performer scene and emails every performer a magic link. Identify the uploader with exactly one of `uploaderAccountId` or `uploaderExternalUserId`; either must resolve to an active solo account under your partner account. The scene record costs $5 (500 cents), plus $25 (2500 cents) for each performer who does not already have an active Easy2257 solo account; performers who do sign at no extra charge. The response reports that split only as a scene-level aggregate, and performer entries never say which path an individual is on. Not idempotent. Every call creates a new scene, sends fresh invitation emails, and charges fees, so guard it behind your own dedupe. Content stays on your platform until the `collab_scene.greenlit` webhook fires. The uploader's attestation is the producer attestation of record, so capture it from a real click event.",
        "tags": [
          "Collab Scenes"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCollabSceneBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Scene created and invitation emails sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CollabSceneResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failure. `code` is one of `missing_field` (no uploader identifier, or another required field missing), `invalid_field` (more than one uploader identifier, or a non-string one), `invalid_email`, `duplicate_emails`, `too_many_performers` (more than 10), or `uploader_not_solo_active` (the identifier does not resolve to an active solo account under your partner account).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/collab-scenes/{sceneId}": {
      "get": {
        "summary": "Get a collab scene",
        "description": "Current scene status and consent progress. Prefer webhooks for real-time signals and use this for dashboards and reconciliation. Visible to the partner that created the scene and to any partner that has registered a publication on it. Publications in the response are scoped to you. The body depends on which of those you are: the creating partner gets the performer roster and the fee block as well as the counts, because it supplied those addresses and paid those fees; a partner holding only a publication gets the counts, matching what `POST /extend` returns.",
        "tags": [
          "Collab Scenes"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd5r9a4t0001qz8hb6k2m3xd"
            },
            "required": true,
            "name": "sceneId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Scene found.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/CollabSceneDetailResponse"
                    },
                    {
                      "description": "Two shapes behind one endpoint, decided by whether you created the scene. Both carry the counts; only the creating partner also gets the performer roster and the fee block."
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such scene, or one you neither created nor published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/collab-scenes/{sceneId}/cancel": {
      "post": {
        "summary": "Cancel a collab scene",
        "description": "Cancels a scene that is still in `pending_signatures`, and notifies you with `collab_scene.cancelled`. No refund is issued: the fees paid for record creation and for verification sessions that were already started. Only the scene's creating partner can cancel it.",
        "tags": [
          "Collab Scenes"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd5r9a4t0001qz8hb6k2m3xd"
            },
            "required": true,
            "name": "sceneId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sceneId": {
                      "type": "string",
                      "example": "cmd5r9a4t0001qz8hb6k2m3xd"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "cancelled"
                      ]
                    },
                    "refunded": {
                      "type": "boolean",
                      "enum": [
                        false
                      ]
                    }
                  },
                  "required": [
                    "sceneId",
                    "status",
                    "refunded"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such scene, or it belongs to another partner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The scene is not in `pending_signatures`. `code` is `invalid_status`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/collab-scenes/{sceneId}/extend": {
      "post": {
        "summary": "Extend a greenlit scene to your platform",
        "description": "Sign once, publish everywhere. Once a scene is greenlit on one platform, the uploader can syndicate it to any other Easy2257 partner platform without putting the performers through a second signing round, because the model releases already cover multi-platform distribution. There is no request body. Authorization is possession of the `sceneId` of a greenlit scene: an unguessable id that only the scene's creating partner holds, handed to you when the uploader arranges the syndication. The scene owner is notified of every publication you register. Charges your partner account a $2 extension fee (200 cents, USD), off-session against the saved payment method of your partner owner user, at most once per partner per scene: a re-POST retries an unpaid fee instead of charging again. The extension is recorded even if the charge fails; read the `fee` object in the response rather than assuming payment succeeded. Then call `POST /publications` with your URL and hash. Each platform's publications stay separate for acquirer reports and for the removal portal.",
        "tags": [
          "Collab Scenes"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd5r9a4t0001qz8hb6k2m3xd"
            },
            "required": true,
            "name": "sceneId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Extension recorded, with the fee outcome in `fee`. Proceed to POST /publications.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtendSceneResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such scene.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The scene is not greenlit (`not_greenlit`), or it is already yours and needs no extension (`already_owner`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/collab-scenes/{sceneId}/publications": {
      "post": {
        "summary": "Register a content publication",
        "description": "Records where you published the content and the SHA-256 of the file you published. Requires the scene to be `greenlit`. The scene's creating partner can call this straight after greenlight; any other partner must call `/extend` first. Publications are isolated per partner: you never see another platform's URLs and they never see yours.",
        "tags": [
          "Collab Scenes"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd5r9a4t0001qz8hb6k2m3xd"
            },
            "required": true,
            "name": "sceneId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterPublicationBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Publication registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicationResponse"
                }
              }
            }
          },
          "400": {
            "description": "`contentUrl` is not a valid URL (`invalid_url`), or `depictionSha256` is not 64 hex characters (`invalid_hash`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "You have not extended this scene to your platform. `code` is `extension_required`. Call `POST /extend` first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such scene.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "You already registered this `contentUrl`. `code` is `duplicate_publication`. The constraint is on (your partner account, contentUrl), so a URL you used on another scene also collides.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The scene is not greenlit. `code` is `not_greenlit`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/collab-scenes/{sceneId}/publications/{pubId}/remove": {
      "post": {
        "summary": "Remove a publication",
        "description": "Marks one of your publications as removed, for example when you action a request that arrived through the Easy2257 removal portal. Idempotent: a repeat call returns the original `removedAt` with `message: \"Already removed\"`. Only your own publications are addressable, and removing yours does not touch another platform's publication of the same scene. Fires `collab_scene.publication_removed` to the scene's creating partner.",
        "tags": [
          "Collab Scenes"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd5r9a4t0001qz8hb6k2m3xd"
            },
            "required": true,
            "name": "sceneId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "example": "cmd5rc1v90007qz8h4p8w2n6y"
            },
            "required": true,
            "name": "pubId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RemovePublicationBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Removed, or already removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemovePublicationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such publication, it is not on this scene, or it belongs to another partner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/upload-sessions": {
      "post": {
        "summary": "Start a hosted attestation session",
        "description": "Hands the whole attestation step to Easy2257. You post the content facts, redirect the uploader to `hostedUrl`, and Easy2257 renders the form, captures the producer attestation with IP, user agent and timestamp, and tells you when it is done. Your platform builds no compliance UI. `detectedFaceCount` chooses the path: 1 records a solo attestation and fires `upload_session.solo_completed`; 2 or more opens a collab scene from the collaborators the uploader names, emails them, and fires `collab_scene.created` carrying `uploadSessionId` so you can correlate it back to the content row. The uploader must already have an active solo account under your partner account. Sessions last 24 hours, and each call creates a new one.",
        "tags": [
          "Hosted Attestation"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadSessionBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Session created. Redirect the uploader to `hostedUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadSessionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failure. `code` is `missing_field` or `invalid_field`, and `field` names the offender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The uploader has no active Easy2257 solo account under your partner account. `code` is `uploader_not_solo_active`. Run them through solo onboarding first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/productions": {
      "get": {
        "summary": "List productions by performer email",
        "description": "Every production a given performer is documented in, scoped to productions linked to your partner account. This is the right-to-removal lookup: when a performer asks to be removed, one call gives you every piece of content on your platform they appear in.",
        "tags": [
          "Productions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "email",
              "description": "Required. Matched case-insensitively.",
              "example": "performer@example.com"
            },
            "required": true,
            "name": "performerEmail",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending_documentation",
                "documented",
                "revoked",
                "takedown_pending",
                "takedown_complete"
              ],
              "description": "Filter by documentation status. Defaults to `documented`, so pass an explicit value to see anything else."
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "1 to 200. Default 50.",
              "example": "50"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`nextCursor` from the previous page."
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Production list. An empty array means no match, which is not an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductionListResponse"
                }
              }
            }
          },
          "400": {
            "description": "`performerEmail` missing. `code` is `missing_parameter`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/productions/{productionId}": {
      "get": {
        "summary": "Get production status",
        "description": "Current compliance status of one production, plus the custodian identity you can print on a records statement. Scoped to productions linked to your partner account: anything else returns 404.",
        "tags": [
          "Productions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd3p8w1k0002qz8h9j4r7v5s"
            },
            "required": true,
            "name": "productionId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Production found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such production, or it is not linked to your partner account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/productions/{productionId}/verify-certificate": {
      "post": {
        "summary": "Verify a compliance certificate",
        "description": "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`.",
        "tags": [
          "Productions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd3p8w1k0002qz8h9j4r7v5s"
            },
            "required": true,
            "name": "productionId",
            "in": "path"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyCertificateBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The check ran. Read `valid`: this status is returned for good and bad certificates alike.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyCertificateResponse"
                }
              }
            }
          },
          "400": {
            "description": "`certificate` missing (`missing_field`), or the certificate verified but belongs to a different production (`production_id_mismatch`, returned as `{ valid: false, reason }`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/productions/{productionId}/certificates": {
      "get": {
        "summary": "List a production's certificates",
        "description": "Every certificate minted for a linked production, newest first, with lifecycle status and — for certificates issued since storage began — the signed JWT itself. Use it to re-fetch a certificate you missed or lost, to pick up the replacement after a renewal, and to see at a glance which certificate is `CURRENT`. Certificates issued before storage began return `jwt: null`; those arrive with the next annual renewal. Scoped to productions linked to your partner account: anything else returns 404.",
        "tags": [
          "Productions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": "cmd3p8w1k0002qz8h9j4r7v5s"
            },
            "required": true,
            "name": "productionId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Certificate list, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CertificatesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: a required field is missing or malformed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such production, or it is not linked to your partner account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/productions/revoked-certificates": {
      "get": {
        "summary": "Certificate revocation list",
        "description": "Certificates revoked since a timestamp, scoped to productions linked to your partner account. Run it on a daily cron. Every entry is a takedown signal: a reason starting `removal_request:` means take the content down. Annual renewals are not revocations and no longer enter this list (entries recorded before that split carry `type: supersession` and their content stays up). Advance your watermark from the top-level `retrievedAt`, and page with `nextCursor`.",
        "tags": [
          "Productions"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time",
              "description": "ISO 8601. Defaults to 30 days ago. Use the `retrievedAt` from your last successful sync.",
              "example": "2026-04-01T00:00:00Z"
            },
            "required": false,
            "name": "since",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "1 to 10000. Default 1000.",
              "example": "1000"
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`nextCursor` from the previous page, passed back verbatim."
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Revocation list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CrlResponse"
                }
              }
            }
          },
          "400": {
            "description": "`since` is not a valid date. `code` is `invalid_parameter`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No `Authorization: Bearer` header, or a key that is unknown, revoked, or expired. The body carries only `error`, never a `code`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "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`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            }
          },
          "500": {
            "description": "Server error. Retry with exponential backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {}
}