Developer infrastructure for the Stellar ecosystem.
| Environment | URL | Notes |
|---|---|---|
| Mainnet | https://api.savitools.com/api |
Production Stellar network |
| Testnet | https://testnet-api.savitools.com/api |
Stellar testnet environment |
| Local Development | http://localhost:3001/api |
Development server (default) |
The API uses URI-based versioning. All endpoints are prefixed with /v1 (or the version number). The current default version is v1.
Example: GET /api/v1/health
- Public endpoints: No authentication required (e.g.,
/wallet/generate,/simulator/paths) - Protected endpoints: Require valid JWT authentication via HTTP-only cookies
- Register or login to create a session
- Use the issued JWT cookie for subsequent requests
The API uses HTTP-only cookies to store JWT tokens automatically after authentication. When you call POST /auth/login or POST /auth/register, the response sets:
access_tokencookie (15-minute expiration)refresh_tokencookie (7-day expiration)
All subsequent requests automatically include these cookies. No header configuration needed.
If cookies are disabled, use:
Authorization: Bearer {accessToken}
To refresh an expired access token:
curl -X POST http://localhost:3001/api/v1/auth/refresh \
-H "Content-Type: application/json" \
--cookie "refresh_token=YOUR_REFRESH_TOKEN"Health check endpoint.
Request:
curl http://localhost:3001/api/v1/healthResponse (200):
{
"status": "ok"
}Register a new user with email and password.
Request:
curl -X POST http://localhost:3001/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "SecurePassword123"
}'Response (201):
{
"user": {
"id": "user-uuid",
"email": "user@example.com",
"fluxaTenantId": null
}
}Errors:
400: User already exists or invalid email format
Login with email and password.
Request:
curl -X POST http://localhost:3001/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "SecurePassword123"
}'Response (200):
{
"user": {
"id": "user-uuid",
"email": "user@example.com",
"fluxaTenantId": null
}
}Cookies Set:
access_token(15 min TTL)refresh_token(7 day TTL)
Errors:
401: Invalid email or password
Rotate refresh token and issue a new access token.
Request:
curl -X POST http://localhost:3001/api/v1/auth/refresh \
--cookie "refresh_token=YOUR_REFRESH_TOKEN"Response (200):
{
"user": {
"id": "user-uuid",
"email": "user@example.com"
}
}Errors:
401: Invalid or expired refresh token
Invalidate refresh token and clear auth cookies.
Request:
curl -X POST http://localhost:3001/api/v1/auth/logoutResponse (200):
{
"success": true
}Exchange a Fluxa API key for a SaviTools session and link accounts.
Request:
curl -X POST http://localhost:3001/api/v1/auth/fluxa \
-H "Content-Type: application/json" \
-d '{
"fluxaApiKey": "your-fluxa-api-key"
}'Response (200):
{
"user": {
"id": "user-uuid",
"email": "user@example.com",
"fluxaTenantId": "fluxa-tenant-id"
}
}Errors:
400: Invalid Fluxa API key
Get the current authenticated user.
Request:
curl http://localhost:3001/api/v1/auth/me \
--cookie "access_token=YOUR_ACCESS_TOKEN"Response (200):
{
"user": {
"id": "user-uuid",
"email": "user@example.com",
"fluxaTenantId": null
}
}Errors:
401: Not authenticated
Generate a new Stellar keypair (public key + secret).
Request:
curl -X POST http://localhost:3001/api/v1/wallet/generateResponse (201):
{
"publicKey": "GBZR7WLLV5OZVUQ4WAWCKVCOVWGZFZVHG5GMRFYVZJZ2AFSGHFKDQ4C",
"secret": "SBUQ54DRQG5Q3QLQHJEZ5ODSLGE...TRUNCATED"
}Fund a testnet account via Friendbot (10 XLM).
Request:
curl -X POST http://localhost:3001/api/v1/wallet/fund \
-H "Content-Type: application/json" \
-d '{
"publicKey": "GBZR7WLLV5OZVUQ4WAWCKVCOVWGZFZVHG5GMRFYVZJZ2AFSGHFKDQ4C"
}'Response (200):
{
"success": true,
"amount": "10.0000000",
"currency": "XLM",
"transactionHash": "6c1e1f6..."
}Errors:
400: Invalid public key or funding failed (rate-limited, etc.)
Get asset balances for a Stellar account.
Request:
curl "http://localhost:3001/api/v1/wallet/balances?publicKey=GBZR7WLLV5OZVUQ4WAWCKVCOVWGZFZVHG5GMRFYVZJZ2AFSGHFKDQ4C"Response (200):
{
"balances": [
{
"asset_type": "native",
"balance": "9.9999800",
"asset_code": "XLM"
},
{
"asset_type": "credit_alphanum4",
"asset_code": "USDC",
"asset_issuer": "GA...",
"balance": "100.0000000",
"limit": "922337203685.4775807"
}
]
}Errors:
400: Invalid public key or account not found
Send a payment from a sandbox wallet.
Request:
curl -X POST http://localhost:3001/api/v1/wallet/payment \
-H "Content-Type: application/json" \
-d '{
"sourceSecret": "SBUQ54DRQG5Q3QLQHJEZ5ODSLGE...",
"destination": "GBZR7WLLV5OZVUQ4WAWCKVCOVWGZFZVHG5GMRFYVZJZ2AFSGHFKDQ4C",
"asset": "XLM",
"amount": "5.00"
}'Response (200):
{
"transactionHash": "6c1e1f6fe...",
"success": true,
"amount": "5.0000000",
"destination": "GBZR7..."
}Errors:
400: Invalid parameters or insufficient balance
GET /simulator/paths?direction=...&source_asset_*=...&destination_asset_*=...&amount=...&network=...
Find payment paths between two assets.
Query Parameters:
direction(required):strict_sendorstrict_receivesource_asset_type(required):native|credit_alphanum4|credit_alphanum12source_asset_code(optional): Asset code (e.g.,USDC)source_asset_issuer(optional): Asset issuer public keydestination_asset_type(required): Asset type for destinationdestination_asset_code(optional): Destination asset codedestination_asset_issuer(optional): Destination asset issueramount(required): Amount to send/receivenetwork(optional, defaultmainnet):mainnetortestnet
Request:
curl "http://localhost:3001/api/v1/simulator/paths?direction=strict_send&source_asset_type=native&destination_asset_type=credit_alphanum4&destination_asset_code=USDC&destination_asset_issuer=GA...&amount=100&network=testnet"Response (200):
{
"paths": [
{
"path": [
{
"asset_type": "native"
}
],
"destination_amount": "99.5000000",
"source_amount": "100.0000000"
}
],
"direction": "strict_send"
}Errors:
400: Invalid parameters or no paths found
Compute destination_min or send_max for a selected path with slippage.
Request:
curl -X POST http://localhost:3001/api/v1/simulator/estimate \
-H "Content-Type: application/json" \
-d '{
"path": [...],
"sendAmount": "100.0",
"slippagePercent": 1.5
}'Response (200):
{
"sourceAmount": "100.0000000",
"destinationAmount": "98.5000000"
}Errors:
400: Invalid path or amount
Find paths for a strict send payment (you control the amount sent).
Request:
curl -X POST http://localhost:3001/api/v1/simulator/path-send \
-H "Content-Type: application/json" \
-d '{
"sourceAsset": {...},
"destinationAsset": {...},
"sendAmount": "100.0",
"network": "testnet"
}'Response (200):
{
"paths": [...],
"direction": "strict_send"
}Find paths for a strict receive payment (you control the amount received).
Request:
curl -X POST http://localhost:3001/api/v1/simulator/path-receive \
-H "Content-Type: application/json" \
-d '{
"sourceAsset": {...},
"destinationAsset": {...},
"receiveAmount": "100.0",
"network": "testnet"
}'Response (200):
{
"paths": [...],
"direction": "strict_receive"
}Estimate transaction fee based on current network fee stats.
Query Parameters:
operations(optional, default1): Number of operations in the transactionnetwork(optional, defaulttestnet):mainnetortestnet
Request:
curl "http://localhost:3001/api/v1/simulator/fee?operations=3&network=testnet"Response (200):
{
"baseFee": 100,
"totalFee": 300,
"operations": 3,
"network": "testnet"
}List all supported operation types with field schemas.
Request:
curl http://localhost:3001/api/v1/composer/operationsResponse (200):
{
"operations": [
{
"type": "payment",
"description": "Send an asset to another account",
"fields": [
{
"name": "destination",
"type": "string",
"description": "Destination account public key",
"required": true
},
{
"name": "asset",
"type": "object",
"description": "Asset to send"
},
{
"name": "amount",
"type": "string",
"description": "Amount to send"
}
]
},
{
"type": "path_payment_strict_send",
"description": "Send an asset via a specific path",
"fields": [...]
}
]
}Build a multi-op transaction and return unsigned XDR envelope.
Request:
curl -X POST http://localhost:3001/api/v1/composer/build \
-H "Content-Type: application/json" \
-d '{
"sourceAccount": {
"publicKey": "GBZR7...",
"sequence": "1234567890"
},
"fee": "300",
"operations": [
{
"type": "payment",
"destination": "GBUQWP...",
"asset": "native",
"amount": "10.00"
}
],
"network": "testnet"
}'Response (200):
{
"xdr": "AAAAAgAAAAB+Ht3sW...",
"hash": "5fa...",
"envelope_type": "ENVELOPE_TYPE_TX"
}Errors:
400: Invalid transaction parameters
Dry-run an XDR transaction against Horizon; returns fee and result codes.
Request:
curl -X POST http://localhost:3001/api/v1/composer/simulate \
-H "Content-Type: application/json" \
-d '{
"xdr": "AAAAAgAAAAB+Ht3sW...",
"network": "testnet"
}'Response (200):
{
"resultXdr": "...",
"fee": "300",
"resultCode": "txSUCCESS",
"operationResults": [
{
"code": "opSUCCESS"
}
]
}Errors:
400: Invalid XDR or simulation failed
Decode and inspect a Stellar transaction by hash.
Request:
curl http://localhost:3001/api/v1/transactions/5fa1f6d8a7c...Response (200):
{
"id": "5fa1f6d8a7c...",
"source_account": "GBZR7...",
"fee_charged": 300,
"successful": true,
"created_at": "2024-06-21T12:34:56Z",
"operations": [
{
"type": "payment",
"destination": "GBUQWP...",
"amount": "10.0000000",
"asset_type": "native"
}
]
}Errors:
404: Transaction not found
Get current Stellar network status and fees.
Query Parameters:
network(optional, defaultmainnet):mainnetortestnet
Request:
curl "http://localhost:3001/api/v1/network/status?network=testnet"Response (200):
{
"network": "testnet",
"baseFee": 100,
"baseReserve": 0.5,
"protocolVersion": 21,
"timestamp": "2024-06-21T12:34:56Z"
}Get last 60 minutes of network status history.
Query Parameters:
network(optional, defaultmainnet):mainnetortestnet
Request:
curl "http://localhost:3001/api/v1/network/status/history?network=testnet"Response (200):
{
"network": "testnet",
"history": [
{
"timestamp": "2024-06-21T11:34:56Z",
"baseFee": 100,
"baseReserve": 0.5
},
{
"timestamp": "2024-06-21T12:34:56Z",
"baseFee": 100,
"baseReserve": 0.5
}
]
}Deploy a Soroban smart contract from a WASM file.
Request (multipart/form-data):
curl -X POST http://localhost:3001/api/v1/contracts/deploy \
-F "file=@contract.wasm" \
-F "args=[\"arg1\",\"arg2\"]"Response (200):
{
"contractId": "CABC...",
"deployTransactionHash": "5fa1f6d...",
"wasmHash": "9e5551...",
"network": "testnet"
}Errors:
400: Invalid WASM file or deployment failed
Invoke a contract function.
Request:
curl -X POST http://localhost:3001/api/v1/contracts/CABC.../invoke \
-H "Content-Type: application/json" \
-d '{
"functionName": "transfer",
"args": ["GBU...", "100.00"]
}'Response (200):
{
"result": "...",
"transactionHash": "5fa1f6d..."
}Errors:
400: Invalid contract ID or parameters
Get contract metadata from the network.
Request:
curl http://localhost:3001/api/v1/contracts/CABC.../infoResponse (200):
{
"contractId": "CABC...",
"wasmHash": "9e5551...",
"createdLedger": 12345,
"createdAt": "2024-06-21T12:34:56Z"
}Errors:
404: Contract not found
List all supported webhook event types with schemas and sample payloads.
Request:
curl http://localhost:3001/api/v1/webhooks/templatesResponse (200):
{
"templates": [
{
"eventType": "transaction.submitted",
"description": "Emitted when a transaction is submitted",
"schema": {...},
"examplePayload": {...}
}
]
}Send a webhook payload to a target endpoint.
Request:
curl -X POST http://localhost:3001/api/v1/webhooks/send \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhook",
"eventType": "transaction.submitted",
"payload": {...}
}'Response (200):
{
"attemptId": "webhook-attempt-123",
"statusCode": 200,
"responseTime": 250
}Errors:
400: Invalid webhook payload
Get the last 50 webhook send attempts.
Request:
curl http://localhost:3001/api/v1/webhooks/historyResponse (200):
{
"attempts": [
{
"id": "webhook-attempt-123",
"eventType": "transaction.submitted",
"url": "https://example.com/webhook",
"statusCode": 200,
"timestamp": "2024-06-21T12:34:56Z"
}
]
}Replay a previous webhook send attempt.
Request:
curl -X POST http://localhost:3001/api/v1/webhooks/replay/webhook-attempt-123Response (200):
{
"attemptId": "webhook-attempt-456",
"statusCode": 200,
"responseTime": 275
}Errors:
404: Webhook attempt not found
Fetch and cache an OpenAPI spec for a provider (requires authentication).
Request:
curl http://localhost:3001/api/v1/playground/spec/stripe \
--cookie "access_token=YOUR_ACCESS_TOKEN"Response (200):
{
"provider": "stripe",
"spec": {...}
}Errors:
404: Provider spec not found401: Not authenticated
Proxy a request to the target API with server-side auth (requires authentication).
Request:
curl -X POST http://localhost:3001/api/v1/playground/proxy \
-H "Content-Type: application/json" \
--cookie "access_token=YOUR_ACCESS_TOKEN" \
-d '{
"provider": "stripe",
"method": "GET",
"path": "/v1/customers",
"params": {}
}'Response (200):
{
"statusCode": 200,
"body": {...}
}Save an encrypted API key (requires authentication).
Request:
curl -X POST http://localhost:3001/api/v1/playground/keys \
-H "Content-Type: application/json" \
--cookie "access_token=YOUR_ACCESS_TOKEN" \
-d '{
"provider": "stripe",
"key": "sk_live_..."
}'Response (201):
{
"id": "key-123",
"provider": "stripe",
"keyMasked": "sk_live_...***"
}List stored API keys (masked, requires authentication).
Request:
curl http://localhost:3001/api/v1/playground/keys \
--cookie "access_token=YOUR_ACCESS_TOKEN"Response (200):
{
"keys": [
{
"id": "key-123",
"provider": "stripe",
"keyMasked": "sk_live_...***"
}
]
}Update a stored API key (requires authentication).
Request:
curl -X PUT http://localhost:3001/api/v1/playground/keys/key-123 \
-H "Content-Type: application/json" \
--cookie "access_token=YOUR_ACCESS_TOKEN" \
-d '{
"key": "sk_live_new..."
}'Response (200):
{
"id": "key-123",
"keyMasked": "sk_live_...***"
}Delete a stored API key (requires authentication).
Request:
curl -X DELETE http://localhost:3001/api/v1/playground/keys/key-123 \
--cookie "access_token=YOUR_ACCESS_TOKEN"Response (204): No content
Get persisted tool state for the current user (requires authentication).
Path Parameters:
tool:sandbox|inspector|webhooks|composer
Request:
curl http://localhost:3001/api/v1/workspaces/composer \
--cookie "access_token=YOUR_ACCESS_TOKEN"Response (200):
{
"tool": "composer",
"data": {...}
}Errors:
400: Invalid tool name401: Not authenticated
Save tool state for the current user (requires authentication).
Request:
curl -X PUT http://localhost:3001/api/v1/workspaces/composer \
-H "Content-Type: application/json" \
--cookie "access_token=YOUR_ACCESS_TOKEN" \
-d '{
"data": {...}
}'Response (200):
{
"tool": "composer",
"data": {...}
}Create a watch for an account or contract (requires authentication).
Request:
curl -X POST http://localhost:3001/api/v1/monitor/watches \
-H "Content-Type: application/json" \
--cookie "access_token=YOUR_ACCESS_TOKEN" \
-d '{
"address": "GBZR7WLLV5OZVUQ4WAWCKVCOVWGZFZVHG5GMRFYVZJZ2AFSGHFKDQ4C",
"type": "account",
"label": "My Account",
"network": "testnet"
}'Response (201):
{
"id": "watch-123",
"address": "GBZR7...",
"type": "account",
"label": "My Account"
}Get all watches for the current user (requires authentication).
Request:
curl http://localhost:3001/api/v1/monitor/watches \
--cookie "access_token=YOUR_ACCESS_TOKEN"Response (200):
{
"watches": [
{
"id": "watch-123",
"address": "GBZR7...",
"type": "account",
"label": "My Account"
}
]
}Delete a watch (requires authentication).
Request:
curl -X DELETE http://localhost:3001/api/v1/monitor/watches/watch-123 \
--cookie "access_token=YOUR_ACCESS_TOKEN"Response (204): No content
Create an alert for a watch (requires authentication).
Request:
curl -X POST http://localhost:3001/api/v1/monitor/watches/watch-123/alerts \
-H "Content-Type: application/json" \
--cookie "access_token=YOUR_ACCESS_TOKEN" \
-d '{
"conditionType": "balance_threshold",
"threshold": "100.00",
"channel": "email",
"destination": "user@example.com"
}'Response (201):
{
"id": "alert-456",
"watchId": "watch-123",
"conditionType": "balance_threshold"
}Generate SDK code from a provider spec.
Request:
curl -X POST http://localhost:3001/api/v1/sdkgen/generate \
-H "Content-Type: application/json" \
-d '{
"spec": "fluxa",
"language": "typescript",
"endpoint": "https://api.example.com"
}'Response (200):
{
"code": "// Generated TypeScript SDK\nimport axios from 'axios';\n..."
}| Code | Meaning | When It Occurs |
|---|---|---|
200 |
OK | Successful GET, POST, or PUT request |
201 |
Created | Successful resource creation (POST) |
204 |
No Content | Successful DELETE request |
400 |
Bad Request | Invalid request parameters or validation failed |
401 |
Unauthorized | Missing or invalid authentication token |
404 |
Not Found | Resource does not exist |
422 |
Unprocessable Entity | Semantic error in request (e.g., invalid WASM) |
500 |
Internal Server Error | Unexpected server error |
Meaning: The Stellar public key provided is malformed or invalid.
Suggested Resolution: Verify the public key format (starts with G, 56 characters). Use /wallet/generate if unsure.
Meaning: The source account doesn't have enough native asset to cover the transaction fee and amount.
Suggested Resolution: Use /wallet/fund to add testnet funds, or send a smaller amount.
Meaning: Email/password combination is incorrect. Suggested Resolution: Double-check your email and password. Register a new account if needed.
Meaning: Your access token has expired (default 15 minutes).
Suggested Resolution: Call POST /auth/refresh with your refresh token to get a new access token.
Meaning: The specified transaction hash doesn't exist on the network. Suggested Resolution: Verify the transaction hash is correct and the network (mainnet/testnet) is correct.
Meaning: The uploaded file is not a valid Soroban WASM binary.
Suggested Resolution: Ensure the file is a compiled .wasm file from a Soroban contract.
SaviTools proxies some errors directly from the Stellar Horizon API. These errors include:
op_no_trust: Destination account doesn't have a trustline for the assetop_line_full: Destination account's limit for the asset is at maxop_underfunded: Source account doesn't have enough fundstx_bad_seq: Transaction sequence number is incorrecttx_bad_auth: Transaction hasn't been signed by the required signers
Example Horizon Error Response:
{
"type": "https://stellar.org/horizon-errors/transaction-failed",
"title": "Transaction Failed",
"status": 400,
"detail": "...",
"extras": {
"envelope_xdr": "...",
"result_xdr": "...",
"result_codes": {
"transaction": "tx_failed",
"operations": ["op_no_trust"]
}
}
}For a complete list, refer to the Stellar Horizon API documentation.
| Endpoint | TTL | Purpose | Cache Key |
|---|---|---|---|
GET /network/status |
60s | Network fees & reserves | network:status:{network} |
GET /network/status/history |
300s | Historical fee data | network:history:{network} |
GET /simulator/paths |
120s | Payment path results | paths:{source}:{dest}:{amount} |
GET /playground/spec/:provider |
3600s | OpenAPI specs | spec:cache:{provider} |
GET /webhooks/templates |
86400s | Webhook schema definitions | webhook:templates |
In development, to clear all cached data:
# If you have Redis CLI access:
redis-cli FLUSHDB
# Or via the API (clear specific cache):
DELETE /api/v1/admin/cache/network:status:mainnetResponses include standard HTTP cache headers:
Cache-Control: public, max-age=60
ETag: "abc123..."
Last-Modified: Mon, 21 Jun 2026 12:34:56 GMT
Current Status: No rate limiting is enforced in development/testing. Production deployment will include:
- 100 requests/minute per IP for public endpoints
- 1000 requests/minute per user for authenticated endpoints
- Custom limits for resource-intensive operations (e.g.,
/composer/simulate)
- CORS Origin: Controlled by
WEB_ORIGINenvironment variable (default:http://localhost:3000) - HTTPS: Enforced in production; cookies marked with
Secureflag - CSRF Protection: HTTP-only cookies prevent client-side token theft
- Input Validation: All inputs are validated and sanitized server-side
- API Status: Check Stellar Horizon Status
- Bug Reports: GitHub Issues
- Questions: Refer to Stellar Docs