openapi: 3.1.0
info:
  title: "Copply: Age Verification API"
  version: 1.0.0
  summary: Age verification API and parental-consent API for apps.
  description: |
    Copply helps social, messaging, gaming, marketplace, and UGC apps add an
    age verification API, parental consent API, privacy deletion logs, and
    audit-ready consent workflows without building a compliance backend from
    scratch. This API is designed for server-side calls from your app.

    Important authentication note: create a Copply account and generate a
    Copply API key at https://copply.basemapped.com/keys. Send that key on
    every API request as `Authorization: Bearer cp_live_...` or
    `Authorization: Bearer cp_test_...`.

    RapidAPI marketplace credentials and `X-RapidAPI-Key` headers do not
    authenticate Copply requests yet. RapidAPI is currently a discovery and
    listing surface for this API.

    Copply is a compliance workflow tool, not legal advice. Customers remain
    responsible for confirming their obligations with counsel and their AHJ or
    regulator.
  contact:
    name: Basemapped
    url: https://basemapped.com/copply
    email: support@basemapped.com
  license:
    name: MIT
    identifier: MIT
servers:
  - url: https://copply.basemapped.com/api/copply
    description: Copply production API
externalDocs:
  description: Copply dashboard and developer docs
  url: https://copply.basemapped.com/docs
security:
  - bearerApiKey: []
tags:
  - name: Verification
    description: Age checks and state-law decisions.
  - name: Consent
    description: Parental consent request and verification flows.
  - name: Privacy
    description: Deletion and audit workflows.
paths:
  /verify-age:
    post:
      tags:
        - Verification
      operationId: verifyAge
      summary: Verify a user's age against configured state rules.
      description: |
        Returns whether a user is allowed, blocked, or must complete parental
        consent. The API detects state from `stateHint`, Cloudflare headers, or
        an IP fallback, then applies the developer's enabled compliance config.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VerifyAgeRequest"
            examples:
              texasMinor:
                summary: Texas minor
                value:
                  userId: smoke-user-1
                  declaredAge: 17
                  stateHint: TX
                  devicePlatform: ios
              federalNoAge:
                summary: No declared age
                value:
                  userId: anonymous-user-42
                  stateHint: Federal
      responses:
        "200":
          description: Verification decision.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VerifyAgeResponse"
              examples:
                requiresConsent:
                  value:
                    allowed: false
                    blocked: false
                    requiresConsent: true
                    consentMethod: email
                    consentDeadline: "2026-07-03T12:00:00.000Z"
                    reason: "Underage: declared 17, minimum 18 for LA"
                    state: LA
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
  /parental-consent:
    post:
      tags:
        - Consent
      operationId: requestParentalConsent
      summary: Request, check, or verify parental consent.
      description: |
        Use `consentMethod: check` to read the latest consent status. Use
        `email`, `creditcard`, or `idupload` to create a new consent flow. Pass
        `consentToken` with the original `userId` to verify a pending consent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ParentalConsentRequest"
            examples:
              check:
                summary: Check existing consent
                value:
                  userId: app-user-123
                  consentMethod: check
              email:
                summary: Send email consent
                value:
                  userId: app-user-123
                  consentMethod: email
                  parentEmail: parent@example.com
              creditCard:
                summary: Create credit-card verification payment intent
                value:
                  userId: app-user-123
                  consentMethod: creditcard
              idUpload:
                summary: Create Persona ID verification inquiry
                value:
                  userId: app-user-123
                  consentMethod: idupload
              verifyToken:
                summary: Verify a pending consent token
                value:
                  userId: app-user-123
                  consentMethod: email
                  consentToken: cns_example_token
      responses:
        "200":
          description: Consent result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ParentalConsentResponse"
              examples:
                pendingEmail:
                  value:
                    consentVerified: false
                    consentToken: cns_example_token
                    consentMethod: email
                    expiresAt: "2026-07-03T12:00:00.000Z"
                    emailSent: true
                    message: "Consent request created. Method: email. Awaiting verification."
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          description: Upstream provider failed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                emailFailed:
                  value:
                    error: EMAIL_FAILED
                    message: Failed to send consent email
        "500":
          $ref: "#/components/responses/InternalError"
  /get-upload-url:
    post:
      tags:
        - Consent
      operationId: createSignedUploadUrl
      summary: Create a signed upload URL for consent ID uploads.
      description: |
        Creates a short-lived Supabase Storage signed upload URL in the
        `consent-id-uploads` bucket. Use this before an ID upload consent flow
        when your app collects the file directly.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UploadUrlRequest"
            examples:
              jpeg:
                value:
                  filename: parent-id.jpg
                  contentType: image/jpeg
      responses:
        "200":
          description: Signed upload URL.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UploadUrlResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /delete-verification-data:
    post:
      tags:
        - Privacy
      operationId: deleteVerificationData
      summary: Delete verification and consent data for one app user.
      description: |
        Soft-deletes matching verification request rows, hard-deletes matching
        consent records, and writes a deletion audit hash for compliance logs.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteVerificationDataRequest"
            examples:
              userDeletion:
                value:
                  userId: app-user-123
      responses:
        "200":
          description: Deletion confirmation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeleteVerificationDataResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
  /verify-consent:
    get:
      tags:
        - Consent
      operationId: verifyConsentLink
      summary: Browser-facing consent verification link.
      description: |
        Parent-facing browser endpoint used from email links. This endpoint
        returns HTML and is not intended for server-to-server API use.
      security: []
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
          description: Consent token from the email consent flow.
      responses:
        "200":
          description: HTML verification result.
          content:
            text/html:
              schema:
                type: string
        "400":
          description: Missing or invalid token.
          content:
            text/html:
              schema:
                type: string
components:
  securitySchemes:
    bearerApiKey:
      type: http
      scheme: bearer
      bearerFormat: Copply API key
      description: "Create a Copply dashboard key at https://copply.basemapped.com/keys. Use `Authorization: Bearer cp_live_...` or `Authorization: Bearer cp_test_...`. RapidAPI `X-RapidAPI-Key` headers do not authenticate Copply requests yet."
  schemas:
    VerifyAgeRequest:
      type: object
      additionalProperties: false
      required:
        - userId
      properties:
        userId:
          type: string
          minLength: 1
          description: Stable user identifier from your app. Copply hashes this before storage.
          examples:
            - app-user-123
        declaredAge:
          type: integer
          minimum: 0
          maximum: 130
          description: User-declared age. If absent, Copply requires consent when available.
          examples:
            - 17
        devicePlatform:
          type: string
          description: Optional platform label for audit logs.
          examples:
            - ios
            - web
        stateHint:
          $ref: "#/components/schemas/StateCode"
    VerifyAgeResponse:
      type: object
      additionalProperties: false
      required:
        - allowed
        - blocked
        - requiresConsent
        - reason
        - state
      properties:
        allowed:
          type: boolean
        blocked:
          type: boolean
        requiresConsent:
          type: boolean
        consentMethod:
          $ref: "#/components/schemas/ConsentMethod"
        consentDeadline:
          type: string
          format: date-time
        reason:
          type: string
        state:
          type: string
        warning:
          type: string
    ParentalConsentRequest:
      type: object
      additionalProperties: false
      required:
        - userId
        - consentMethod
      properties:
        userId:
          type: string
          minLength: 1
        consentMethod:
          $ref: "#/components/schemas/ConsentMethod"
        consentToken:
          type: string
          description: Existing consent token to verify.
        parentEmail:
          type: string
          format: email
          description: Required when `consentMethod` is `email`.
        filePath:
          type: string
          description: Optional storage path for uploaded ID evidence.
    ParentalConsentResponse:
      type: object
      additionalProperties: true
      required:
        - consentVerified
      properties:
        consentVerified:
          type: boolean
        consentStatus:
          type: string
          enum:
            - pending
            - verified
            - expired
            - revoked
        consentToken:
          type: string
        consentMethod:
          $ref: "#/components/schemas/ConsentMethod"
        expiresAt:
          type: string
          format: date-time
        verifiedAt:
          type: string
          format: date-time
        message:
          type: string
        emailSent:
          type: boolean
        clientSecret:
          type: string
          description: Stripe PaymentIntent client secret for credit-card consent.
        personaInquiryId:
          type: string
        personaUrl:
          type: string
          format: uri
    UploadUrlRequest:
      type: object
      additionalProperties: false
      properties:
        filename:
          type: string
          maxLength: 100
          examples:
            - parent-id.jpg
        contentType:
          type: string
          examples:
            - image/jpeg
    UploadUrlResponse:
      type: object
      additionalProperties: false
      required:
        - signedUrl
        - path
      properties:
        signedUrl:
          type: string
          format: uri
        path:
          type: string
        expiresAt:
          type: string
          format: date-time
    DeleteVerificationDataRequest:
      type: object
      additionalProperties: false
      required:
        - userId
      properties:
        userId:
          type: string
          minLength: 1
    DeleteVerificationDataResponse:
      type: object
      additionalProperties: false
      required:
        - success
        - confirmationHash
        - verificationsDeleted
        - consentsDeleted
        - message
      properties:
        success:
          type: boolean
        confirmationHash:
          type: string
        verificationsDeleted:
          type: integer
          minimum: 0
        consentsDeleted:
          type: integer
          minimum: 0
        message:
          type: string
    ErrorResponse:
      type: object
      additionalProperties: true
      required:
        - error
        - message
      properties:
        error:
          type: string
        message:
          type: string
    ConsentMethod:
      type: string
      enum:
        - email
        - creditcard
        - idupload
        - check
    StateCode:
      type: string
      enum:
        - TX
        - CA
        - LA
        - UT
        - Federal
      description: Supported configured law profile.
  responses:
    BadRequest:
      description: Missing or invalid request fields.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Missing, invalid, or revoked Copply API key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimited:
      description: Daily request or MAU limit exceeded.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    InternalError:
      description: Server-side error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
