Version: 1.0.0
License: MIT
Once the server is running, access the Swagger UI at:
http://localhost:3001/api/docs
The raw OpenAPI specification is available at:
http://localhost:3001/api/docs-json
The TrustFlow Backend API provides off-chain services for the TrustFlow gig economy platform. It handles:
- Authentication: Wallet-based JWT authentication using Stellar signatures
- Escrow Management: Create, manage, and release escrow vaults
- Dispute Resolution: Raise disputes and trigger juror notifications
- Webhooks: Register endpoints to receive event notifications
- Monitoring: Health checks and Prometheus metrics
- IPFS Pinning: Pin deliverables across multiple IPFS providers with content-hash verification, automatic failover, and a background re-pin worker for durability
- Admin Analytics: Read-only system-wide dashboards for protocol admins, aggregating escrow, gig, dispute, reputation, migration, and reconciliation state
-
Get Challenge:
GET /auth/challenge?address=YOUR_ADDRESS- Receive a challenge message to sign
-
Sign with Wallet: Sign the challenge using your Stellar wallet
-
Verify Signature:
POST /auth/verify{ "address": "GXXXXX...", "signature": "base64_signature..." } -
Receive JWT Token: Use this token in the
AuthorizationheaderAuthorization: Bearer YOUR_JWT_TOKEN
All non-monitoring endpoints are protected by a Redis-backed distributed token bucket so limits remain coordinated across multiple API nodes.
- Per-IP limits: Every request consumes from a route-specific bucket keyed by client IP.
- Per-wallet limits: Requests that include wallet identity consume a second route-specific bucket keyed by wallet address. Wallet identity is read from JWT user data, request body, query string, or route params.
- Abuse detection: Empty-bucket attempts are tracked in a Redis sorted set over a sliding abuse window.
- Lockouts: Repeated violations create temporary Redis lockout keys and return
429 Too Many Requestswithout consuming more bucket state.
{
"statusCode": 429,
"message": "Too many requests - rate limit exceeded",
"retryAfter": 30,
"scope": "wallet:gabc123"
}REDIS_URL=redis://localhost:6379
RATE_LIMIT_ABUSE_WINDOW_SECONDS=300
RATE_LIMIT_ABUSE_THRESHOLD=5
RATE_LIMIT_LOCKOUT_SECONDS=900/health and /metrics are exempt through @SkipRateLimit().
Mutating endpoints that create a resource (currently POST /gigs and POST /escrows) accept an
optional Idempotency-Key header so retries β e.g. after a client timeout β don't create
duplicate resources.
The TrustFlow backend implements automatic failover for Stellar RPC endpoints to ensure high availability. When the primary RPC endpoint becomes unavailable, the system automatically switches to configured fallback endpoints.
-
Multiple Endpoint Configuration: Configure comma-separated Horizon and Soroban RPC endpoints in
STELLAR_HORIZON_ENDPOINTSandSOROBAN_RPC_ENDPOINTSenvironment variables. -
Health Monitoring: Regular health checks (every 30 seconds) monitor all configured endpoints.
-
Automatic Failover: If the current endpoint fails 3 consecutive health checks, the system automatically switches to the next healthy endpoint.
-
Retry Logic: All Stellar operations include automatic retry with exponential backoff across available endpoints.
-
Monitoring: The
/rpc-statusendpoint provides real-time visibility into endpoint health and current failover state.
# Primary endpoint + fallbacks
STELLAR_HORIZON_ENDPOINTS=https://horizon-testnet.stellar.org,https://testnet.stellar.org,https://horizon-futurenet.stellar.org
SOROBAN_RPC_ENDPOINTS=https://soroban-testnet.stellar.org,https://rpc-testnet.stellar.orgcurl -X GET http://localhost:3001/rpc-status \
-H "Authorization: Bearer YOUR_JWT_TOKEN"Response:
{
"currentHorizonEndpoint": "https://horizon-testnet.stellar.org",
"currentSorobanEndpoint": "https://soroban-testnet.stellar.org",
"horizonEndpoints": [
{
"url": "https://horizon-testnet.stellar.org",
"healthy": true,
"lastChecked": "2024-01-01T00:00:00.000Z",
"failureCount": 0
},
{
"url": "https://testnet.stellar.org",
"healthy": true,
"lastChecked": "2024-01-01T00:00:00.000Z",
"failureCount": 0
}
],
"sorobanEndpoints": [
{
"url": "https://soroban-testnet.stellar.org",
"healthy": true,
"lastChecked": "2024-01-01T00:00:00.000Z",
"failureCount": 0
}
],
"timestamp": "2024-01-01T00:00:00.000Z"
}curl -X POST https://api.example.com/escrows \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "depositor": "G...", "beneficiary": "G...", "amountXLM": "100" }'- Generate a fresh, unique key (a UUID is recommended) per logical operation, not per HTTP attempt β reuse the same key when retrying the same request.
- The key is scoped to the specific endpoint (method + route), so the same key value can safely
be reused across different endpoints (e.g. once for
POST /gigsand separately forPOST /escrows) without colliding.
| Situation | Response |
|---|---|
No Idempotency-Key header |
Request is processed normally; not cached. |
| First request with a given key | Request is processed; the response is cached. |
| Retry with the same key and the same body | The original cached response is replayed (same status code and body) β the handler does not run again. |
| Retry with the same key and a different body | 422 Unprocessable Entity β the key has already been used for a different payload. |
| Concurrent request with the same key while the first is still in flight | 409 Conflict β a request with this key is already being processed; wait and retry rather than assuming failure. |
Cached responses are stored in Redis for IDEMPOTENCY_KEY_TTL_SECONDS (default 24h). Keys are
claimed atomically (SET NX), so concurrent duplicate requests cannot both create a resource. If
Redis is unavailable, idempotency protection is skipped and requests are processed normally
(fail-open) rather than blocking traffic.
Response bodies are cached in full, so avoid decorating @Idempotent() onto endpoints that return
very large or streamed payloads.
- Get your challenge and sign it
- Verify and receive a JWT token
- Click the π "Authorize" button in Swagger UI
- Enter your token (without "Bearer" prefix)
- All protected endpoints will now include your auth token
| Method | Endpoint | Description |
|---|---|---|
| GET | /auth/challenge |
Get authentication challenge |
| POST | /auth/verify |
Verify wallet signature and get JWT |
| Method | Endpoint | Description |
|---|---|---|
| POST | /escrows |
Create new escrow |
| GET | /escrows/:id |
Get escrow by ID |
| GET | /escrows/depositor/:address |
Get escrows by depositor |
| POST | /escrows/:id/release |
Release escrow funds |
| POST | /escrows/:id/dispute |
Raise a dispute |
| Method | Endpoint | Description |
|---|---|---|
| POST | /webhooks |
Register webhook |
| DELETE | /webhooks/:id |
Unregister webhook |
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /metrics |
Prometheus metrics |
Provides visibility into Stellar RPC endpoint health and failover status. Requires JWT authentication.
| Method | Endpoint | Description |
|---|---|---|
| GET | /rpc-status |
Get current RPC endpoint status, health information, and failover state |
| Method | Endpoint | Description |
|---|---|---|
| POST | /ipfs/pins |
Pin content across multiple providers with content-hash verification |
| GET | /ipfs/pins |
List all pin records |
| GET | /ipfs/pins/:cid |
Get a pin record by CID |
| POST | /ipfs/pins/:cid/verify |
Re-verify durability and top up replication if degraded |
| DELETE | /ipfs/pins/:cid |
Unpin from every provider currently holding the content |
Restricted to wallet addresses listed in ADMIN_ADDRESSES (see Environment Variables). All routes require a JWT (Authorization: Bearer ...) from an admin address and return 403 Forbidden for anyone else.
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/analytics/overview |
Full dashboard snapshot: escrows, gigs, disputes, reputation, migrations, reconciliation |
| GET | /admin/analytics/escrows |
Escrow totals and status breakdown |
| GET | /admin/analytics/gigs |
Gig solicitation totals and status breakdown |
| GET | /admin/analytics/disputes |
Dispute saga totals, step, and verdict breakdown |
curl -X POST http://localhost:3001/escrows \
-H "Content-Type: application/json" \
-d '{
"depositor": "GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"beneficiary": "GYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
"amountXLM": "100"
}'Response:
{
"id": "esc-1234567890",
"depositor": "GXXXXX...",
"beneficiary": "GYYYY...",
"amountXLM": "100",
"status": "pending",
"createdAt": "2026-06-13T00:00:00.000Z"
}curl -X POST http://localhost:3001/escrows/esc-1234567890/dispute \
-H "Content-Type: application/json" \
-d '{
"reason": "Work not delivered as specified"
}'Response:
{
"id": "esc-1234567890",
"status": "disputed",
"disputeReason": "Work not delivered as specified",
"disputedAt": "2026-06-13T01:00:00.000Z"
}Note: This also triggers:
- Webhook event (
dispute.raised) - Discord notification (if configured)
curl -X POST http://localhost:3001/webhooks \
-H "Content-Type: application/json" \
-d '{
"id": "my-webhook",
"url": "https://example.com/webhooks/trustflow"
}'Response:
{
"registered": true,
"id": "my-webhook"
}curl -X POST http://localhost:3001/ipfs/pins \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"content": "SGVsbG8sIFRydXN0RmxvdyE=",
"filename": "milestone-1-receipt.json",
"replicationFactor": 2
}'Response:
{
"cid": "bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku",
"size": 20,
"filename": "milestone-1-receipt.json",
"replicationFactor": 2,
"status": "HEALTHY",
"providers": [
{ "provider": "pinata", "status": "PINNED", "attempts": 1, "pinnedAt": "..." },
{ "provider": "web3.storage", "status": "PINNED", "attempts": 1, "pinnedAt": "..." }
],
"createdAt": "...",
"updatedAt": "..."
}The cid is derived purely from the submitted bytes (CIDv1, raw, sha2-256), so any provider that
ends up storing different bytes fails content-hash verification and is automatically failed over.
If a provider later loses the pin, POST /ipfs/pins/:cid/verify (also run automatically by the
background re-pin worker) detects it and restores replication via a spare provider.
When you register a webhook, you'll receive POST requests for these events:
| Event | Description | Payload |
|---|---|---|
escrow.created |
New escrow created | Escrow details |
escrow.released |
Funds released | Escrow details |
dispute.raised |
Dispute initiated | Dispute details |
dispute.resolved |
Dispute resolved | Resolution details |
ipfs.pin.created |
Content newly pinned | CID, replication factor, pinned providers |
ipfs.pin.degraded |
Pin dropped below its replication factor | CID |
ipfs.pin.restored |
Replication restored after a loss | CID, healthy provider count |
ipfs.pin.lost |
A provider no longer holds a previously-pinned CID | CID, provider |
ipfs.pin.failed |
Every registered provider failed to pin a CID | CID |
ipfs.pin.removed |
Content unpinned from all providers | CID |
{
"event": "dispute.raised",
"data": {
"escrowId": "esc-1234567890",
"depositor": "GXXXXX...",
"beneficiary": "GYYYY...",
"amountXLM": "100",
"reason": "Work not delivered",
"disputedAt": "2026-06-13T01:00:00.000Z"
},
"timestamp": "2026-06-13T01:00:00.000Z"
}| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid input |
| 401 | Unauthorized - Invalid or missing JWT |
| 404 | Not Found - Resource doesn't exist |
| 500 | Internal Server Error |
| 503 | Service Unavailable |
cd backend
npm install
npm run devThe API will be available at: http://localhost:3001
Swagger UI will be at: http://localhost:3001/api/docs
PORT=3001
JWT_SECRET=your-secret
REDIS_URL=redis://localhost:6379
RATE_LIMIT_ABUSE_WINDOW_SECONDS=300
RATE_LIMIT_ABUSE_THRESHOLD=5
RATE_LIMIT_LOCKOUT_SECONDS=900
IDEMPOTENCY_KEY_TTL_SECONDS=86400
STELLAR_NETWORK=TESTNET
STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
# Multiple endpoints for RPC failover (comma-separated, first is primary)
STELLAR_HORIZON_ENDPOINTS=https://horizon-testnet.stellar.org,https://testnet.stellar.org
SOROBAN_RPC_ENDPOINTS=https://soroban-testnet.stellar.org,https://rpc-testnet.stellar.org
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... (optional)
# IPFS pinning providers (all optional β unconfigured providers run in an
# in-memory simulated mode so pinning/failover work out of the box in dev/CI)
PINATA_JWT=
WEB3_STORAGE_TOKEN=
INFURA_IPFS_PROJECT_ID=
INFURA_IPFS_PROJECT_SECRET=
IPFS_REPIN_INTERVAL_MS=300000
# Admin dashboard β comma-separated Stellar addresses allowed to call /admin/*
# (required for those routes to return anything but 403)
ADMIN_ADDRESSES=# Get the specification
curl http://localhost:3001/api/docs-json > openapi.jsonThe OpenAPI specification can be used with:
- Postman: Import the JSON to create a collection
- Insomnia: Import for API testing
- Code Generators: Generate client SDKs
# Generate TypeScript client npx @openapitools/openapi-generator-cli generate \ -i http://localhost:3001/api/docs-json \ -g typescript-axios \ -o ./generated-client
- Start the server:
npm run dev - Open Swagger UI: http://localhost:3001/api/docs
- Try an endpoint:
- Click on any endpoint (e.g.,
GET /health) - Click "Try it out"
- Fill in parameters (if any)
- Click "Execute"
- View the response
- Click on any endpoint (e.g.,
-
Get Challenge:
- Expand
GET /auth/challenge - Enter your Stellar address
- Execute
- Copy the challenge message
- Expand
-
Sign with your wallet (outside Swagger)
-
Verify Signature:
- Expand
POST /auth/verify - Enter address and signature
- Execute
- Copy the JWT token
- Expand
-
Authorize:
- Click π "Authorize" button at top
- Paste your JWT token
- Click "Authorize"
-
Test Protected Endpoints: Now you can test escrow endpoints!
When adding new endpoints, include Swagger decorators:
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
@ApiTags('YourTag')
@Controller('your-route')
export class YourController {
@Get()
@ApiOperation({ summary: 'Your endpoint summary' })
@ApiResponse({ status: 200, description: 'Success response' })
yourMethod() {
// ...
}
}@ApiTags()- Group endpoints by tag@ApiOperation()- Describe the endpoint@ApiResponse()- Document response schemas@ApiParam()- Document path parameters@ApiQuery()- Document query parameters@ApiBody()- Document request body@ApiBearerAuth()- Mark as requiring JWT
When adding new API endpoints:
- β Add Swagger decorators to controllers
- β Document all parameters and responses
- β Test in Swagger UI
- β Update this documentation if needed
- Documentation: http://localhost:3001/api/docs
- Issues: https://github.com/trustflow-protocol/trustflow-backend/issues
- Community: Discord (link in main README)
Auto-generated API documentation powered by Swagger/OpenAPI