Agentcard Identity verification API

Verify a connected user's identity: upload their ID, submit any extra fields we ask for, then show a short face scan.

OpenAPI Specification

agentcard-identity-verification-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: Agentcard Authentication Identity verification API
  version: 2.0.0
  description: The Agentcard v2 API — connect your users and verify their identity from your own backend. Every call is authenticated with a platform access token minted from your `client_id` + `client_secret`.
servers:
- url: https://api.agentcard.sh
  description: There is one base URL. Sandbox vs production is decided by the client credential you use, never by the host.
security:
- platformToken: []
tags:
- name: Identity verification
  description: 'Verify a connected user''s identity: upload their ID, submit any extra fields we ask for, then show a short face scan.'
paths:
  /api/v2/kyc/documents/front:
    post:
      tags:
      - Identity verification
      summary: Upload the front of the ID
      operationId: kycUploadFront
      description: 'Uploads the front of the user''s identity document as a base64-encoded image. This step acknowledges receipt; the next step (`back`) tells you what comes next.


        Any upload response may include a `warnings` array with actionable feedback (for example, that the other side of the document is still needed).'
      requestBody:
        $ref: '#/components/requestBodies/KycDocument'
      responses:
        '200':
          description: Receipt acknowledged — upload the back next. May include a `warnings` array.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycState'
              example:
                object: kyc
                status: awaiting_documents
        '400':
          $ref: '#/components/responses/KycDocumentBadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/ConnectionNotFound'
        '409':
          $ref: '#/components/responses/UserConflict'
        '422':
          $ref: '#/components/responses/DocumentUnprocessable'
        '502':
          $ref: '#/components/responses/VerificationError'
  /api/v2/kyc/documents/back:
    post:
      tags:
      - Identity verification
      summary: Upload the back of the ID
      operationId: kycUploadBack
      description: 'Uploads the back of the document. The response tells you what to do next — this is the branch point of the flow:


        - `needs_information` → collect exactly the `required_fields` and post them to `/kyc/information`.

        - `requires_verification` → show the user the `iframe_url` for the face scan.

        - `rejected` → the document couldn''t be verified.'
      requestBody:
        $ref: '#/components/requestBodies/KycDocument'
      responses:
        '200':
          description: The next step of the flow.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycState'
              examples:
                needs_information:
                  summary: Needs more info
                  value:
                    object: kyc
                    status: needs_information
                    required_fields:
                    - national_id_number
                    - phone_number
                    - address_line1
                    - address_city
                    - address_region
                    - address_postal_code
                    - address_country
                requires_verification:
                  summary: Ready for face scan
                  value:
                    object: kyc
                    status: requires_verification
                    iframe_url: https://in.sumsub.com/websdk/p/…
        '400':
          $ref: '#/components/responses/KycDocumentBadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/ConnectionNotFound'
        '409':
          $ref: '#/components/responses/UserConflict'
        '422':
          $ref: '#/components/responses/DocumentUnprocessable'
        '502':
          $ref: '#/components/responses/VerificationError'
  /api/v2/kyc/information:
    post:
      tags:
      - Identity verification
      summary: Submit information
      operationId: kycSubmitInformation
      description: Submits the extra fields requested by a `needs_information` response. Send only the fields listed in `required_fields`. On success, the response returns the `iframe_url` for the face scan.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user_id
              properties:
                user_id:
                  type: string
                  description: The connected user's id.
                first_name:
                  type: string
                last_name:
                  type: string
                date_of_birth:
                  type: string
                  description: ISO 8601 date, `YYYY-MM-DD`.
                national_id_number:
                  type: string
                  description: The user's national identification number.
                phone_number:
                  type: string
                  description: E.164 format with country code (e.g. `+15551234567`).
                address_line1:
                  type: string
                address_line2:
                  type: string
                address_city:
                  type: string
                address_region:
                  type: string
                  description: State, province, or region.
                address_postal_code:
                  type: string
                address_country:
                  type: string
                  description: ISO 3166-1 alpha-2 country code (e.g. `US`).
            example:
              user_id: user_7g8h9i
              national_id_number: '123456789'
              address_line1: 123 Main St
              address_city: San Francisco
              address_region: CA
              address_postal_code: '94105'
              address_country: US
      responses:
        '200':
          description: The next step — usually `requires_verification` with the `iframe_url`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycState'
              example:
                object: kyc
                status: requires_verification
                iframe_url: https://in.sumsub.com/websdk/p/…
        '400':
          description: '`invalid_request` — missing `user_id`. `invalid_fields` — a value didn''t check out; the error adds a `field_errors` object naming each field to fix.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: invalid_fields
                  message: Some of the information provided is invalid.
                  field_errors:
                    date_of_birth: Enter a valid date.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/ConnectionNotFound'
        '409':
          $ref: '#/components/responses/UserConflict'
        '502':
          $ref: '#/components/responses/VerificationError'
  /api/v2/kyc:
    get:
      tags:
      - Identity verification
      summary: Get verification status
      operationId: kycGetStatus
      description: Polls the current verification status — the alternative to the `identity.verification.updated` webhook.
      parameters:
      - name: user_id
        in: query
        required: true
        schema:
          type: string
        description: The connected user's id.
      responses:
        '200':
          description: The current status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycState'
              example:
                object: kyc
                status: approved
        '400':
          description: '`invalid_request` — missing `user_id`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/ConnectionNotFound'
        '409':
          $ref: '#/components/responses/UserConflict'
  /api/v2/kyc/simulate:
    post:
      tags:
      - Identity verification
      summary: Simulate an outcome (test mode)
      operationId: kycSimulate
      description: '**Test mode only.** Drives a test-mode verification to a chosen terminal outcome instantly — test verifications never complete on their own. The simulated verdict flows through the same status contract and fires the same `identity.verification.updated` webhook a real review produces, so your status handling and webhook consumer are exercised end to end. Requires a test-mode client credential; live tokens get `403 sandbox_only`.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user_id
              - outcome
              properties:
                user_id:
                  type: string
                  description: The connected user's id.
                outcome:
                  type: string
                  enum:
                  - approved
                  - rejected
                  - requires_input
                  description: The verdict to apply. `approved` — verification succeeds. `rejected` — terminal rejection. `requires_input` — a retryable bounce asking for new document photos.
                reason:
                  type: string
                  maxLength: 300
                  description: Optional end-user-safe explanation carried on non-approved outcomes — it appears as `reason` in statuses and webhook events, exactly like a real review's. It must not name internal providers or identifiers (rejected with `400 invalid_reason`), since it is shown to end users verbatim.
            example:
              user_id: user_7g8h9i
              outcome: rejected
              reason: The name on the document does not match the application.
      responses:
        '200':
          description: The verification's new state, exactly as `GET /api/v2/kyc` now reports it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycState'
              example:
                object: kyc
                simulated: true
                status: rejected
                reason: The name on the document does not match the application.
        '400':
          description: '`invalid_request` — missing `user_id` or an unknown `outcome`. `invalid_reason` — the `reason` names an internal provider or identifier (it is shown to end users verbatim).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: '`sandbox_only` — the token is a live credential. Live verifications are decided by the identity provider and cannot be simulated.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: sandbox_only
                  message: Simulated verification outcomes only exist in test mode. Live verifications are decided by the identity provider.
        '404':
          $ref: '#/components/responses/ConnectionNotFound'
        '409':
          $ref: '#/components/responses/UserConflict'
        '502':
          $ref: '#/components/responses/VerificationError'
components:
  responses:
    UserConflict:
      description: '`user_conflict` — the email on file in your organization belongs to a different account. Contact support.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: '`unauthorized` — the platform access token is missing or expired. Exchange your client credentials for a fresh one.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    VerificationError:
      description: '`verification_error` — the step failed downstream. Try again.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    KycDocumentBadRequest:
      description: '`invalid_request` — missing `user_id` or `image`, or an unrecognized `document_type` / `issuing_country`. `invalid_image` — `image` isn''t valid base64. `client_credentials_required` — the token wasn''t minted from client credentials.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ConnectionNotFound:
      description: '`connection_not_found` — no connection exists for that user under your client.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    DocumentUnprocessable:
      description: '`document_rejected` — the image couldn''t be processed; ask the user to retake the photo. `document_expired` — the document itself is expired; ask for a valid one. Either may include a `warnings` array with actionable feedback.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: document_rejected
              message: The document image could not be processed — ask the user for a new, clear photo.
              warnings:
              - The verification provider still needs the back of the document — ask the user for the other side.
  requestBodies:
    KycDocument:
      required: true
      content:
        application/json:
          schema:
            type: object
            required:
            - user_id
            - image
            properties:
              user_id:
                type: string
                description: The connected user's id.
              image:
                type: string
                description: The image bytes, base64-encoded.
              mime_type:
                type: string
                default: image/jpeg
                description: The image's MIME type.
              document_type:
                type: string
                enum:
                - drivers_license
                - state_id
                - passport
                description: Optional document-type hint. Without it the document is auto-detected and defaults to a US driver's license. If a response `warnings` entry asks you to resubmit with a `document_type`, send the same image again with this field set — no need to go back to the user.
              issuing_country:
                type: string
                description: Optional ISO 3166-1 country code (alpha-2 or alpha-3) of the country that issued the document. Defaults to US when the document doesn't reveal it — always send it for non-US documents (e.g. `DE` for a German national ID).
          example:
            user_id: user_7g8h9i
            image: <base64>
            mime_type: image/jpeg
  schemas:
    Error:
      type: object
      description: Every v2 error uses the same envelope.
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: A stable, machine-readable string (snake_case). Branch on this.
            message:
              type: string
              description: A human-readable explanation, safe to log.
            docs:
              type: string
              description: A link back to the reference.
            field_errors:
              type: object
              additionalProperties:
                type: string
              description: Only on `invalid_fields` — names each field to fix.
            warnings:
              type: array
              items:
                type: string
              description: Only on document upload errors — actionable feedback safe to show the user.
    KycState:
      type: object
      description: The single status contract every KYC response carries.
      properties:
        object:
          type: string
          enum:
          - kyc
        status:
          type: string
          enum:
          - awaiting_documents
          - needs_information
          - requires_verification
          - pending
          - approved
          - rejected
          description: '`awaiting_documents` — upload the front and back. `needs_information` — collect the `required_fields` and submit them. `requires_verification` — show the user the `iframe_url`. `pending` — under review, no action needed. `approved` — verified, done. `rejected` — the user did not pass. Statuses are not one-way: a review can send a user back — `pending` may return to `needs_information` (a detail didn''t match the document; re-collect the listed fields and resubmit, the check re-runs automatically) or to `awaiting_documents` (the images were unusable; upload both sides again). Always branch on the current status.'
        required_fields:
          type: array
          items:
            type: string
          description: Only on `needs_information` — exactly the fields to collect and post to `/kyc/information`.
        iframe_url:
          type: string
          description: 'Only on `requires_verification` — the URL to show the user for the face scan. Embed it in an iframe with `allow="camera; microphone"`. Short-lived: always use the most recent one from a poll or webhook, never a stored copy.'
        warnings:
          type: array
          items:
            type: string
          description: Optional, on document uploads — actionable feedback safe to show the user (for example, that the other side of the document is still needed).
        reason:
          type: string
          description: Optional, on `needs_information`, `awaiting_documents`, `requires_verification`, and `rejected` — a short, end-user-safe explanation of what the review asked for (for example, “Enter your full name exactly as it appears on your identity document.”). Safe to show the user verbatim.
  securitySchemes:
    platformToken:
      type: http
      scheme: bearer
      description: 'A platform access token. Get one on the **Create an access token** endpoint by exchanging your `client_id` + `client_secret`, then send it as `Authorization: Bearer <token>`. Tokens live one hour.'