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
This guide walks through issuing a credential with POST /v1/credentials. You’ll learn what goes in the request body, what comes back, and how the four credential types differ.
Prerequisites
A Beltic API key with credentials:write permission
A subject (the entity the credential is about) — an organisation, person, agent, or document
Knowledge of which credential_type you want to issue
Choosing a Credential Type
Type When to use businessAttest that an organisation has been KYB-verified — registered, beneficial owners disclosed, sanctions cleared. userAttest a fact about a person — KYC status, age, jurisdiction, trust tier. agent_authorizationAuthorize an AI agent to act on behalf of a principal within explicit, resource-scoped limits. outcome_attestationWorkflow-attested credentials carrying generic claims — useful for compliance, audit, or workflow-block outputs.
Step 1: Build the Request
The request body has three required fields plus a few optional ones:
{
"credential_type" : "business" ,
"subject" : {
"id" : "biz_widgetcorp" ,
"type" : "organisation" ,
"name" : "WidgetCorp Ltd"
},
"claims" : {
"kyb_status" : "approved" ,
"verified_at" : "2026-05-20T00:00:00Z" ,
"jurisdiction" : "US-DE"
},
"expires_at" : "2027-05-20T00:00:00Z"
}
British spelling matters : subject.type for a company is "organisation", not "organization". The schema follows the W3C VC business profile.
Subject Shapes by Type
The required fields on subject and claims depend on credential_type:
business
user
agent_authorization
outcome_attestation
{
"credential_type" : "business" ,
"subject" : {
"id" : "biz_acmecorp" ,
"type" : "organisation" ,
"name" : "Acme Corp"
},
"claims" : {
"kyb_status" : "approved" ,
"verified_at" : "2026-05-20T00:00:00Z" ,
"jurisdiction" : "US-DE"
}
}
Required claims: kyb_status (one of pending | approved | declined | manual_review). {
"credential_type" : "user" ,
"subject" : {
"id" : "user_jane_doe_001" ,
"type" : "person" ,
"name" : { "first" : "Jane" , "last" : "Doe" }
},
"claims" : {
"kyc_status" : "approved" ,
"trust_level" : "idv_verified" ,
"verified_at" : "2026-05-20T00:00:00Z"
}
}
Required claims: kyc_status, trust_level (one of self_attested | liveness_verified | idv_verified | enterprise_verified). {
"credential_type" : "agent_authorization" ,
"subject" : {
"id" : "agent_widgetcorp_assistant" ,
"type" : "agent" ,
"name" : "WidgetCorp Customer Assistant"
},
"claims" : {
"delegated_by_subject_id" : "usr_01HXYZ..." ,
"role" : [ "payment_agent" ],
"permissions" : [
{
"resource_type" : "wallet" ,
"resource_id" : "*" ,
"actions" : [ "payment_authorize" , "checkout" ],
"conditions" : [
{ "field" : "transaction_amount" , "op" : "lte" , "value" : 50000 },
{ "field" : "transaction_currency" , "op" : "eq" , "value" : "usd" }
]
}
],
"spend_limit" : { "amount" : 50000 , "currency" : "usd" , "period" : "daily" },
"max_idle_duration" : "PT15M" ,
"human_present" : true
}
}
Required claims: permissions (at least one entry with resource_type, resource_id, and actions[]). delegated_by_subject_id is required when any permission includes resource_type: "wallet" — it must be the subject.id of the verified user delegating this agent. {
"credential_type" : "outcome_attestation" ,
"attestation_type" : "transaction_attested" ,
"subject" : {
"id" : "txn_9982311" ,
"type" : "transaction"
},
"claims" : {
"transaction_id" : "txn_9982311" ,
"amount" : 25000 ,
"currency" : "usd" ,
"approved_by_user" : true ,
"attested_at" : "2026-05-20T00:00:00Z"
}
}
attestation_type is required — picks the claims sub-schema. Available types:attestation_typeUse case transaction_attestedRecords that a transaction was approved and attested at a specific point in time stripe_payment_authorizedAttests a Stripe PaymentIntent was authorized via a Beltic credential — includes payment_intent_id and verification_id for AML audit chain kyb_outcomeKYB verdict (approved / declined / manual_review) with tier and reason codes document_validationDocument verification result with fraud signals and extracted fields identity_verificationIDV session outcome — provider, liveness and document check results, trust level email_riskEmail risk score (0–1) with signals from a risk vendor
Step 2: Send the Request
curl -X POST https://api.beltic.com/v1/credentials \
-H "X-Api-Key: $BELTIC_API_KEY " \
-H "Content-Type: application/json" \
-d '{
"credential_type": "business",
"subject": {
"id": "biz_widgetcorp",
"type": "organisation",
"name": "WidgetCorp Ltd"
},
"claims": {
"kyb_status": "approved",
"verified_at": "2026-05-20T00:00:00Z",
"jurisdiction": "US-DE"
}
}'
Step 3: Inspect the Response
{
"id" : "cred_01HQ7P4M6..." ,
"credential_id" : "cred_01HQ7P4M6..." ,
"credential_type" : "business" ,
"subject" : {
"id" : "biz_widgetcorp" ,
"type" : "organisation" ,
"name" : "WidgetCorp Ltd"
},
"claims" : {
"kyb_status" : "approved" ,
"verified_at" : "2026-05-20T00:00:00Z" ,
"jurisdiction" : "US-DE"
},
"issuer_did" : "did:web:beltic.com" ,
"kid" : "K8L9..." ,
"alg" : "ES256" ,
"proof_format" : "jwt_vc" ,
"signed_payload" : "eyJhbGciOiJFUzI1NiIs..." ,
"status" : "active" ,
"status_list_index" : 4287 ,
"issued_at" : "2026-05-21T10:30:00Z" ,
"expires_at" : "2026-08-19T10:30:00Z" ,
"created_at" : "2026-05-21T10:30:00Z" ,
"updated_at" : "2026-05-21T10:30:00Z"
}
Fields worth knowing:
signed_payload — the JWT-VC. This is the artifact you hand to verifiers; it carries all the claims plus the signature.
credential_id — the globally unique identifier. Stable across the credential’s lifetime; use it in verify requests via by_credential_id.
status_list_index — the bit slot reserved in your org’s Status List 2021 bitstring. You don’t need this for verification, but it’s useful for audit.
expires_at — defaults to 90 days from issuance. Override it by passing expires_at (ISO 8601) in the request body. Passing a date in the past returns 422.
Idempotency
Issue requests support Stripe-style idempotency keys via the Idempotency-Key header. Retrying the same request with the same key within 24 hours returns the original response without minting a new credential. Concurrent retries with the same key return 409 conflict.
curl -X POST https://api.beltic.com/v1/credentials \
-H "X-Api-Key: $BELTIC_API_KEY " \
-H "Idempotency-Key: $( uuidgen )" \
-H "Content-Type: application/json" \
-d @payload.json
Error Codes
HTTP Code Cause 400 validation_failedRequest body failed Zod validation (missing field, wrong enum value, British vs American spelling) 401 unauthorizedAPI key missing, invalid, or environment-mismatched 403 forbiddenAPI key lacks credentials:write 409 idempotency_conflictConcurrent request with the same idempotency key 422 unprocessable_entityPayload structurally valid but semantically rejected (e.g., expires_at in the past)
Next Steps
Verify a Credential Validate the JWT-VC you just received.
Batch Issue Issue thousands of credentials in a single async job.