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.

Business capability
Identity Verification AE-160.30

Operations 6

POST /api/v2/kyc/documents/front Upload the front of the ID #
POST /api/v2/kyc/documents/back Upload the back of the ID #
POST /api/v2/kyc/information Submit information #
GET /api/v2/kyc Get verification status #
POST /api/v2/kyc/simulate Simulate an outcome (test mode) #
POST /api/v2/kyc/import Import a verification (Sumsub share token) #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/agentcard:agentcard-identity-verification-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

agentcard-identity-verification-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Agentcard 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).


        The response''s `extracted` object carries what the document reader pulled off the image — use it to prefill your details form so the user confirms instead of typing.'
      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
                extracted:
                  document_type: drivers_license
                  issuing_country: US
                  first_name: Jane
                  last_name: Doe
                  date_of_birth: '1990-05-14'
        '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
                    extracted:
                      document_type: drivers_license
                      issuing_country: US
                      document_number: D1234567
                      first_name: Jane
                      last_name: Doe
                      date_of_birth: '1990-05-14'
                      address_line1: 123 Market St
                      address_city: San Francisco
                      address_region: CA
                      address_postal_code: '94105'
                      address_country: US
                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`; values are trimmed, and a blank value counts as not provided. For a fresh verification, the response returns the `iframe_url` for the face scan. For an imported verification (Reusable KYC), the submit that completes the residential address returns `pending` and files the card issuer application right after it, in the background, so the response never waits on the issuer; the verification then moves to `approved` or `rejected` through the status poll or the `identity.verification.updated` webhook. Repeating the request is safe: it returns the current status.'
      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 ID or tax identification number for the country that issued their document — for US documents this is the SSN; for any other country it is the national ID / tax number printed on (or associated with) the document. Forwarded to the verification provider only; never stored by Agentcard.
                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`).
                user_ip:
                  type: string
                  description: 'The end user''s IP address as your frontend saw it (public IPv4 or IPv6; private or reserved addresses are rejected). Strongly recommended: identity screening checks the applicant''s IP, and an IP belonging to a datacenter (such as your backend''s) can fail an otherwise valid verification. Pass the address your user connected to you from; without it, your server''s own address may be recorded as a last resort and can fail that screening.'
            example:
              user_id: usr_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: `requires_verification` with the `iframe_url` for a fresh verification, or `pending` when the submit completed an imported verification (the card issuer application is filed right after the response).'
          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`, or a `user_ip` that is not a public IPv4/IPv6 address. `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: usr_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'
  /api/v2/kyc/import:
    post:
      tags:
      - Identity Verification
      summary: Import a verification (Sumsub share token)
      operationId: kycImport
      description: 'Import a verification you already ran on your **own Sumsub account** (Reusable KYC). Generate a single-use share token for the Agentcard client id, send it here, and the user skips document capture and the face scan. A successful import drops into the exact same status contract as a fresh verification: poll `GET /api/v2/kyc` or listen for `identity.verification.updated`. Requires one-time partner pairing between your Sumsub account and Agentcard''s (per environment) — ask your Agentcard contact to enable it. Share tokens are single-use with a short TTL, so generate one fresh per import attempt.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - user_id
              - share_token
              properties:
                user_id:
                  type: string
                  description: The connected user's id.
                share_token:
                  type: string
                  description: A Sumsub share token generated by your account for the Agentcard client id (`POST /resources/accessTokens/shareToken` with `forClientId`). Single-use; expires after its `ttlInSecs`.
                user_ip:
                  type: string
                  description: 'The end user''s IP address as your frontend saw it (public IPv4 or IPv6; private or reserved addresses are rejected). Strongly recommended: identity screening checks the applicant''s IP, and an IP belonging to a datacenter (such as your backend''s) can fail an otherwise valid verification. Pass the address your user connected to you from; without it, your server''s own address may be recorded as a last resort and can fail that screening.'
            example:
              user_id: usr_7g8h9i
              share_token: _act-jwt-eyJhbGciOiJub25lIn0...
      responses:
        '200':
          description: 'The verification''s state after the import, exactly as `GET /api/v2/kyc` now reports it. A complete applicant lands on `pending` while checks run (then `approved`). An applicant whose shared verification carried no residential address lands on `needs_information` with the exact `required_fields`: submit them via `POST /api/v2/kyc/information` right away (import first, then information), and the card issuer application is filed as soon as the address lands.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/KycState'
              example:
                object: kyc
                status: pending
        '400':
          description: '`invalid_request` — missing `user_id` or `share_token`, or a `user_ip` that is not a public IPv4/IPv6 address. `invalid_share_token` — the token is invalid, expired, or already used; generate a fresh one and retry. `sharing_not_configured` — test mode has no sharing tenant configured; use `POST /api/v2/kyc/simulate` instead.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: '`sharing_not_enabled` — your Sumsub account and Agentcard''s are not paired in this environment. Complete partner pairing first.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: '`connection_not_found` — the user has no active connection under this client.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: '`verification_in_progress` — a verification is already in flight for this user. Drive it to completion with the status flow instead of importing.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: '`verification_not_compatible` — the shared verification does not include everything the card program requires (an identity document and a selfie check). Share applicants from a level that includes both, or let the user run the standard flow.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
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'
    VerificationError:
      description: '`verification_error` — the step failed downstream. Try again.'
      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'
    KycDocumentBadRequest:
      description: '`invalid_request` — missing `user_id` or `image`, an unrecognized `document_type` / `issuing_country`, or a `user_ip` that is not a public IPv4/IPv6 address. `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'
    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'
    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).
              user_ip:
                type: string
                description: 'The end user''s IP address as your frontend saw it (public IPv4 or IPv6; private or reserved addresses are rejected). Strongly recommended: identity screening checks the applicant''s IP, and an IP belonging to a datacenter (such as your backend''s) can fail an otherwise valid verification. Pass the address your user connected to you from; without it, your server''s own address may be recorded as a last resort and can fail that screening.'
          example:
            user_id: usr_7g8h9i
            image: <base64>
            mime_type: image/jpeg
  schemas:
    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: 'On every actionable status (`awaiting_documents`, `needs_information`, `requires_verification`): the hosted page that collects whatever the verification still needs, and the face scan at `requires_verification`. 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).
        extracted:
          type: object
          description: 'Optional, on document uploads — what the document reader pulled off the uploaded image(s), so you can prefill your details form instead of asking the user to re-type what the ID already says. Keys match the `/kyc/information` request fields (`first_name`, `last_name`, `date_of_birth`, `address_line1`, `address_city`, `address_region`, `address_postal_code`, `address_country`) plus `document_type`, `issuing_country`, and `document_number` (the number printed on the document — for US documents this is NOT the SSN, so never prefill it into `national_id_number` when `issuing_country` is `US`). Fields appear as they become readable: the front usually carries the name and date of birth; a US back adds the barcode address. Always let the user confirm or correct prefilled values.'
          additionalProperties:
            type: string
        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.
    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.
  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.'
    userAccessToken:
      type: http
      scheme: bearer
      description: The user's connection access_token (user authentication), or an org-minted buy_token for org-owned accounts.
x-provenance:
  method: harvested
  publisher: Agentcard
  source: https://docs.agentcard.sh/openapi.json
  note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
    stamped by stamp-openapi-provenance.py (roadmap#793)