Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.beltic.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Verification answers a simple question: is this credential currently valid, and what does it say? The Beltic Credentials API runs a deterministic seven-step pipeline for every verify call, returning a flat response that’s either a success (with the full subject + claims) or a structured rejection (with a machine-readable reason code).

Prerequisites

  • A Beltic API key with credentials:verify permission
  • A signed credential JWT — the signed_payload field from a previous issue response

The Seven-Step Pipeline

Every verify call runs through these checks, in order. The first failure short-circuits the rest.
1

Parse JWS

Decode the JWT header and payload. Extract alg, kid, and iss. Reject if malformed or if alg isn’t ES256.
2

Resolve the issuer key

Look up iss in the trusted-issuer list (V1 is just did:web:beltic.com), then fetch the public JWK by kid from /.well-known/jwks.json.
3

Verify the signature

Cryptographically verify the JWT against the resolved key using jose.jwtVerify. Reject on signature mismatch.
4

Check standard claims

Validate iat, exp, nbf. Optionally validate aud if context.audience was provided.
5

Validate the schema

Match the JWT claims against the registered credential-type schema. Catches forged credentials whose signature is valid but whose claim shape is wrong.
6

Check status

Look up the credential in Beltic’s registry, then check the Status List 2021 bit. Reject if revoked, expired, or suspended.
7

Apply policy

For agent_authorization credentials with a verify context, evaluate the credential’s permissions[] against the request context — fields like amount, currency, resource. Reject on no matching permission or condition failure.

Basic Verify

The minimum request: just the JWT.
curl -X POST https://api.beltic.com/v1/credentials/verify \
  -H "X-Api-Key: $BELTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"credential\": \"$CREDENTIAL_JWT\"}"
The issue response returns the JWT as signed_payload; the verify endpoint accepts it as credential. Same value, different field names — signed_payload is the persisted resource field, credential follows the JWT-VC presentation convention. Pass the JWT string directly either way.

Success Response

{
  "valid": true,
  "credential_id": "cred_01HQ7P4M6...",
  "credential_type": "business",
  "issuer_did": "did:web:beltic.com",
  "subject": {
    "id": "biz_widgetcorp",
    "type": "organisation",
    "name": "WidgetCorp Ltd"
  },
  "issued_at": "2026-05-21T10:30:00Z",
  "expires_at": "2026-08-19T10:30:00Z",
  "verified_at": "2026-05-21T14:22:11Z",
  "status": "active",
  "evidence_refs": [],
  "verification_id": "ver_01HQ8R..."
}
  • valid: true is the headline.
  • status is the persisted lifecycle (active, revoked, expired, suspended) — different from valid, which is the protocol outcome.
  • verification_id is unique per call. Use it as a correlation handle for audit and support.

Rejection Response

{
  "valid": false,
  "reason": "revoked",
  "credential_id": "cred_01HQ7P4M6...",
  "status": "revoked",
  "verified_at": "2026-05-21T14:30:00Z",
  "verification_id": "ver_01HQ8S...",
  "details": {
    "revoked_at": "2026-05-21T14:25:00Z",
    "reason": "rotated_key"
  }
}

Verify with Policy Context

For agent_authorization credentials, you can pass a context object to evaluate the agent’s permissions against a specific request. This is how downstream services (e.g. a payment gateway) check that the agent is authorized for this particular call.
curl -X POST https://api.beltic.com/v1/credentials/verify \
  -H "X-Api-Key: $BELTIC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "credential": "'"$AGENT_JWT"'",
    "context": {
      "resource_type": "wallet",
      "resource_id": "*",
      "action": "payment_authorize",
      "transaction_amount": 4999,
      "transaction_currency": "usd",
      "audience": "stripe.com"
    }
  }'
If the credential’s permissions allow the action, the response includes a policy_match:
{
  "valid": true,
  "credential_id": "cred_...",
  "credential_type": "agent_authorization",
  "policy_match": {
    "matched": true,
    "permission_index": 0,
    "conditions_evaluated": [
      { "field": "amount", "op": "lte", "result": "pass" },
      { "field": "currency", "op": "eq", "result": "pass" }
    ]
  },
  "verification_id": "ver_..."
}
If a condition fails, you get a structured rejection:
{
  "valid": false,
  "reason": "condition_failed",
  "credential_id": "cred_...",
  "details": {
    "deny_reason": "condition_failed:amount",
    "conditions_evaluated": [
      { "field": "amount", "op": "lte", "result": "fail" }
    ]
  },
  "verification_id": "ver_..."
}

Reject Reason Codes

ReasonPipeline stepMeaning
malformed_jwt1JWT couldn’t be parsed
alg_not_allowed1Algorithm isn’t ES256
issuer_not_trusted2iss isn’t in the trust list
kid_not_found2Key ID doesn’t resolve in the issuer’s JWKS
signature_mismatch3Signature failed verification
expired4 or 6Credential past its exp, or status list says expired
not_yet_valid4nbf is in the future
audience_mismatch4aud doesn’t match context.audience
schema_mismatch5JWT claim shape doesn’t match the credential type
revoked6Status List 2021 bit is flipped
policy_deny7Generic policy denial
no_matching_permission7No permission in the credential matched the request
condition_failed7A permission matched but its conditions failed

Verifying Offline (Without the API)

The full pipeline runs server-side, but the cryptographic checks (steps 1-5) can be replicated offline:
  1. Fetch https://api.beltic.com/.well-known/jwks.json and cache it. Honour the response’s Cache-Control header (typically 1 hour).
  2. Use any JWT library to verify the JWS against the JWKs.
  3. Validate iat, exp, nbf yourself.
  4. To check revocation, fetch https://api.beltic.com/.well-known/status-lists/v1 and decode the bitstring at the credential’s status_list_index.
The Beltic verify endpoint is recommended for most callers — it handles trust-list management, JWKS caching, and policy evaluation. Offline verification is appropriate when you need to verify in air-gapped environments or want to minimize calls to Beltic.

Public Verify (No Auth)

There’s also a public verify endpoint at POST /v1/credentials/_public/verify that doesn’t require an API key. It returns the same shape but doesn’t expose org-internal fields. Use it for verifier integrations where you can’t ship an API key (e.g. browser-side checks).

Next Steps

Revoke a Credential

Mark a credential revoked and understand how verifiers see the change.

Issue a Credential

Mint a new credential to verify.