All endpoints are served from the base URL configured via PORT (default: 4000).
Most protected routes require a Bearer JWT obtained from POST /auth/token.
Authorization: Bearer <token>
Tokens are issued after a successful SEP-10 Stellar wallet challenge/response flow.
Liveness check. No auth required.
Response 200
{
"status": "ok",
"healthStatus": {
"stellar": "ok"
}
}Returns a SEP-10 challenge XDR for the given Stellar account. No auth required.
Query params
| Param | Type | Required | Description |
|---|---|---|---|
account |
string | ✅ | Stellar public key (G…) |
Response 200
{
"challenge": "<XDR string>",
"networkPassphrase": "Test SDF Network ; September 2015"
}Submit a signed SEP-10 XDR to receive a JWT. No auth required.
Request body
{
"signedXdr": "<signed XDR string>",
"account": "GABC...XYZ"
}Response 200
{
"token": "<JWT>",
"account": "GABC...XYZ",
"expiresAt": 1700000000
}Pin player metadata to IPFS and return the content ID. No auth required.
Request body
{
"wallet": "GABC...XYZ",
"position": "Midfielder",
"region": "West Africa",
"metadata": {
"name": "Kwame Asante",
"age": 19,
"club": "Accra Lions FC",
"highlightReels": ["QmXyz..."],
"stats": { "topSpeed": "32 km/h" }
}
}Response 201
{
"success": true,
"data": {
"metadataUri": "QmXyz...",
"gatewayUrl": "https://gateway.pinata.cloud/ipfs/QmXyz..."
}
}Filter players by region, position, and minimum verified tier. No auth required.
Query params
| Param | Type | Required | Description |
|---|---|---|---|
region |
string | ❌ | Filter by region |
position |
string | ❌ | Filter by position |
minTier |
integer | ❌ | Minimum progress level (0–3) |
page |
integer | ❌ | Page number (default: 1) |
pageSize |
integer | ❌ | Results per page (default: 20, max: 100) |
Response 200
{
"success": true,
"data": [
{
"player_id": "abc123",
"wallet": "GABC...XYZ",
"position": "Midfielder",
"region": "West Africa",
"progress_level": 2
}
],
"total": 1,
"page": 1,
"pageSize": 20
}Error 400 — invalid minTier
{
"success": false,
"error": "minTier 5 is out of range. Valid values: 0, 1, 2, 3."
}Retrieve a single player profile. No auth required.
Response 200
{
"success": true,
"data": {
"player_id": "abc123",
"wallet": "GABC...XYZ",
"position": "Midfielder",
"region": "West Africa",
"progress_level": 2,
"tierName": "tier.2.name",
"tierDescription": "tier.2.description"
}
}Error 404
{ "success": false, "error": "Player not found" }Tamper-proof milestone history for a player. No auth required.
Response 200
{
"success": true,
"data": [
{
"type": "milestone_approved",
"ledger": 12345,
"txHash": "abc...",
"payload": {
"player_id": "abc123",
"milestone_type": "performance",
"evidence_uri": "QmEvidence..."
}
}
]
}Check active subscription status for a scout. Requires Bearer auth.
Response 200
{
"success": true,
"data": {
"active": true,
"expiresAt": 1700000000
}
}
⚠️ Stubbed — subscription data is read from indexed contract events; no write endpoint yet.
List players unlocked by a scout. Requires Bearer auth.
Response 200
{
"success": true,
"data": [
{ "playerId": "abc123", "unlockedAt": 1700000000 }
]
}
⚠️ Stubbed — contact data is read from indexed contract events; no write endpoint yet.
Pin milestone evidence to IPFS and return the CID. Requires Bearer auth (validator role).
Request body
{
"playerId": "abc123",
"milestoneType": "performance",
"evidence": {
"description": "Scored 5 goals in Local Cup",
"date": "2024-03-15"
}
}Response 201
{
"success": true,
"data": {
"evidenceUri": "QmEvidence...",
"gatewayUrl": "https://gateway.pinata.cloud/ipfs/QmEvidence..."
}
}List pending milestone approvals. Requires Bearer auth (validator role).
Response 200
{
"success": true,
"data": [
{
"milestoneId": "m001",
"playerId": "abc123",
"milestoneType": "performance",
"evidenceUri": "QmEvidence...",
"submittedAt": 1700000000
}
]
}
⚠️ Stubbed — returns events indexed from the contract; approval must be submitted on-chain.
Platform-wide counts. Requires Bearer auth (admin role).
Response 200
{
"success": true,
"data": {
"players": 42,
"milestones": 130,
"subscriptions": 17,
"events": 500
}
}All indexed contract events. Requires Bearer auth.
Response 200
{
"success": true,
"data": [
{
"type": "player_registered",
"ledger": 12345,
"txHash": "abc...",
"payload": {}
}
]
}Fee withdrawal history. Requires Bearer auth.
Response 200
{
"success": true,
"data": [
{
"type": "fees_withdrawn",
"ledger": 12399,
"txHash": "def...",
"payload": { "amount": "5000000", "recipient": "GADMIN..." }
}
]
}The following routes currently return data sourced entirely from indexed on-chain events and have no corresponding write/mutation endpoint in the backend:
| Route | Reason |
|---|---|
GET /api/scouts/:wallet/subscription |
Subscription state managed on-chain via subscribe(); backend is read-only |
GET /api/scouts/:wallet/contacts |
Contact unlocks managed on-chain via pay_to_contact(); backend is read-only |
GET /api/validators/milestones/pending |
Milestone approval is an on-chain transaction; backend only indexes events |
All error responses follow this shape:
{
"success": false,
"error": "<human-readable message>"
}Common HTTP status codes:
| Code | Meaning |
|---|---|
| 400 | Validation error |
| 401 | Missing or invalid auth token |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 500 | Internal server error |