Arkova Verification API

Credential verification endpoints

Operations 6

GET /verify/{publicId} Verify a credential #
GET /verify/{publicId}/proof Get Merkle inclusion proof #
GET /credentials/{publicId}/ctdl Get CTDL JSON-LD for a credential #
POST /verify/batch Batch verify credentials #
GET /verify/entity Entity verification #
GET /verify/search Agentic verification search #

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/arkova-ai:arkova-ai-verification-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

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

arkova-ai-verification-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Arkova Verification API
  version: 1.0.1
  description: Programmatic credential verification API. Verify credentials anchored on the Bitcoin network. Worker-generated v1 JSON errors expose common top-level code and message fields. The legacy error field is retained unchanged (string or object), along with details and quota context, for existing integrations. Parse code and message for new integrations. Upstream gateway errors may not be JSON. Revision 1.0.1 restores legacy label and DIPLOMA inputs and clarifies receipts and webhook payloads; the endpoint version remains v1.
  contact:
    name: Arkova Support
    url: https://arkova.ai
servers:
- url: /api/v1
  description: Verification API v1
security:
- ApiKeyBearer: []
- ApiKeyHeader: []
tags:
- name: Verification
  description: Credential verification endpoints
paths:
  /verify/{publicId}:
    get:
      summary: Verify a credential
      description: Verify a single credential by its public ID. Returns the frozen verification response schema.
      operationId: verifyCredential
      tags:
      - Verification
      security:
      - ApiKeyBearer: []
      - ApiKeyHeader: []
      - {}
      parameters:
      - name: publicId
        in: path
        required: true
        description: The public ID of the credential (e.g., ARK-2026-TEST-001)
        schema:
          type: string
          minLength: 3
      responses:
        '200':
          description: Verification result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerificationResult'
              example:
                verified: true
                status: ACTIVE
                issuer_name: University of Michigan
                credential_type: DIPLOMA
                issued_date: '2026-01-15T00:00:00Z'
                expiry_date: null
                anchor_timestamp: '2026-03-12T10:30:00Z'
                bitcoin_block: 204567
                network_receipt_id: b8e381df09ca404eaae2e5e9d9b3d27567fe97ece39ead718f6d2c77ca60eb57
                record_uri: https://app.arkova.ai/verify/ARK-2026-TEST-001
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /verify/{publicId}/proof:
    get:
      summary: Get Merkle inclusion proof
      description: 'Returns the Merkle inclusion proof for a batch-anchored credential: the proof path, batch root, and the on-chain transaction it was committed in. `verified` is computed by locally recomputing the root from the proof — never trusted from stored status. Pass `?format=signed` to receive the bundle wrapped in a signed envelope bound to the issuer DID (requires PROOF_SIGNING_KEY_PEM/PROOF_SIGNING_KEY_ID to be configured; otherwise 503).'
      operationId: getMerkleProof
      tags:
      - Verification
      security:
      - ApiKeyBearer: []
      - ApiKeyHeader: []
      - {}
      parameters:
      - name: publicId
        in: path
        required: true
        description: The public ID of the credential
        schema:
          type: string
          minLength: 3
      - name: format
        in: query
        required: false
        description: Pass `signed` to receive a DID-bound signed proof envelope instead of the raw bundle.
        schema:
          type: string
          enum:
          - signed
      responses:
        '200':
          description: Merkle inclusion proof
          content:
            application/json:
              schema:
                type: object
                properties:
                  public_id:
                    type: string
                  fingerprint:
                    type: string
                  merkle_root:
                    type:
                    - string
                    - 'null'
                  merkle_proof:
                    type:
                    - array
                    - 'null'
                    items:
                      type: string
                  tx_id:
                    type:
                    - string
                    - 'null'
                  block_height:
                    type:
                    - integer
                    - 'null'
                  block_timestamp:
                    type:
                    - string
                    - 'null'
                    format: date-time
                  batch_id:
                    type:
                    - string
                    - 'null'
                  verified:
                    type: boolean
                    description: Recomputed locally from the proof path — never trusted from stored anchor status.
                  verdict:
                    type: string
                    enum:
                    - valid
                    - invalid
                    - unverifiable
                    description: 'R3: the same computation as `verified`, as three states. `invalid` means a check RAN and FAILED (an alarm; equivalent to verified=false). `unverifiable` means a check could not be completed — the duplicate-node structural guard was not exercised, either because the record carries no merkle_index/leaf_count or because the stored branch length does not match the tree its leaf_count describes — and is NOT an alarm. `valid` and `unverifiable` partition the old verified=true bucket; `verified` is unchanged and not deprecated.'
                  verdict_note:
                    type: string
                    description: The measured / asserted / NOT-asserted statement for `verdict` (Constitution §1.5). Always accompanies `verdict`.
                  proof_bundle:
                    type:
                    - object
                    - 'null'
                    additionalProperties: true
                    description: 'PROOF-05: self-contained bundle (block header, OP_RETURN payload, schema version, and — R1 — the tx_inclusion_branch + tx_block_index that let a verifier confirm transaction inclusion in the block locally) when the confirmation layer has been populated; null otherwise. The two R1 fields are additive and nullable (no schema-version bump) and are null on records populated before migration 0427.'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          description: Record not found, or no Merkle proof available (not batch-anchored)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: 'Stored proof data is malformed, OR the proof is batch-linked but its exact leaf_count could not be determined (fail-closed: the CVE-2012-2459 structural guard cannot honestly run, so the request is not downgraded to a weaker verdict). Only the leaf_count body carries `verdict`.'
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  verdict:
                    type: string
                    enum:
                    - unverifiable
                    description: 'R3: present ONLY on the indeterminate-leaf_count body, where a verification was attempted and could not be completed. Absent on the malformed-proof-data body (that fails during extraction, before any verification), and absent on 400/404/503. Consumers MUST fall back to `error` / the HTTP status when it is absent.'
                  verdict_note:
                    type: string
                    description: The measured / asserted / NOT-asserted statement for `verdict` (Constitution §1.5). Present exactly when `verdict` is.
        '503':
          description: Signed-format requested but signing is not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
  /credentials/{publicId}/ctdl:
    get:
      summary: Get CTDL JSON-LD for a credential
      description: 'Returns the public Credential Transparency Description Language JSON-LD projection for a publishable Arkova credential. Records that are not credentials (publications, patents, regulatory and SEC filings, financial and legal documents, charity and business-entity registry records, contracts, resumes, identity documents) have no CTDL projection and return 404 with `reason: "not_a_credential"`.'
      operationId: getCredentialCtdl
      tags:
      - Verification
      security:
      - ApiKeyBearer: []
      - ApiKeyHeader: []
      - {}
      parameters:
      - name: publicId
        in: path
        required: true
        description: The public ID of the credential (e.g., ARK-2026-TEST-001)
        schema:
          type: string
          minLength: 3
          maxLength: 128
      responses:
        '200':
          description: CTDL JSON-LD document for an active credential
          content:
            application/ld+json:
              schema:
                $ref: '#/components/schemas/CtdlCredential'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '410':
          description: CTDL JSON-LD document for a revoked credential
          content:
            application/ld+json:
              schema:
                $ref: '#/components/schemas/CtdlCredential'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
  /verify/batch:
    post:
      summary: Batch verify credentials
      description: Verify multiple credentials in a single request. Synchronous for ≤20 items, async for >20 (returns job_id).
      operationId: batchVerifyCredentials
      tags:
      - Verification
      security:
      - ApiKeyBearer: []
      - ApiKeyHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - public_ids
              properties:
                public_ids:
                  type: array
                  items:
                    type: string
                    minLength: 3
                  minItems: 1
                  maxItems: 100
                  description: Array of credential public IDs to verify
            example:
              public_ids:
              - ARK-2026-TEST-001
              - ARK-2026-TEST-002
      responses:
        '200':
          description: Synchronous batch results (≤20 items)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchResponse'
        '202':
          description: Async job created (>20 items)
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                    format: uuid
                  total:
                    type: integer
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
  /verify/entity:
    get:
      summary: Entity verification
      description: Search for an entity across all public records and credentials. x402 payment gate.
      operationId: verifyEntity
      tags:
      - Verification
      security:
      - ApiKeyBearer: []
      - ApiKeyHeader: []
      - {}
      parameters:
      - name: name
        in: query
        required: true
        schema:
          type: string
        description: Entity name to search
      - name: type
        in: query
        schema:
          type: string
          enum:
          - person
          - organization
          - any
        description: Entity type filter
      responses:
        '200':
          description: Entity verification results
          content:
            application/json:
              schema:
                type: object
                properties:
                  entity:
                    type: string
                  matches:
                    type: array
                    items:
                      type: object
                  total:
                    type: integer
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          description: Payment required (x402)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
  /verify/search:
    get:
      summary: Agentic verification search
      description: 'Search returning frozen verification schema results. Designed for AI agents, ATS systems, and background check integrations. Requires API key (not JWT). SCRUM-3906: this route now answers with EITHER a semantic (embedding + cosine-similarity) match OR a lexical (ILIKE substring) match — `search_mode` on the response says which. Semantic is used when ENABLE_SEMANTIC_SEARCH is on and the embed+match RPC succeeds; the route falls back to lexical, never a 503, when the flag is off or that RPC/embedding call fails. Only the semantic path costs an AI credit or emits `similarity`; `issuer_name`, `issued_date`, `expiry_date` and `anchor_timestamp` are omitted (never null) on a lexical result — that path does not query them. Semantic results always include `anchor_timestamp`: the Network Observed Time, or null when no network observation is available; creation time is never substituted.'
      operationId: agenticVerifySearch
      tags:
      - Verification
      security:
      - ApiKeyBearer: []
      - ApiKeyHeader: []
      parameters:
      - name: q
        in: query
        required: true
        schema:
          type: string
          minLength: 1
          maxLength: 500
      - name: threshold
        in: query
        schema:
          type: number
          default: 0.75
        description: Semantic-path only; ignored on a lexical-path request.
      - name: limit
        in: query
        schema:
          type: integer
          default: 5
          maximum: 20
      responses:
        '200':
          description: Verification results. `search_mode` (`semantic_vector` | `lexical_substring`) tells the caller which path answered — see the operation description.
          content:
            application/json:
              schema:
                type: object
                properties:
                  query:
                    type: string
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/VerificationResult'
                  count:
                    type: integer
                  threshold:
                    type: number
                    description: Echoes the request threshold; not applied on the lexical path.
                  search_mode:
                    type: string
                    enum:
                    - semantic_vector
                    - lexical_substring
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: No AI credits remaining for the semantic path (flag on, RPC not yet attempted). Retry to get a lexical result instead is NOT automatic on this status — insufficient credits is a distinct condition from a semantic RPC failure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: ENABLE_VERIFICATION_API is off (worker-wide gate, applies to all of /api/v1/*). No longer returned for ENABLE_SEMANTIC_SEARCH off — see the operation description.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
components:
  schemas:
    ApiError:
      type: object
      required:
      - error
      properties:
        error:
          oneOf:
          - type: string
          - type: object
            additionalProperties: true
            properties:
              code:
                type: string
              message:
                type: string
        code:
          type: string
          description: Common machine-readable error code; legacy error remains unchanged.
        message:
          type: string
    VerificationResult:
      type: object
      description: Frozen verification response schema (v1). Fields cannot be removed or changed.
      required:
      - verified
      properties:
        verified:
          type: boolean
        status:
          type: string
          enum:
          - ACTIVE
          - REVOKED
          - SUPERSEDED
          - EXPIRED
          - PENDING
          - SUBMITTED
        issuer_name:
          type: string
        recipient_identifier:
          type: string
          description: Hashed identifier, never raw PII
        credential_type:
          type: string
        issued_date:
          type:
          - string
          - 'null'
          format: date-time
        expiry_date:
          type:
          - string
          - 'null'
          format: date-time
        anchor_timestamp:
          type:
          - string
          - 'null'
          format: date-time
          description: 'Network Observed Time: the moment the Bitcoin network observed this anchor. Measured from the confirmed block, NOT the time the record was created in Arkova (the two differ by the confirmation interval). Semantic search retains this key with null when no observation exists; exact-id verification omits it in that case. Not asserted: anything about the content, authenticity, or legal effect of the underlying document.'
        bitcoin_block:
          type:
          - integer
          - 'null'
        network_receipt_id:
          type:
          - string
          - 'null'
        merkle_proof_hash:
          type:
          - string
          - 'null'
        record_uri:
          type: string
          format: uri
        jurisdiction:
          type: string
          description: Omitted when null, never returned as null
        description:
          type: string
          description: Immutable credential description, omitted when not present
        proof_availability:
          type: string
          enum:
          - per_document
          - root_only
          description: SCRUM-2575. Whether a per-document proof can actually be retrieved for this record. `per_document` = a per-document inclusion branch is stored and GET /verify/{publicId}/proof returns it. `root_only` = no per-document branch is stored; the fingerprint is committed on-chain via the referenced anchor receipt, but no self-contained offline proof bundle is available. Measured from stored proof data — never derived from status. Omitted (never null) for records that have not reached a settled on-chain state (PENDING / SUBMITTED). Additive field (Constitution 1.8); no API version change.
        proof_availability_note:
          type: string
          description: SCRUM-2575. Omitted when not applicable, never returned as null. Present exactly when proof_availability is. States what is measured, what is asserted, and what is NOT asserted for that class (Constitution 1.5) — in particular, for root_only records, that no self-contained per-document offline proof is available, and that its absence is not evidence the record is invalid.
        source:
          type: object
          properties:
            provider:
              type: string
              enum:
              - connector
              - docusign
              - google_drive
              - microsoft_365
              description: The connected system this record's document was retrieved from.
          description: 'SCRUM-4507. Which connected document source this record originated from. OMITTED (never null, never an empty object) when the record carries no recognised connector marker — absence means "not stated", NOT "uploaded by a person". Carries the provider label and NOTHING ELSE: no file, folder, shared-drive or revision identifier and no deep link into the source system. This endpoint answers anonymously, so a source identifier here would let any holder of a public record id probe the source system for that object; those identifiers are shown only to the record owner on the authenticated record page. Not asserted: that the document still exists in the source system, is unchanged there, or is reachable by the caller — see fingerprint_rederivability for what the fingerprint does and does not commit. Additive field (Constitution 1.8); no API version change.'
        compliance_controls:
          type:
          - array
          - 'null'
          items:
            type: string
          example:
          - SOC2-CC6.1
          - GDPR-5.1f
          - FERPA-99.31
          description: 'API-RICH-01 regulatory control IDs mapped to this anchor. Informational metadata only — see compliance_controls_note, which is always present alongside this field and states what these identifiers do NOT assert. OMITTED for records that are not a current anchored credential (REVOKED / EXPIRED / SUPERSEDED / not yet anchored): these identifiers describe a live compliance posture, so listing them for such a record would assert something Arkova does not hold (BUG-2026-06-24-007). Absence is not a statement that the record was never anchored — the anchor receipt fields are unaffected.'
        compliance_controls_note:
          type: string
          description: SCRUM-2227. Omitted when not applicable, never returned as null. Present whenever compliance_controls is present, absent otherwise. States that control identifiers are a credential-type mapping and NOT an audit, certification, conformity assessment, or attestation — in particular that no identifier asserts qualified status under eIDAS. Additive nullable field (Constitution 1.8); no API version change.
        chain_confirmations:
          type:
          - integer
          - 'null'
          description: API-RICH-01 Bitcoin block confirmations at anchor time.
        parent_public_id:
          type:
          - string
          - 'null'
          description: API-RICH-01 public ID of the parent anchor; internal UUIDs are never exposed.
        version_number:
          type:
          - integer
          - 'null'
          minimum: 1
          description: API-RICH-01 version in this anchor lineage; omitted when equal to 1.
        revocation_tx_id:
          type:
          - string
          - 'null'
          description: API-RICH-01 Bitcoin transaction ID of the revocation when status is REVOKED.
        revocation_block_height:
          type:
          - integer
          - 'null'
          description: API-RICH-01 Bitcoin block height at which revocation was anchored.
        file_mime:
          type:
          - string
          - 'null'
          description: API-RICH-01 source document MIME type.
        file_size:
          type:
          - integer
          - 'null'
          minimum: 0
          description: API-RICH-01 source document size in bytes.
        confidence_scores:
          type:
          - object
          - 'null'
          additionalProperties: true
          description: API-RICH-02 per-field confidence scores from the latest extraction manifest.
        sub_type:
          type:
          - string
          - 'null'
          description: API-RICH-02 fine-grained credential subtype from anchors.sub_type.
        error:
          type: string
    CtdlCredential:
      type: object
      description: Public CTDL JSON-LD credential projection.
      required:
      - '@context'
      - '@type'
      - ceterms:name
      - ceterms:offeredBy
      - ceterms:credentialStatusType
      - ceterms:dateEffective
      - ceterms:verificationServiceProfile
      - ceterms:identifier
      additionalProperties: true
      properties:
        '@context':
          type: string
          enum:
          - https://credreg.net/ctdl/schema/context/json
        '@type':
          type: string
          pattern: ^ceterms:[A-Za-z][A-Za-z]*$
        ceterms:name:
          type: string
        ceterms:ctid:
          type: string
          pattern: ^ce-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
        ceterms:offeredBy:
          type: object
          required:
          - '@type'
          - ceterms:name
          additionalProperties: true
          properties:
            '@type':
              type: string
              enum:
              - ceterms:Organization
            ceterms:name:
              type: string
            ceterms:ctid:
              type: string
              pattern: ^ce-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$
            ceterms:subjectWebpage:
              type: string
              format: uri
        ceterms:credentialStatusType:
          type: string
          enum:
          - ceterms:Active
          - ceterms:Revoked
          - ceterms:Expired
          - ceterms:Superseded
        ceterms:dateEffective:
          type: string
          format: date-time
        ceterms:verificationServiceProfile:
          type: object
          required:
          - '@type'
          - ceterms:name
          - ceterms:verificationService
          additionalProperties: true
          properties:
            '@type':
              type: string
              enum:
              - ceterms:VerificationServiceProfile
            ceterms:name:
              type: string
            ceterms:verificationService:
              type: string
              format: uri
        ceterms:identifier:
          type: object
          required:
          - ceterms:identifierType
          - ceterms:identifierValue
          additionalProperties: false
          properties:
            ceterms:identifierType:
              type: string
            ceterms:identifierValue:
              type: string
        ceterms:description:
          type: string
        ceterms:expirationDate:
          type: string
          format: date-time
        ceterms:revocationDate:
          type: string
          format: date-time
        ceterms:revocationReason:
          type: string
    BatchResponse:
      type: object
      properties:
        results:
          type: array
          items:
            allOf:
            - $ref: '#/components/schemas/VerificationResult'
            - type: object
              properties:
                public_id:
                  type: string
        job_id:
          type: string
          format: uuid
        total:
          type: integer
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    RateLimited:
      description: Rate limit exceeded
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until retry is allowed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    ServiceUnavailable:
      description: API not enabled (feature flag off)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    Unauthorized:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
  securitySchemes:
    ApiKeyBearer:
      type: http
      scheme: bearer
      description: 'API key as Bearer token: `Authorization: Bearer ak_live_...`'
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'API key via header: `X-API-Key: ak_live_...`'
    SupabaseJWT:
      type: http
      scheme: bearer
      description: Supabase JWT for key management endpoints