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 payments
  • credits:consume — consume credits
  • credits:purchase — purchase credits
  • admin:read — read admin data
  • admin: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

json
{
  "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

FieldTypeRequiredDescription
paymentRequiredobjectyesThe full x402 v2 payload from the 402 response
paymentRequired.x402VersionintegeryesMust be 2
paymentRequired.acceptsarrayyesAt least one payment option
paymentRequired.accepts[].schemestringyesPayment scheme, typically "exact"
paymentRequired.accepts[].networkstringyesCAIP-2 network ID, e.g. "eip155:8453" (Base)
paymentRequired.accepts[].assetstringyesToken contract address (USDC on Base: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913)
paymentRequired.accepts[].amountstringyesAmount in smallest unit (USDC has 6 decimals, so "1500000" = $1.50)
paymentRequired.accepts[].payTostringyesRecipient address (service provider)
paymentRequired.accepts[].maxTimeoutSecondsintegernoMax time for payment settlement. Default: 60
paymentRequired.accepts[].extraobjectnoAdditional payment parameters
paymentRequired.resourceobjectnoDescribes the resource being accessed
paymentRequired.resource.urlstringnoURL of the resource
paymentRequired.resource.descriptionstringnoHuman-readable description
paymentRequired.resource.mimeTypestringnoExpected response MIME type
paymentRequired.errorstringnoError message from the service
paymentRequired.extensionsobjectnoProtocol extensions
organizationIdstring (UUID)yesYour organization ID
entityIdstring (UUID)yesThe user or swarm making the payment
entityTypestringyes"user" or "swarm"
providerIdstringyesIdentifier for the AI service (e.g. domain name)
metadataobjectnoArbitrary key-value metadata for audit

Response — Approved (HTTP 200)

json
{
  "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)

FieldTypeDescription
approvedbooleanAlways true for approved payments
signature.vintegerECDSA recovery ID (27 or 28)
signature.rstringECDSA R component (0x-prefixed, 32 bytes hex)
signature.sstringECDSA S component (0x-prefixed, 32 bytes hex)
authorization.fromstringViatika platform wallet address (payer)
authorization.tostringService provider address (payee)
authorization.valuestringAmount in token smallest units
authorization.validAfterstringUnix timestamp; signature valid after this time (usually "0")
authorization.validBeforestringUnix timestamp; signature expires after this time (~5 min window)
authorization.noncestringRandom 32-byte nonce (0x-prefixed hex) for replay protection
transactionIdstring (UUID)Viatika's internal transaction ID for audit
creditRemaininginteger or nullRemaining credits after this payment

Response — Denied by Policy (HTTP 403)

json
{
  "approved": false,
  "denialReasons": [
    {
      "category": "budget-exceeded",
      "message": "Daily budget of 5000 credits exceeded"
    }
  ],
  "message": "Payment denied by policy"
}

Response — Requires Manual Approval (HTTP 202)

json
{
  "approved": false,
  "denialReasons": [
    {
      "category": "not-whitelisted",
      "message": "Provider not on whitelist"
    }
  ],
  "approvalId": "approval-uuid",
  "message": "Payment requires manual approval"
}

curl Example

bash
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_...
ParameterLocationTypeRequiredDescription
orgIdpathstring (UUID)yesOrganization ID

Response (HTTP 200)

json
{
  "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"
}
FieldTypeDescription
idstring (UUID)Credit account ID
organizationIdstring (UUID)Organization this account belongs to
balanceintegerCurrent balance in credits (1 credit = $0.001)
lowBalanceThresholdinteger or nullAlert threshold in credits
createdAtstring (ISO 8601)Account creation time
updatedAtstring (ISO 8601)Last update time

curl Example

bash
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_...
ParameterLocationTypeRequiredDefaultDescription
orgIdpathstring (UUID)yesOrganization ID
limitqueryintegerno50Max results per page (1-100)
offsetqueryintegerno0Number of results to skip

Response (HTTP 200)

json
[
  {
    "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"
  }
]
FieldTypeDescription
idstring (UUID)Transaction ID
accountIdstring (UUID)Credit account ID
amountintegerPositive = credit added, negative = credit consumed
transactionTypestring"recurring_purchase" | "ad_hoc_purchase" | "consumption" | "refund"
descriptionstringHuman-readable description
stripePaymentIdstring or nullStripe payment intent ID (for purchases)
createdAtstring (ISO 8601)Transaction time

curl Example

bash
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_...
json
{
  "amount": 1500,
  "description": "GPT-4 API call",
  "providerId": "openai-gpt4",
  "entityId": "660e8400-e29b-41d4-a716-446655440001",
  "entityType": "individual"
}
FieldTypeRequiredDescription
amountintegeryesCredits to consume (positive integer)
descriptionstringyesWhat the credits are for
providerIdstringnoService provider identifier
entityIdstring (UUID)noOverride entity from token
entityTypestringno"individual" | "team" | "swarm"

Response (HTTP 200)

json
{
  "transactionId": "tx-uuid",
  "remainingBalance": 7000
}

curl Example

bash
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>
json
{
  "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
}
FieldTypeRequiredDescription
organizationIdstring (UUID)yesOrganization to create token for
entityIdstring (UUID)yesEntity (user/swarm) the token represents
entityTypestringyes"individual" | "team" | "swarm"
namestringyesHuman-readable token name
descriptionstringnoDescription of token purpose
scopesarray of stringsyesPermission scopes
expiresInDaysintegernoDays until expiration (omit for no expiry)

Response (HTTP 201)

json
{
  "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

bash
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)

json
[
  {
    "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

bash
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

bash
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)

json
{
  "status": "ok"
}

Error Handling

HTTP Status Codes

CodeMeaningAction
200SuccessProcess response
201CreatedResource created successfully
202AcceptedPayment requires manual approval; check approvalId
204No ContentDeletion successful
400Bad RequestFix request body/parameters
401UnauthorizedCheck API token
403ForbiddenPayment denied by policy; check denialReasons
404Not FoundResource doesn't exist
429Too Many RequestsRate limited; back off and retry
500Internal Server ErrorRetry with exponential backoff

Parsing a 402 from an AI Service

When you receive 402 Payment Required from an AI service, extract the x402 payload:

  1. Check the response body — most services return x402 JSON directly
  2. Check Payment-Required header — some services base64-encode it in a header

The payload must have x402Version: 2 and at least one entry in accepts.

python
# 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:

CategoryMeaningSuggested Action
budget-exceededEntity's budget limit reachedReport to user, wait for budget reset
not-whitelistedProvider not on whitelistRequest whitelist addition
rate-limit-exceededToo many payments in time windowWait and retry
amount-exceededSingle payment exceeds limitReport to user
provider-blockedProvider is blacklistedDo not retry

Retry Strategy

For 500 errors or network failures to Viatika:

  1. Retry once immediately
  2. If still failing, retry with 2s backoff
  3. 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.