API Endpoints

SIWA Server — HTTP endpoints for the full authentication flow

Live — These endpoints are running on this website right now. You can call them directly from your terminal or agent to try the full SIWA authentication flow. No setup required.

Overview

The SIWA server implements a challenge-response authentication flow. An agent requests a nonce, signs a structured message, and submits it for verification. On success, the server returns a verification receipt. The agent then uses ERC-8128 HTTP Message Signatures with the receipt for subsequent authenticated requests.

Base URL

https://siwa.id

All endpoints accept and return application/json. CORS is enabled for all origins. You can also run a local instance with the siwa-testing package.

Networks

The server supports both testnet and mainnet. Use the appropriate endpoint prefix for your target network:

NetworkChain IDEndpoint Prefix
Base Sepolia (testnet)84532/api/siwa/*
Base (mainnet)8453/api/siwa/mainnet/*

For example, to authenticate on mainnet, use /api/siwa/mainnet/nonce and /api/siwa/mainnet/verify.

Address Formats

SIWA follows EVM address standards:

  • Wallet addresses — Must be EIP-55 checksummed (mixed-case), e.g. 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0
  • Agent Registry — Uses CAIP-10 format: eip155:{chainId}:{address}
  • Chain IDs — Numeric identifiers per EIP-155 (e.g. 8453 for Base, 84532 for Base Sepolia)

Authentication Flow

1.
AgentPOST/api/siwa/nonceServer returns nonce + timestamps
2.
Agent builds SIWA message and signs via EIP-191
3.
AgentPOST/api/siwa/verifyServer returns verification receipt
4.
Agent usesERC-8128 signature + receiptfor protected endpoints

Authentication Endpoints

Request Nonce

POST/api/siwa/nonce
POST/api/siwa/mainnet/nonce

Request a cryptographic nonce to include in the SIWA message. Nonces are single-use and expire after 5 minutes. Use /api/siwa/nonce for Base Sepolia (testnet) or /api/siwa/mainnet/nonce for Base (mainnet).

Request Body

FieldTypeRequiredDescription
addressstringYesAgent wallet address (EIP-55 checksummed, mixed-case)
agentIdnumberNoERC-8004 agent token ID (numeric)
agentRegistrystringNoCAIP-10 registry reference (eip155:{chainId}:{address})

The agentRegistry uses CAIP-10 format: eip155:{chainId}:{contractAddress}. For Base Sepolia: eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e. For Base mainnet: eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432.

Response 200

{
"nonce": "a1b2c3d4e5f6g7h8",
"nonceToken": "eyJub25jZSI6ImExYjJj...",
"issuedAt": "2026-02-05T12:00:00.000Z",
"expirationTime": "2026-02-05T12:05:00.000Z",
"domain": "siwa.id",
"uri": "https://siwa.id/api/siwa/verify",
"chainId": 84532
}

The nonceToken is an HMAC-signed token that binds the nonce to the server. You must pass it back in the verify request.

Error 400

{ "error": "Missing address" }

Error 403

{
"status": "not_registered",
"code": "NOT_REGISTERED",
"error": "Agent is not registered onchain"
}

Verify Signature

POST/api/siwa/verify
POST/api/siwa/mainnet/verify

Submit the signed SIWA message for verification. On success, the server validates the signature, checks nonce freshness, verifies domain binding, and returns a verification receipt (30 minute default expiry). Use /api/siwa/verify for Base Sepolia (testnet) or /api/siwa/mainnet/verify for Base (mainnet).

Request Body

FieldTypeRequiredDescription
messagestringYesFull SIWA message (plaintext)
signaturestringYesEIP-191 signature (hex, 0x-prefixed)
nonceTokenstringYesHMAC nonce token from the /nonce response

Response 200

{
"status": "authenticated",
"receipt": "eyJhZGRyZXNzIjoiMHg3NDJk...",
"receiptExpiresAt": "2026-02-05T12:30:00.000Z",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"agentId": 42,
"agentRegistry": "eip155:84532:0x8004A818...",
"signerType": "eoa",
"verified": "onchain"
}

The verified field indicates the verification mode: offline (signature only) or onchain (signature + ERC-721 ownership check via RPC).

Error 400

{
"status": "rejected",
"code": "VERIFICATION_FAILED",
"error": "Missing message or signature"
}
{
"status": "rejected",
"code": "INVALID_NONCE",
"error": "Missing nonceToken"
}

Error 401

{
"status": "rejected",
"code": "VERIFICATION_FAILED",
"error": "Signature mismatch"
}

Other possible errors: INVALID_NONCE, MESSAGE_EXPIRED, DOMAIN_MISMATCH.

Error 403

{
"status": "not_registered",
"code": "NOT_REGISTERED",
"error": "Signer does not own agent NFT"
}

Protected Endpoints

These endpoints require ERC-8128 HTTP Message Signatures with a valid verification receipt. Get a receipt by completing the nonce + verify flow above, then sign requests using signAuthenticatedRequest().

X-SIWA-Receipt: <receipt>
Signature: eth=:<base64-signature>:
Signature-Input: eth=(...);keyid="erc8128:<chainId>:<address>";...
Content-Digest: sha-256=:<base64-hash>: (for POST requests)

Test Auth

GET/api/protectedERC-8128 signed

Simple endpoint to verify your session is working. Returns the authenticated agent's identity.

Response 200

{
"message": "Hello Agent #42!",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"agentId": 42,
"timestamp": "2026-02-05T12:01:00.000Z"
}

Error 401

{ "error": "Unauthorized" }

Agent Action

POST/api/agent-actionERC-8128 signed

Submit an action as an authenticated agent. The server echoes the request and identifies the agent.

Request Body

FieldTypeRequiredDescription
actionstringYesAction identifier
dataobjectNoArbitrary action payload

Response 200

{
"received": {
"action": "transfer",
"data": { "to": "0xabc...", "amount": "1.0" }
},
"processedBy": "siwa-server",
"agent": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"agentId": 42
},
"timestamp": "2026-02-05T12:02:00.000Z"
}

Error 401

{ "error": "Unauthorized" }

x402 Paid Endpoints

These endpoints require both SIWA authentication and an x402 payment. The server enforces a two-gate flow: first SIWA auth (returns 401 if invalid), then x402 payment (returns 402 if missing or invalid). Payments are settled on-chain via a Coinbase CDP facilitator. For server-side setup, middleware integration, agent-side handling, and session configuration, see the x402 Payments documentation.

Payment Flow

1.
Agent sends request withERC-8128 + receiptSIWA gate passes
2.
NoPayment-SignatureheaderServer returns402withPayment-Requiredheader
3.
Agent signs payment and retries withPayment-Signatureheader
4.
Server verifies + settles via facilitatorReturns data +Payment-Responseheader with txHash

x402 Headers

HeaderDirectionDescription
Payment-RequiredResponse (402)Base64 JSON with accepted payment options and resource info
Payment-SignatureRequestBase64 JSON with signed payment payload
Payment-ResponseResponse (200)Base64 JSON with settlement details (txHash, amount, network)

The Payment-Required header is base64-encoded JSON containing the accepts array (payment options) and resource info. Decode it to build your payment:

{
"accepts": [{
"scheme": "exact",
"network": "eip155:84532",
"amount": "10000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0xffddB7C78D466f7d55C879c56EB8BF4c66400ab5",
"maxTimeoutSeconds": 60
}],
"resource": {
"url": "/api/x402/weather",
"description": "Premium weather data"
}
}

Payment Configuration

FieldValue
NetworkBase Sepolia (eip155:84532)
AssetUSDC (0x036CbD53842c5426634e7929541eC2318f3dCF7e)
Amount0.01 USDC (10000 units, 6 decimals)
Pay To0xffddB7C78D466f7d55C879c56EB8BF4c66400ab5
Facilitatorhttps://api.cdp.coinbase.com/platform/v2/x402

Weather (Per-Request)

GET/api/x402/weatherERC-8128 signedx402 paid

Premium weather data endpoint. Requires a new payment on every request (no session caching). This demonstrates the basic x402 per-request payment model.

Response 200

{
"agent": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"agentId": 42
},
"payment": {
"txHash": "0xabc123...",
"amount": "10000",
"network": "eip155:84532"
},
"weather": {
"location": "Base Sepolia Testnet City",
"temperature": 21,
"unit": "celsius",
"conditions": "Sunny with a chance of blocks",
"humidity": 55,
"wind": { "speed": 12, "direction": "NE", "unit": "km/h" },
"forecast": [
{ "day": "Tomorrow", "high": 23, "low": 15, "conditions": "Partly cloudy" },
{ "day": "Day after", "high": 19, "low": 12, "conditions": "Light rain" }
]
},
"timestamp": "2026-02-17T12:00:00.000Z"
}

The response includes a Payment-Response header with the on-chain settlement details.

Error 401

{ "error": "Missing or invalid SIWA receipt" }

Error 402

Returned when no Payment-Signature header is present, or when payment verification/settlement fails:

{
"error": "Payment required",
"accepts": [{ "scheme": "exact", "network": "eip155:84532", "amount": "10000", "..." }],
"resource": { "url": "/api/x402/weather", "description": "Premium weather data" }
}

Analytics (Pay-Once Session)

GET/api/x402/analyticsERC-8128 signedx402 paid
POST/api/x402/analyticsERC-8128 signedx402 paid

Agent analytics dashboard with pay-once session mode. The first request requires payment. After payment succeeds, a session is created for the agent and resource. Subsequent requests within the session TTL (1 hour) skip payment automatically. Both GET and POST share the same session.

GET Response 200

{
"agent": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"agentId": 42
},
"sessionActive": false,
"payment": {
"txHash": "0xabc123...",
"amount": "10000",
"network": "eip155:84532"
},
"analytics": {
"totalRequests": 7421,
"uniqueAgents": 312,
"avgResponseTime": "45.2ms",
"topEndpoints": [
{ "path": "/api/protected", "calls": 4821 },
{ "path": "/api/agent-action", "calls": 3102 },
{ "path": "/api/x402/weather", "calls": 1547 }
],
"period": "last_24h"
},
"timestamp": "2026-02-17T12:00:00.000Z"
}

When sessionActive is true, the request was served from an existing session (no new payment). When false, a fresh payment was processed and payment contains the settlement details.

POST Request Body

FieldTypeRequiredDescription
eventstringYesEvent name
dataobjectNoArbitrary event payload

POST Response 200

{
"agent": {
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"agentId": 42
},
"sessionActive": true,
"received": { "event": "page_view", "data": { "path": "/dashboard" } },
"status": "event_recorded",
"timestamp": "2026-02-17T12:01:00.000Z"
}

Session Behavior

RequestSessionPaymentsessionActive
1st requestNoneRequired (402 then retry with payment)false
2nd request (within TTL)ActiveSkippedtrue
After TTL expiresExpiredRequired againfalse

Sessions are isolated by (address, resource) — different agents and different routes maintain separate sessions.

Try It

Run the full SIWA auth flow from your terminal. These endpoints are live — you can call them right now.

Full Flow (curl)

Step 1 — Request a nonce:

curl -s -X POST https://siwa.id/api/siwa/nonce \
-H "Content-Type: application/json" \
-d '{
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"agentId": 42,
"agentRegistry": "eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e"
}'

Step 2 — Build and sign the SIWA message using the nonce from step 1. Save the nonceToken for step 3. Use the SDK or any EIP-191 signer:

import { signSIWAMessage } from '@buildersgarden/siwa';
const { message, signature } = await signSIWAMessage({
domain: 'siwa.id',
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
statement: 'Authenticate as a registered ERC-8004 agent.',
uri: 'https://siwa.id/api/siwa/verify',
agentId: 42,
agentRegistry: 'eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e',
chainId: 84532,
nonce: '<nonce-from-step-1>',
issuedAt: '<issuedAt-from-step-1>',
expirationTime: '<expirationTime-from-step-1>'
});

Step 3 — Submit message + signature + nonceToken for verification:

curl -s -X POST https://siwa.id/api/siwa/verify \
-H "Content-Type: application/json" \
-d '{
"message": "<siwa-message-from-step-2>",
"signature": "<signature-from-step-2>",
"nonceToken": "<nonceToken-from-step-1>"
}'

Step 4 — Use the receipt with ERC-8128 signed requests. In code, this is one function call:

import { signAuthenticatedRequest } from '@buildersgarden/siwa/erc8128';
const req = new Request('https://siwa.id/api/protected', { method: 'GET' });
const signedReq = await signAuthenticatedRequest(req, receipt, signer, 84532);
const res = await fetch(signedReq);

SIWA Message Format

{domain} wants you to sign in with your Agent account:
{address}

{statement}

URI: {uri}
Version: 1
Agent ID: {agentId}
Agent Registry: {agentRegistry}
Chain ID: {chainId}
Nonce: {nonce}
Issued At: {issuedAt}
Expiration Time: {expirationTime}