REST API Reference
Base URL: https://api.viatika.ai/v1
Authentication
All requests require a Bearer token in the Authorization header:
Authorization: Bearer vt_live_abc123456789...Token format:
vt_live_abc123456789defghijklmnopqrstuvwxyz...
│ │ │
│ │ └─ Base64-encoded random bytes (32 bytes)
│ └────── Environment: live (production) | test (sandbox)
└────────── Prefix: vt (viatika token)Token scopes control what operations are allowed:
x402:sign— sign x402 paymentscredits:consume— consume creditscredits:purchase— purchase creditsadmin:read— read admin dataadmin:write— modify policies/budgets
Rate limits: 60 requests/minute, 3,600 requests/hour per token.
POST /v1/x402/sign-payment
Sign an x402 payment. This is the core endpoint. Send the 402 response payload from an AI service, and Viatika evaluates policy, checks budget, signs the payment, and returns the signature.
Latency: ~150ms typical (policy <10ms, signing ~100ms, DB ~40ms)
Request
POST /v1/x402/sign-payment
Content-Type: application/json
Authorization: Bearer vt_live_...Request Body Schema
{
"paymentRequired": {
"x402Version": 2,
"error": "",
"resource": {
"url": "string",
"description": "string",
"mimeType": "string"
},
"accepts": [
{
"scheme": "string",
"network": "string",
"asset": "string",
"amount": "string",
"payTo": "string",
"maxTimeoutSeconds": 0,
"extra": {}
}
],
"extensions": {}
},
"organizationId": "string (UUID, required)",
"entityId": "string (UUID, required)",
"entityType": "string (required: 'user' | 'swarm')",
"providerId": "string (required: service identifier)",
"metadata": {}
}Field Details
| Field | Type | Required | Description |
|---|---|---|---|
paymentRequired | object | yes | The full x402 v2 payload from the 402 response |
paymentRequired.x402Version | integer | yes | Must be 2 |
paymentRequired.accepts | array | yes | At least one payment option |
paymentRequired.accepts[].scheme | string | yes | Payment scheme, typically "exact" |
paymentRequired.accepts[].network | string | yes | CAIP-2 network ID, e.g. "eip155:8453" (Base) |
paymentRequired.accepts[].asset | string | yes | Token contract address (USDC on Base: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) |
paymentRequired.accepts[].amount | string | yes | Amount in smallest unit (USDC has 6 decimals, so "1500000" = $1.50) |
paymentRequired.accepts[].payTo | string | yes | Recipient address (service provider) |
paymentRequired.accepts[].maxTimeoutSeconds | integer | no | Max time for payment settlement. Default: 60 |
paymentRequired.accepts[].extra | object | no | Additional payment parameters |
paymentRequired.resource | object | no | Describes the resource being accessed |
paymentRequired.resource.url | string | no | URL of the resource |
paymentRequired.resource.description | string | no | Human-readable description |
paymentRequired.resource.mimeType | string | no | Expected response MIME type |
paymentRequired.error | string | no | Error message from the service |
paymentRequired.extensions | object | no | Protocol extensions |
organizationId | string (UUID) | yes | Your organization ID |
entityId | string (UUID) | yes | The user or swarm making the payment |
entityType | string | yes | "user" or "swarm" |
providerId | string | yes | Identifier for the AI service (e.g. domain name) |
metadata | object | no | Arbitrary key-value metadata for audit |
Response — Approved (HTTP 200)
{
"approved": true,
"signature": {
"v": 28,
"r": "0x1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef1234567890",
"s": "0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"
},
"authorization": {
"from": "0xViatikaWalletAddress",
"to": "0xServiceProviderAddress",
"value": "1500000",
"validAfter": "0",
"validBefore": "1769917810",
"nonce": "0xRANDOM_32_BYTES_HEX"
},
"transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"creditRemaining": 8500
}Response Field Details (Approved)
| Field | Type | Description |
|---|---|---|
approved | boolean | Always true for approved payments |
signature.v | integer | ECDSA recovery ID (27 or 28) |
signature.r | string | ECDSA R component (0x-prefixed, 32 bytes hex) |
signature.s | string | ECDSA S component (0x-prefixed, 32 bytes hex) |
authorization.from | string | Viatika platform wallet address (payer) |
authorization.to | string | Service provider address (payee) |
authorization.value | string | Amount in token smallest units |
authorization.validAfter | string | Unix timestamp; signature valid after this time (usually "0") |
authorization.validBefore | string | Unix timestamp; signature expires after this time (~5 min window) |
authorization.nonce | string | Random 32-byte nonce (0x-prefixed hex) for replay protection |
transactionId | string (UUID) | Viatika's internal transaction ID for audit |
creditRemaining | integer or null | Remaining credits after this payment |
Response — Denied by Policy (HTTP 403)
{
"approved": false,
"denialReasons": [
{
"category": "budget-exceeded",
"message": "Daily budget of 5000 credits exceeded"
}
],
"message": "Payment denied by policy"
}Response — Requires Manual Approval (HTTP 202)
{
"approved": false,
"denialReasons": [
{
"category": "not-whitelisted",
"message": "Provider not on whitelist"
}
],
"approvalId": "approval-uuid",
"message": "Payment requires manual approval"
}curl Example
curl -X POST https://api.viatika.ai/v1/x402/sign-payment \
-H "Authorization: Bearer vt_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"paymentRequired": {
"x402Version": 2,
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "1500000",
"payTo": "0x1234567890abcdef1234567890abcdef12345678",
"maxTimeoutSeconds": 60
}
],
"resource": {
"url": "https://api.example.com/chat",
"description": "AI chat completion"
}
},
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"entityId": "660e8400-e29b-41d4-a716-446655440001",
"entityType": "user",
"providerId": "api.example.com"
}'GET /v1/accounts/{orgId}
Get the credit balance for an organization.
Request
GET /v1/accounts/{orgId}
Authorization: Bearer vt_live_...| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
orgId | path | string (UUID) | yes | Organization ID |
Response (HTTP 200)
{
"id": "account-uuid",
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"balance": 8500,
"lowBalanceThreshold": 1000,
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-15T12:30:00Z"
}| Field | Type | Description |
|---|---|---|
id | string (UUID) | Credit account ID |
organizationId | string (UUID) | Organization this account belongs to |
balance | integer | Current balance in credits (1 credit = $0.001) |
lowBalanceThreshold | integer or null | Alert threshold in credits |
createdAt | string (ISO 8601) | Account creation time |
updatedAt | string (ISO 8601) | Last update time |
curl Example
curl https://api.viatika.ai/v1/accounts/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer vt_live_abc123..."GET /v1/accounts/{orgId}/transactions
List credit transactions for an organization. Paginated.
Request
GET /v1/accounts/{orgId}/transactions?limit=50&offset=0
Authorization: Bearer vt_live_...| Parameter | Location | Type | Required | Default | Description |
|---|---|---|---|---|---|
orgId | path | string (UUID) | yes | — | Organization ID |
limit | query | integer | no | 50 | Max results per page (1-100) |
offset | query | integer | no | 0 | Number of results to skip |
Response (HTTP 200)
[
{
"id": "tx-uuid",
"accountId": "account-uuid",
"amount": -1500,
"transactionType": "consumption",
"description": "x402 payment to api.example.com",
"stripePaymentId": null,
"createdAt": "2025-01-15T12:30:00Z"
},
{
"id": "tx-uuid-2",
"accountId": "account-uuid",
"amount": 10000,
"transactionType": "ad_hoc_purchase",
"description": "Credit purchase",
"stripePaymentId": "pi_abc123",
"createdAt": "2025-01-15T10:00:00Z"
}
]| Field | Type | Description |
|---|---|---|
id | string (UUID) | Transaction ID |
accountId | string (UUID) | Credit account ID |
amount | integer | Positive = credit added, negative = credit consumed |
transactionType | string | "recurring_purchase" | "ad_hoc_purchase" | "consumption" | "refund" |
description | string | Human-readable description |
stripePaymentId | string or null | Stripe payment intent ID (for purchases) |
createdAt | string (ISO 8601) | Transaction time |
curl Example
curl "https://api.viatika.ai/v1/accounts/550e8400-e29b-41d4-a716-446655440000/transactions?limit=10" \
-H "Authorization: Bearer vt_live_abc123..."POST /v1/accounts/{orgId}/consume
Consume credits directly (used internally by the sign-payment flow, but available for custom integrations).
Request
POST /v1/accounts/{orgId}/consume
Content-Type: application/json
Authorization: Bearer vt_live_...{
"amount": 1500,
"description": "GPT-4 API call",
"providerId": "openai-gpt4",
"entityId": "660e8400-e29b-41d4-a716-446655440001",
"entityType": "individual"
}| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | yes | Credits to consume (positive integer) |
description | string | yes | What the credits are for |
providerId | string | no | Service provider identifier |
entityId | string (UUID) | no | Override entity from token |
entityType | string | no | "individual" | "team" | "swarm" |
Response (HTTP 200)
{
"transactionId": "tx-uuid",
"remainingBalance": 7000
}curl Example
curl -X POST https://api.viatika.ai/v1/accounts/550e8400-e29b-41d4-a716-446655440000/consume \
-H "Authorization: Bearer vt_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"amount": 1500,
"description": "GPT-4 API call",
"providerId": "openai-gpt4"
}'POST /v1/tokens
Create a new API token.
Request
POST /v1/tokens
Content-Type: application/json
Authorization: Bearer <session-token>{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"entityId": "660e8400-e29b-41d4-a716-446655440001",
"entityType": "individual",
"name": "My Agent Key",
"description": "API key for production agent",
"scopes": ["x402:sign", "credits:consume"],
"expiresInDays": 90
}| Field | Type | Required | Description |
|---|---|---|---|
organizationId | string (UUID) | yes | Organization to create token for |
entityId | string (UUID) | yes | Entity (user/swarm) the token represents |
entityType | string | yes | "individual" | "team" | "swarm" |
name | string | yes | Human-readable token name |
description | string | no | Description of token purpose |
scopes | array of strings | yes | Permission scopes |
expiresInDays | integer | no | Days until expiration (omit for no expiry) |
Response (HTTP 201)
{
"token": "vt_live_abc123456789defghijklmnopqrstuvwxyz123456",
"tokenId": "770e8400-e29b-41d4-a716-446655440003",
"tokenPrefix": "vt_live_abc12345",
"message": "⚠️ Save this token now - it will never be shown again!"
}Important: The full token is only returned once. Store it securely.
curl Example
curl -X POST https://api.viatika.ai/v1/tokens \
-H "Authorization: Bearer <session-token>" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"entityId": "660e8400-e29b-41d4-a716-446655440001",
"entityType": "individual",
"name": "My Agent Key",
"scopes": ["x402:sign", "credits:consume"],
"expiresInDays": 90
}'GET /v1/tokens
List tokens for an organization.
Request
GET /v1/tokens?organizationId={orgId}
Authorization: Bearer <session-token>Response (HTTP 200)
[
{
"tokenId": "770e8400-e29b-41d4-a716-446655440003",
"tokenPrefix": "vt_live_abc12345",
"name": "My Agent Key",
"description": "API key for production agent",
"entityId": "660e8400-e29b-41d4-a716-446655440001",
"entityType": "individual",
"scopes": ["x402:sign", "credits:consume"],
"status": "active",
"createdAt": "2025-01-15T10:00:00Z",
"lastUsedAt": "2025-01-15T12:30:00Z",
"totalRequests": 1234
}
]curl Example
curl "https://api.viatika.ai/v1/tokens?organizationId=550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer <session-token>"DELETE /v1/tokens/{tokenId}
Revoke a token immediately.
Request
DELETE /v1/tokens/{tokenId}
Authorization: Bearer <session-token>Response (HTTP 204)
No body.
curl Example
curl -X DELETE https://api.viatika.ai/v1/tokens/770e8400-e29b-41d4-a716-446655440003 \
-H "Authorization: Bearer <session-token>"GET /v1/health
Health check. No authentication required.
Response (HTTP 200)
{
"status": "ok"
}Error Handling
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
200 | Success | Process response |
201 | Created | Resource created successfully |
202 | Accepted | Payment requires manual approval; check approvalId |
204 | No Content | Deletion successful |
400 | Bad Request | Fix request body/parameters |
401 | Unauthorized | Check API token |
403 | Forbidden | Payment denied by policy; check denialReasons |
404 | Not Found | Resource doesn't exist |
429 | Too Many Requests | Rate limited; back off and retry |
500 | Internal Server Error | Retry with exponential backoff |
Parsing a 402 from an AI Service
When you receive 402 Payment Required from an AI service, extract the x402 payload:
- Check the response body — most services return x402 JSON directly
- Check
Payment-Requiredheader — some services base64-encode it in a header
The payload must have x402Version: 2 and at least one entry in accepts.
# Python example
import requests, json
resp = requests.get("https://api.example.com/resource")
if resp.status_code == 402:
# Try body first
x402_payload = resp.json()
# Verify it's valid x402
assert x402_payload.get("x402Version") == 2
assert len(x402_payload.get("accepts", [])) > 0
# Send to Viatika for signing
sign_resp = requests.post(
f"{VIATIKA_API_URL}/v1/x402/sign-payment",
headers={"Authorization": f"Bearer {VIATIKA_API_TOKEN}"},
json={
"paymentRequired": x402_payload,
"organizationId": ORG_ID,
"entityId": ENTITY_ID,
"entityType": "user",
"providerId": "api.example.com"
}
)Policy Denial Handling
When approved: false, the denialReasons array tells you why:
| Category | Meaning | Suggested Action |
|---|---|---|
budget-exceeded | Entity's budget limit reached | Report to user, wait for budget reset |
not-whitelisted | Provider not on whitelist | Request whitelist addition |
rate-limit-exceeded | Too many payments in time window | Wait and retry |
amount-exceeded | Single payment exceeds limit | Report to user |
provider-blocked | Provider is blacklisted | Do not retry |
Retry Strategy
For 500 errors or network failures to Viatika:
- Retry once immediately
- If still failing, retry with 2s backoff
- After 3 failures, report the error
Do not retry 403 (policy denial) — the policy decision is final for the current request.
For 202 (pending approval), poll the approval status or wait for notification.