Base URL: http://localhost:4000 (default; override with PORT)
Interactive docs: Swagger UI · OpenAPI JSON
🎯 27 total endpoints across Health, Auth, Federation, Accounts, Payments, Analytics, Tips, Turrets, Webhooks, AI Parsing, and Scheduled Transactions.
Most JSON endpoints use one of these shapes:
Success (typical)
{ "success": true, "data": { } }Success with message
{ "success": true, "data": { }, "message": "..." }Error (standardized — #169)
All API errors now follow a canonical shape:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message",
"details": { ... }
}
}Note: Legacy
{ "error": "message" }shapes are being migrated. New code should expect the canonical shape above.
Some endpoints (health, federation, auth challenge, webhooks list) return a flat object without the success / data wrapper. Each route below shows the actual response shape.
Authentication: Account detail routes require a JWT from SEP-0010 auth. Send:
Authorization: Bearer <token>
All errors returned by the API use a machine-readable error code. The canonical registry is at shared/errorCodes.js.
| Code | HTTP | Description |
|---|---|---|
AUTH_MISSING_TOKEN |
401 | Authentication token is required. |
AUTH_EXPIRED_TOKEN |
401 | Token has expired. Please re-authenticate. |
AUTH_INVALID_TOKEN |
401 | Token is invalid or malformed. |
AUTH_MISSING_HEADER |
401 | Missing or invalid Authorization header. |
AUTH_FORBIDDEN |
403 | You do not have permission to access this resource. |
AUTH_CHALLENGE_FAILED |
401 | SEP-0010 challenge verification failed. |
| Code | HTTP | Description |
|---|---|---|
VAL_INVALID_PUBLIC_KEY |
400 | Invalid Stellar public key format. |
VAL_INVALID_AMOUNT |
400 | Amount must be a positive number. |
VAL_MISSING_FIELD |
400 | Required field is missing. |
VAL_INVALID_JSON |
400 | Request body contains invalid JSON. |
VAL_BODY_TOO_LARGE |
413 | Request body exceeds the maximum allowed size. |
VAL_CONTENT_TYPE |
415 | Content-Type must be application/json. |
VAL_INVALID_USERNAME |
400 | Username must be 3–20 alphanumeric characters. |
VAL_INVALID_STELLAR_ADDRESS |
400 | Invalid Stellar address format. |
VAL_INVALID_URL |
400 | Invalid URL format. |
VAL_INVALID_DATE |
400 | Invalid ISO 8601 date format. |
VAL_MEMO_TOO_LONG |
400 | Memo exceeds 28 bytes. |
VAL_WEAK_SECRET |
400 | Secret must be at least 8 characters. |
VAL_INVALID_LIMIT |
400 | Limit must be a positive integer. |
VAL_INVALID_FEDERATION_TYPE |
400 | Federation type must be 'name' or 'id'. |
| Code | HTTP | Description |
|---|---|---|
RES_NOT_FOUND |
404 | The requested resource was not found. |
RES_ACCOUNT_NOT_FOUND |
404 | Stellar account not found. |
RES_CONFLICT |
409 | Resource already exists. |
RES_USERNAME_CONFLICT |
409 | Username already registered. |
RES_PUBLIC_KEY_CONFLICT |
409 | Public key already registered. |
RES_GONE |
410 | Resource no longer available. |
RES_ROUTE_NOT_FOUND |
404 | Route not found. |
| Code | HTTP | Description |
|---|---|---|
RATE_LIMITED_GLOBAL |
429 | Too many requests. Try again later. |
RATE_LIMITED_SENSITIVE |
429 | Too many requests to sensitive routes. |
RATE_LIMITED_USER |
429 | Too many requests from this account. |
Mapped from the Soroban contract's numeric ContractError codes (1–17).
| Code | HTTP | Contract Code | Description |
|---|---|---|---|
CONTRACT_ALREADY_INITIALIZED |
409 | 1 | Contract already initialized. |
CONTRACT_UNAUTHORIZED |
403 | 2 | Not authorized for this action. |
CONTRACT_NON_POSITIVE_AMOUNT |
400 | 3 | Amount must be strictly positive. |
CONTRACT_RELEASE_LEDGER_IN_PAST |
400 | 4 | Release ledger must be in the future. |
CONTRACT_NOT_FOUND |
404 | 5 | Contract resource not found. |
CONTRACT_INVALID_STATE |
409 | 6 | Invalid state for this operation. |
CONTRACT_OVERFLOW |
500 | 7 | Arithmetic overflow. |
CONTRACT_INVALID_THRESHOLD |
400 | 8 | Signers/threshold mismatch. |
CONTRACT_LENGTH_MISMATCH |
400 | 9 | Array length mismatch. |
CONTRACT_ALREADY_SIGNED |
409 | 10 | Already approved this proposal. |
CONTRACT_INSUFFICIENT_FUNDS |
400 | 11 | Insufficient deposited funds. |
CONTRACT_PAUSED |
503 | 12 | Contract is paused. |
CONTRACT_SELF_TRANSFER |
400 | 13 | Cannot transfer to yourself. |
CONTRACT_BATCH_TOO_LARGE |
400 | 14 | Batch size exceeds maximum. |
CONTRACT_DUPLICATE_SIGNER |
400 | 15 | Duplicate signer detected. |
CONTRACT_PROPOSAL_EXPIRED |
410 | 16 | Proposal has expired. |
CONTRACT_TRANSFER_FAILED |
502 | 17 | Token transfer verification failed. |
| Code | HTTP | Description |
|---|---|---|
PAY_BUILD_FAILED |
500 | Failed to build payment transaction. |
PAY_SIGN_FAILED |
400 | Failed to sign transaction. |
PAY_SUBMIT_FAILED |
502 | Failed to submit to Stellar network. |
PAY_CONFIRMATION_TIMEOUT |
504 | Transaction confirmation timed out. |
PAY_INSUFFICIENT_BALANCE |
400 | Insufficient balance. |
PAY_SELF_PAYMENT |
400 | Cannot send to your own wallet. |
PAY_DESTINATION_NOT_FUNDED |
400 | Destination account does not exist. |
PAY_INVALID_DESTINATION |
400 | Invalid payment destination. |
PAY_HORIZON_ERROR |
502 | Stellar Horizon returned an error. |
| Code | HTTP | Description |
|---|---|---|
SRV_INTERNAL |
500 | Internal server error. |
SRV_HORIZON_UNAVAILABLE |
502 | Stellar Horizon is temporarily unavailable. |
SRV_FEDERATION_FAILED |
502 | External federation resolution failed. |
SRV_AI_NOT_CONFIGURED |
501 | AI payment parsing not configured. |
SRV_METRICS_FAILED |
500 | Failed to collect Prometheus metrics. |
SRV_NOT_IMPLEMENTED |
501 | Feature not yet implemented. |
| Code | HTTP | Description |
|---|---|---|
GEN_UNKNOWN |
500 | An unexpected error occurred. |
GEN_NETWORK_ERROR |
0 | Network error. Check your connection. |
GEN_OFFLINE |
0 | You are offline. |
| Limiter | Window | Limit | Applies to |
|---|---|---|---|
| Global | 15 minutes | 100 req/IP | All routes except /health and /api/health |
| Strict | 1 minute | 20 req/IP | /api/accounts/*, /api/payments/*, /api/analytics/*, /api/tips/*, /api/turrets/*, /federation |
Responses include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers.
| Status | Body |
|---|---|
| 429 (global) | RATE_LIMITED_GLOBAL — { "error": { "code": "RATE_LIMITED_GLOBAL", "message": "Too many requests. Please try again later." } } |
| 429 (strict) | RATE_LIMITED_SENSITIVE — { "error": { "code": "RATE_LIMITED_SENSITIVE", "message": "Too many requests to sensitive routes. Please wait 1 minute." } } |
- Health
- API documentation
- Authentication
- Stellar federation
- Accounts
- Payments
- Analytics
- Tips
- Turrets (txFunctions)
- Webhooks
- AI Payment Parsing
- Scheduled Transactions
- Global errors
Liveness probe. Not subject to global rate limiting.
Response 200
{
"status": "ok",
"service": "finchippay-api",
"network": "testnet",
"timestamp": "2025-01-01T00:00:00.000Z"
}| Field | Type | Description |
|---|---|---|
| status | string | Always "ok" when healthy |
| service | string | Service identifier |
| network | string | STELLAR_NETWORK env or "testnet" |
| timestamp | string (ISO 8601) | Server time |
Serves Swagger UI (HTML).
Returns the OpenAPI 3.0 specification as JSON.
Response 200 — OpenAPI document (large JSON object).
SEP-0010 Stellar Web Authentication.
Issue a challenge transaction for the client to sign.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| account | string | yes | Stellar public key (G + 55 alphanumerics) |
Response 200
{
"transaction": "<base64 XDR>",
"networkPassphrase": "Test SDF Network ; September 2015"
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Missing account query parameter" } |
| 400 | { "error": "<validation message>" } |
Verify a signed challenge and issue a JWT (also set as httpOnly cookie jwt).
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| transaction | string | yes | Signed challenge XDR (base64) |
Example request
{
"transaction": "AAAAAgAAAAC..."
}Response 200
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Missing transaction in request body" } |
| 401 | { "error": "Unauthorized: <reason>" } |
SEP-0001 discovery document (TOML, not JSON).
Response 200 (Content-Type: application/toml)
# Finchippay Solution federation discovery
FEDERATION_SERVER="http://localhost:4000/federation"SEP-0002 federation resolver. Subject to strict rate limit.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| q | string | yes | For type=name: username*domain; for type=id: Stellar account ID (G...) |
| type | string | yes | "name" or "id" |
Response 200 (type=name)
{
"stellar_address": "alice*stellarfinchippay.io",
"account_id": "GABC1234567890123456789012345678901234567890123456789012345"
}Response 200 (type=id)
{
"stellar_address": "alice*stellarfinchippay.io",
"account_id": "GABC1234567890123456789012345678901234567890123456789012345"
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Missing required parameters: q and type" } |
| 400 | { "error": "Invalid required parameters: q and type must be strings" } |
| 400 | { "error": "Invalid type parameter. Must be 'name' or 'id'" } |
| 400 | { "error": "Invalid stellar address format" } |
| 404 | { "error": "Not found" } |
| 404 | { "error": "Account ID not found" } |
Resolve a registered username to a public key. Subject to strict rate limit.
Path parameters
| Name | Type | Description |
|---|---|---|
| username | string | 3–20 alphanumeric characters (trimmed and lowercased) |
Response 200
{
"success": true,
"data": {
"username": "alice",
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345"
}
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Username is required" } |
| 400 | { "error": "Username must be 3-20 characters long and contain only letters and numbers" } |
| 404 | { "error": "Username not found" } |
Register a username for a Stellar public key. Subject to strict rate limit.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| username | string | yes | 3–20 alphanumeric characters |
| publicKey | string | yes | Stellar G... public key (56 chars) |
Example request
{
"username": "alice",
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345"
}Response 201
{
"success": true,
"data": {
"username": "alice",
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345"
},
"message": "Username registered successfully"
}Errors
| Status | Body |
|---|---|
| 400 | { "success": false, "error": "Username and public key are required" } |
| 400 | { "error": "Invalid Stellar public key format" } |
| 409 | { "error": "Username already registered" } |
| 409 | { "error": "Public key already registered to another username" } |
Fetch account info and balances from Horizon. Requires JWT; caller may only access their own key. Subject to strict rate limit.
Path parameters
| Name | Type | Description |
|---|---|---|
| publicKey | string | Stellar G... public key (56 chars) |
Headers
| Name | Value |
|---|---|
| Authorization | Bearer <jwt> |
Response 200
{
"success": true,
"data": {
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"sequence": "12345678",
"subentryCount": 0,
"balances": [
{
"assetCode": "XLM",
"balance": "9999.9999900",
"asset_type": "native"
}
]
}
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Invalid Stellar public key format" } |
| 401 | { "error": "Unauthorized: missing or invalid token" } |
| 401 | { "error": "Unauthorized: invalid or expired token" } |
| 403 | { "error": "Forbidden: you may only access your own account data" } |
| 404 | { "error": "Account not found. It may not be funded yet. Use Friendbot on testnet." } |
Fetch native XLM balance only. Same auth and rate-limit rules as GET /api/accounts/:publicKey.
Response 200
{
"success": true,
"data": {
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"xlm": "9999.9999900"
}
}Errors — Same as GET /api/accounts/:publicKey.
Payment history from Horizon. Subject to strict rate limit.
Path parameters
| Name | Type | Description |
|---|---|---|
| publicKey | string | Stellar G... public key |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
| limit | integer | 20 | Max results (capped at 100) |
| cursor | string | — | Horizon pagination cursor |
Response 200
{
"success": true,
"data": [
{
"id": "operation-id",
"type": "sent",
"amount": "10.0000000",
"asset": "XLM",
"from": "GABC...SENDER",
"to": "GXYZ...RECIPIENT",
"memo": "Coffee",
"createdAt": "2025-01-01T12:00:00.000Z",
"transactionHash": "abc123...",
"pagingToken": "..."
}
]
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Invalid Stellar public key format" } |
Aggregate payment statistics (computed from up to 100 recent payments).
Path parameters
| Name | Type | Description |
|---|---|---|
| publicKey | string | Stellar G... public key |
Response 200
{
"success": true,
"data": {
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"totalSentXLM": "150.0000000",
"totalReceivedXLM": "75.0000000",
"sentCount": 12,
"receivedCount": 5,
"totalTransactions": 17
}
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Invalid Stellar public key format" } |
All analytics routes use a 5-minute in-memory cache per public key. Subject to strict rate limit.
Path parameters
| Name | Type | Description |
|---|---|---|
| publicKey | string | Stellar G... public key |
Response 200
{
"success": true,
"data": {
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"totalSentXLM": "150.0000000",
"totalReceivedXLM": "75.0000000",
"uniqueCounterparties": 8,
"averageTransactionSize": "15.0000000",
"totalTransactions": 17
}
}Top 5 recipients by total XLM sent.
Response 200
{
"success": true,
"data": {
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"topRecipients": [
{
"address": "GXYZ...RECIPIENT",
"totalXLMSent": "50.0000000"
}
],
"count": 1
}
}Payment counts grouped by day of week (UTC).
Response 200
{
"success": true,
"data": {
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"activityByDay": [
{ "day": "Sunday", "dayIndex": 0, "transactionCount": 2 },
{ "day": "Monday", "dayIndex": 1, "transactionCount": 5 }
]
}
}Errors (all analytics routes)
| Status | Body |
|---|---|
| 400 | { "error": "Invalid Stellar public key format" } |
In-memory tip ledger (v1). Subject to strict rate limit.
Record a tip after an on-chain payment.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| senderPublicKey | string | yes | Sender G... key |
| creatorPublicKey | string | yes | Creator G... key |
| amount | string | yes | Positive numeric amount |
| asset | string | no | Asset code (default "XLM") |
| memo | string | no | Optional message |
| txHash | string | no | Stellar transaction hash |
Example request
{
"senderPublicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"creatorPublicKey": "GXYZ1234567890123456789012345678901234567890123456789012345",
"amount": "5.0",
"asset": "XLM",
"memo": "Great stream!",
"txHash": "a1b2c3..."
}Response 201
{
"success": true,
"data": {
"id": 1,
"senderPublicKey": "GABC...",
"creatorPublicKey": "GXYZ...",
"amount": "5.0",
"asset": "XLM",
"memo": "Great stream!",
"txHash": "a1b2c3...",
"timestamp": "2025-01-01T12:00:00.000Z"
},
"message": "Tip recorded successfully"
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "senderPublicKey is required, creatorPublicKey is required, ..." } (combined validation messages) |
| 400 | { "error": "Invalid sender public key format" } |
| 400 | { "error": "amount must be a positive number" } |
Tips received by a creator, with embedded stats.
Path parameters
| Name | Type | Description |
|---|---|---|
| creatorPublicKey | string | Creator G... key |
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
| limit | integer | 50 | Page size |
| offset | integer | 0 | Skip count |
Response 200
{
"success": true,
"data": {
"tips": [
{
"id": 1,
"senderPublicKey": "GABC...",
"creatorPublicKey": "GXYZ...",
"amount": "5.0",
"asset": "XLM",
"memo": "",
"txHash": "",
"timestamp": "2025-01-01T12:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0,
"stats": {
"totalTips": 1,
"totalByAsset": {
"XLM": { "count": 1, "amount": "5" }
},
"averageTip": "5",
"largestTip": "5",
"smallestTip": "5"
}
}
}Tip statistics for a creator.
Response 200
{
"success": true,
"data": {
"totalTips": 10,
"totalByAsset": {
"XLM": { "count": 10, "amount": "50" }
},
"averageTip": "5",
"largestTip": "20",
"smallestTip": "1"
}
}Tips sent by a user.
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
| limit | integer | 50 | Page size |
| offset | integer | 0 | Skip count |
Response 200
{
"success": true,
"data": {
"tips": [],
"total": 0,
"limit": 50,
"offset": 0
}
}Automated transaction functions (DCA, stop-loss, escrow release). Subject to strict rate limit (20 req/min).
Supported type values: dca, stop_loss, escrow_release.
List deployments.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| ownerPublicKey | string | no | Filter by owner G... key |
Response 200
{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"ownerPublicKey": "GABC...",
"type": "dca",
"status": "active",
"config": { "intervalMinutes": 60, "amountQuote": 10, "quoteAssetCode": "USDC", "quoteAssetIssuer": null },
"deploymentHash": "abc123...",
"createdAt": "2025-01-01T12:00:00.000Z",
"nextRunAt": "2025-01-01T13:00:00.000Z",
"lastExecutedAt": null,
"lastCheckedAt": null,
"lastObservedPriceUsd": null,
"lastError": null
}
]
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Invalid Stellar public key format" } |
Create a signing challenge (ManageData transaction XDR).
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| ownerPublicKey | string | yes | Owner G... key |
| type | string | yes | dca, stop_loss, or escrow_release |
| config | object | yes | Type-specific configuration |
Example request (DCA)
{
"ownerPublicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"type": "dca",
"config": {
"intervalMinutes": 60,
"amountQuote": 10,
"quoteAssetCode": "USDC",
"quoteAssetIssuer": "GBBD47IF6LOC7NNYVK5WQCCFNNBX2L5TBRW2NTRU3OBMKENZ5YKF3NPS"
}
}Response 200
{
"success": true,
"data": {
"challengeXDR": "AAAAAgAAAAC...",
"deploymentHash": "a1b2c3d4e5f6...",
"normalizedConfig": {
"intervalMinutes": 60,
"amountQuote": 10,
"quoteAssetCode": "USDC",
"quoteAssetIssuer": "GBBD47IF6LOC7NNYVK5WQCCFNNBX2L5TBRW2NTRU3OBMKENZ5YKF3NPS"
},
"networkPassphrase": "Test SDF Network ; September 2015"
}
}Errors
| Status | Body (examples) |
|---|---|
| 400 | { "error": "Invalid Stellar public key format" } |
| 400 | { "error": "Unsupported txFunction type. Use 'dca', 'stop_loss', or 'escrow_release'." } |
| 400 | { "error": "DCA intervalMinutes must be at least 1" } |
Deploy a txFunction after signing the challenge.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| ownerPublicKey | string | yes | Owner G... key |
| type | string | yes | dca, stop_loss, or escrow_release |
| config | object | yes | Same config used for challenge |
| deploymentHash | string | yes | Hash from challenge response |
| signedChallengeXDR | string | yes | Challenge XDR signed by owner |
Response 201
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"ownerPublicKey": "GABC...",
"type": "dca",
"status": "active",
"config": { },
"deploymentHash": "a1b2c3...",
"createdAt": "2025-01-01T12:00:00.000Z",
"nextRunAt": "2025-01-01T13:00:00.000Z"
}
}Errors
| Status | Body (examples) |
|---|---|
| 400 | { "error": "Configuration hash mismatch. Recreate challenge and sign again." } |
| 400 | { "error": "Asset issuer is required for non-native asset USDC" } |
| 401 | { "error": "Signed challenge was not signed by the owner account" } |
Get a single deployment.
Path parameters
| Name | Type | Description |
|---|---|---|
| id | string (UUID) | Deployment ID |
Response 200 — { "success": true, "data": { ...deployment } }
Errors
| Status | Body |
|---|---|
| 404 | { "error": "txFunction not found" } |
Execution log for a deployment (newest first).
Response 200
{
"success": true,
"data": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"deploymentId": "550e8400-e29b-41d4-a716-446655440000",
"status": "executed",
"message": "DCA txFunction generated",
"result": { "action": "buy_xlm_dca" },
"createdAt": "2025-01-01T12:30:00.000Z"
}
]
}Errors
| Status | Body |
|---|---|
| 404 | { "error": "txFunction not found" } |
Pause a deployment.
Response 200 — { "success": true, "data": { ...deployment, "status": "paused" } }
Resume a paused deployment.
Response 200 — { "success": true, "data": { ...deployment, "status": "active" } }
Register Horizon SSE listeners that POST to your URL when payments are received.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| publicKey | string | yes | Account to monitor |
| url | string | yes | HTTPS endpoint to receive events |
| secret | string | yes | HMAC secret for X-Webhook-Signature |
Example request
{
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"url": "https://example.com/webhooks/stellar",
"secret": "whsec_..."
}Response 201
{
"success": true,
"webhook": {
"id": "1",
"publicKey": "GABC...",
"url": "https://example.com/webhooks/stellar",
"secret": "whsec_...",
"createdAt": "2025-01-01T12:00:00.000Z"
}
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "publicKey, url, and secret are required" } |
| 500 | { "error": "<message>" } |
Outbound webhook payload (POST to your url)
{
"event": "payment.received",
"publicKey": "GABC...",
"payment": {
"id": "...",
"from": "G...",
"to": "G...",
"amount": "10.0000000",
"asset": "XLM",
"createdAt": "2025-01-01T12:00:00Z"
}
}Header: X-Webhook-Signature — HMAC-SHA256 hex of the JSON body using secret.
List webhooks for an account.
Response 200
{
"webhooks": [
{
"id": "1",
"publicKey": "GABC...",
"url": "https://example.com/webhooks/stellar",
"secret": "whsec_...",
"createdAt": "2025-01-01T12:00:00.000Z"
}
]
}Delete a webhook by numeric ID.
Path parameters
| Name | Type | Description |
|---|---|---|
| id | string | Webhook ID assigned at registration |
Response 200
{
"success": true,
"message": "Webhook 1 deleted"
}Errors
| Status | Body |
|---|---|
| 404 | { "error": "Webhook not found" } |
AI-powered natural language payment intent parsing using Anthropic's Claude.
Requires ANTHROPIC_API_KEY to be set in the backend environment. Returns 501 if not configured.
Parse a natural language payment description into a structured payment intent.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| input | string | yes | Natural language payment description |
Example request
{
"input": "Send 50 XLM to GABC123 for design work"
}Response 200
{
"amount": "50 XLM",
"recipient": "GABC123",
"memo": "design work",
"isValid": true,
"clarification": ""
}Response 200 (ambiguous input)
{
"amount": "",
"recipient": "Alice",
"memo": "job",
"isValid": false,
"clarification": "What amount should be sent?"
}Errors
| Status | Body |
|---|---|
| 400 | { "amount": "", "recipient": "", "memo": "", "isValid": false, "clarification": "Please provide a payment description." } |
| 501 | { "amount": "", "recipient": "", "memo": "", "isValid": false, "clarification": "AI payment parsing is not configured. Set ANTHROPIC_API_KEY." } |
| 500 | { "amount": "", "recipient": "", "memo": "", "isValid": false, "clarification": "Server error. Try again." } |
Schedule pre-signed Stellar transactions for future automatic submission.
Schedule a transaction for future submission.
Request body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| signedXDR | string | yes | Signed transaction XDR (base64) |
| submitAt | string | yes | ISO 8601 timestamp when the transaction should be submitted |
| publicKey | string | yes | Stellar public key that owns this transaction |
Example request
{
"signedXDR": "AAAAAgAAAAC...",
"submitAt": "2026-08-01T12:00:00Z",
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345"
}Response 201
{
"message": "Transaction scheduled successfully",
"id": 1,
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"submitAt": "2026-08-01T12:00:00.000Z"
}Errors
| Status | Body |
|---|---|
| 400 | { "error": "Missing signedXDR, submitAt, or publicKey" } |
| 400 | { "error": "submitAt must be a valid ISO 8601 date string" } |
List all pending scheduled transactions for a public key (sorted by earliest first).
Path parameters
| Name | Type | Description |
|---|---|---|
| publicKey | string | Stellar G... public key |
Response 200
[
{
"id": 1,
"submitAt": "2026-08-01T12:00:00.000Z",
"publicKey": "GABC1234567890123456789012345678901234567890123456789012345",
"attempts": 0,
"createdAt": "2026-07-15T08:00:00.000Z"
}
]Cancel a scheduled transaction by its ID.
Path parameters
| Name | Type | Description |
|---|---|---|
| id | string | Transaction ID assigned at scheduling |
Response 200
{
"message": "Transaction 1 cancelled successfully."
}Errors
| Status | Body |
|---|---|
| 404 | { "error": "Transaction 1 not found or not pending." } |
All errors now follow the standardized shape (see Error Codes Reference).
| HTTP status | Error Code | When |
|---|---|---|
| 400 | VAL_INVALID_JSON |
Invalid JSON body |
| 404 | RES_ROUTE_NOT_FOUND |
Unknown route |
| 415 | VAL_CONTENT_TYPE |
Missing Content-Type: application/json |
| 413 | VAL_BODY_TOO_LARGE |
Request body exceeds size limit |
| 429 | RATE_LIMITED_GLOBAL |
Global rate limit exceeded |
| 429 | RATE_LIMITED_SENSITIVE |
Strict rate limit exceeded |
| 500 | SRV_INTERNAL |
Unhandled server error |
Example standardized error response:
{
"error": {
"code": "RATE_LIMITED_GLOBAL",
"message": "Too many requests. Please try again later."
}
}CORS: Requests from origins not listed in ALLOWED_ORIGINS are rejected by the CORS middleware.
When TURRETS_PORT is set (default 4100), a separate process exposes:
| Method | Path | Description |
|---|---|---|
| GET | http://localhost:4100/health |
Sidecar health |
| * | http://localhost:4100/tx-functions/* |
Same txFunction routes as /api/turrets/* on the main server |
The main API on port 4000 mounts turrets at /api/turrets; prefer that URL for application integration.