Skip to content

Admin API

Administrative endpoints for tenant, asset, pool, token factory, bridge, and PSP top-up management. All admin endpoints require X-Service-Token authentication (except the PSP callback, which the PSP itself calls and which is verified by signature instead).

API ConfigurationNot configured

WARNING

These endpoints are for platform administration only. Tenant operators should use API Key/Secret authentication.


Tenant Management

Create Tenant

Create Tenant
curl -X POST "http://localhost:8100/api/v1/admin/tenants" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"name": "new-tenant", "api_key": "tenant-key-001", "api_secret": "tenant-secret-001"}'
Response200
{ "tenantId": "tenant-001", "name": "new-tenant", "api_key": "tenant-key-001", "created_at": "2026-08-30T12:00:00Z" }

List Tenants

List Tenants
curl -X GET "http://localhost:8100/api/v1/admin/tenants" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "tenants": [{ "tenantId": "comet", "name": "FusionFi", "created_at": "2026-08-01T00:00:00Z" }], "count": 1 }

Rotate Tenant Secret

Rotate Secret
curl -X POST "http://localhost:8100/api/v1/admin/tenants/comet/rotate-secret" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "tenantId": "comet", "new_api_secret": "new-secret-123", "rotated_at": "2026-08-30T12:00:00Z" }

Asset Management

Register Asset

Registers a new token in the engine's asset registry, on any configured chain — EVM or Stellar.

Register Asset
curl -X POST "http://localhost:8100/api/v1/admin/assets/IMC/register?chain=celo" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"contract": "0xF150E30B1C7Ab81938d650567D1454Ee0132d5A2", "decimals": 6, "name": "ImpalaCoin", "type": "airtime", "backing": "airtime"}'
Response200
{ "message": "asset IMC registered on celo", "asset": { "symbol": "IMC", "contract": "0xF150...d5A2" } }
FieldTypeRequiredDescription
contractstringYesERC-20 contract address on EVM chains; the asset's issuer account (G...) on Stellar
decimalsnumberYesToken decimals (6 for USDT/USDC/IMC; Stellar's native XLM is always 7, but registered Stellar assets use whatever you set here)
namestringYesHuman-readable token name
typestringNoairtime, stablecoin, or utility
backingstringNoairtime or none

Full request/response detail, including a Stellar example, is in the Assets API →.

Deregister Asset

Deregister Asset
curl -X DELETE "http://localhost:8100/api/v1/admin/assets/mytoken?chain=celo" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "message": "asset mytoken deregistered from celo" }

Pool Management

Create Pool

Creates a new Uniswap V2 pair and registers it.

Create Pool
curl -X POST "http://localhost:8100/api/v1/admin/pools/create" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"tokenA": "USDT", "tokenB": "IMC", "chain": "celo"}'
Response200
{ "pair": "0xfE6A7492c21DE2C196B465A814A1FAf9B2564082", "tokenA": "USDT", "tokenB": "IMC" }

Fund Pool

Adds liquidity to an existing pool.

Fund Pool
curl -X POST "http://localhost:8100/api/v1/admin/pools/fund" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"pair": "0xfE6A7492c21DE2C196B465A814A1FAf9B2564082", "amountA": 500000000, "amountB": 1000000000, "chain": "celo"}'
Response200
{ "status": "success", "txHash": "0x...", "pair": "0xfE6A...4082" }

Reset Pool Ratio

Forces pool reserves to a target ratio.

Reset Pool
curl -X POST "http://localhost:8100/api/v1/admin/pools/reset" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"pair": "0xfE6A7492c21DE2C196B465A814A1FAf9B2564082", "reserveA": 1000000000, "reserveB": 2000000000, "chain": "celo"}'
Response200
{ "status": "success", "txHash": "0x...", "pair": "0xfE6A...4082" }

Remove Liquidity

Remove Liquidity
curl -X POST "http://localhost:8100/api/v1/admin/pools/remove-liquidity" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"pair": "0xfE6A7492c21DE2C196B465A814A1FAf9B2564082", "liquidity": 1000000000, "chain": "celo"}'
Response200
{ "status": "success", "txHash": "0x...", "pair": "0xfE6A...4082" }

Token Factory

Deploys and manages tenant-owned tokens on-chain, tracked in a MongoDB registry separate from the env-var-configured assets above — use this for tokens a tenant creates at runtime (e.g. a customer's own utility or security token) rather than platform assets like USDT/IMC.

Create Token

Deploys a new token contract via the on-chain factory and registers it.

Create Token
curl -X POST "http://localhost:8100/api/v1/tokens/create" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"name": "Acme Rewards", "symbol": "ACME", "tokenType": "utility", "totalSupply": 1000000, "maxSupply": 10000000, "chain": "celo"}'
Response200
{ "token": { "id": 4, "chain": "celo", "tokenType": "utility", "contractAddress": "0x...", "name": "Acme Rewards", "symbol": "ACME" }, "onChain": { "factoryTokenId": 4, "contract": "0x...", "chain": "celo" } }
FieldTypeRequiredDescription
namestringYesToken name
symbolstringYesToken symbol
tokenTypestringYesutility or security
totalSupplynumberNoInitial supply (base units)
maxSupplynumberNoHard cap (base units)
chainstringNoDefaults to celo. The chain must have a factory contract deployed (FactoryAddress configured), or this returns 400 factory not deployed on this chain.
backingstringNoFree-form backing description, e.g. "real-estate"
valuationnumberNoOff-chain valuation reference
propertyRefstringNoOff-chain reference ID (e.g. property/asset record)

Security token only:

FieldTypeDescription
kycRequiredbooleanRequires KYC before holding the token
accreditedOnlybooleanRestricts to accredited holders
maxHoldersnumberCap on distinct holders
lockupDaysnumberTransfer lockup period

List Tokens

List Tokens
curl -X GET "http://localhost:8100/api/v1/tokens?chain=celo" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "tokens": [{ "id": 4, "chain": "celo", "symbol": "ACME", "tokenType": "utility" }], "count": 1 }

Scoped to the calling tenant. chain is optional — omit it to list across all chains.

Get Token

Get Token
curl -X GET "http://localhost:8100/api/v1/tokens/4" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "token": { "id": 4, "chain": "celo", "symbol": "ACME", "tokenType": "utility", "totalSupply": 1000000 } }

Mint Token

Mint Token
curl -X POST "http://localhost:8100/api/v1/tokens/4/mint" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"to": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "amount": 5000}'
Response200
{ "txHash": "0x...", "newSupply": 1005000 }

Burn Token

Burn Token
curl -X POST "http://localhost:8100/api/v1/tokens/4/burn" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"from": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "amount": 5000}'
Response200
{ "txHash": "0x...", "newSupply": 1000000 }

Fails with 400 burn amount exceeds total supply if amount is greater than the token's currently tracked supply.


Bridge

Wraps assets from any registered token into a unified YSH representation (1:1, amount in base units in → same amount of YSH out) and back. Requires BridgeAddress configured for the chain (Celo only today) — every bridge endpoint returns 400 bridge not deployed otherwise.

Wrap

Wrap
curl -X POST "http://localhost:8100/api/v1/bridge/wrap" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"user": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "primaryToken": "USDT", "amount": 5000000}'
Response200
{ "txHash": "0x...", "yshMinted": 5000000 }

Unwrap

Unwrap
curl -X POST "http://localhost:8100/api/v1/bridge/unwrap" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"user": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "primaryToken": "USDT", "yshAmount": 5000000}'
Response200
{ "txHash": "0x...", "tokensReleased": 5000000 }

Multi-Wrap

Wraps several tokens into YSH in a single transaction — tokens and amounts must be the same length, paired by index.

Multi-Wrap
curl -X POST "http://localhost:8100/api/v1/bridge/multi-wrap" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"user": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "tokens": ["USDT", "IMC"], "amounts": [3000000, 2000000]}'
Response200
{ "txHash": "0x...", "totalYSH": 5000000 }

Get Wrapped Position

Get Position
curl -X GET "http://localhost:8100/api/v1/bridge/positions/0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE"
Response200
{ "user": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d", "wrappedYSH": "5000000" }

PSP Top-up

Initiates mobile-money STK push (customer-to-platform) and B2C transfer (platform-to-customer) payments through the configured PSP, and journals the movement automatically. Every endpoint here returns 503 PSP integration not configured unless PSP_API_KEY/PSP_API_SECRET are set — see Configuration →.

STK Push (Deposit)

Prompts the user's phone for a mobile-money PIN and, once paid, credits the tenant's KES treasury.

STK Push
curl -X POST "http://localhost:8100/api/v1/admin/topup/stk-push" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"externalUserId": "123", "phone": "0712345678", "amount": 500, "mobileMoneySP": "MPESA", "externalId": "topup-001", "displayName": "Acme Wallet"}'
Response200
{ "status": "pending", "message": "Success. Request accepted for processing", "secureId": "sec_abc123", "transactionId": "txn_xyz789" }
FieldTypeRequiredDescription
externalUserIdstringYesUser the deposit is credited against
phonestringYesPayer's phone number (local or 254... format)
amountnumberYesAmount in KES, must be > 0
mobileMoneySPstringYesMobile money service provider, e.g. MPESA
externalIdstringYesYour own idempotency key for this transaction
displayNamestringNoShown to the payer on the STK prompt

A journal entry is recorded immediately as pending (debit cash, credit tenant treasury) — the PSP callback updates its status when the PSP confirms.

B2C Transfer (Withdrawal)

Sends money from the tenant's KES treasury out to a phone number. Checks the tenant's ledger balance before calling the PSP — returns 400 insufficient balance rather than attempting a transfer the tenant can't cover.

B2C Transfer
curl -X POST "http://localhost:8100/api/v1/admin/topup/transfer" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"externalUserId": "123", "phone": "0712345678", "amount": 500, "mobileMoneySP": "MPESA", "externalId": "withdraw-001"}'
Response200
{ "status": "pending", "message": "Success. Request accepted for processing", "secureId": "sec_abc124", "transactionId": "txn_xyz790" }

Same fields as STK Push (minus displayName).

PSP Callback

The PSP calls this back directly (not through the tenant's own backend) to report the final status of a top-up or transfer. Not X-Service-Token-authenticated like the rest of this page — instead verified via an HMAC signature.

PSP Callback (received)
curl -X POST "http://localhost:8100/api/v1/psp/callback?tenant=comet" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_TOKEN_HERE" \
  -d '{"transactionId": "txn_xyz789", "externalId": "topup-001", "status": "completed", "amount": 500, "reference": "QGH7XYZ123", "secureId": "sec_abc123"}'
Response200
{ "status": "received" }
HeaderDescription
X-Mamlaka-SignatureHMAC signature over the raw request body, verified against PSP_CALLBACK_SECRET. Requests that fail verification get 401 invalid callback signature.

The engine looks up the pending transaction by transactionId (falling back to externalId), updates its status (completed/failed/other), and stores the raw callback body for audit. Unrecognized tenants or transactions still return 200 {"status": "ignored", ...} rather than an error — PSPs generally retry non-2xx responses, and there's nothing to retry once the tenant/transaction can't be resolved.


End-to-End Example

Deploy and Fund a New Pool

bash
# 1. Register both assets
curl -X POST http://localhost:8100/api/v1/admin/assets/USDT/register?chain=celo \
  -H "X-Service-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"contract": "0x3C8EB6c8DF02B43a7bae71314917aCBdd60edDDF", "decimals": 6, "name": "Tether USD"}'

curl -X POST http://localhost:8100/api/v1/admin/assets/IMC/register?chain=celo \
  -H "X-Service-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"contract": "0xF150E30B1C7Ab81938d650567D1454Ee0132d5A2", "decimals": 6, "name": "ImpalaCoin", "type": "airtime", "backing": "airtime"}'

# 2. Create pool
curl -X POST http://localhost:8100/api/v1/admin/pools/create \
  -H "X-Service-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tokenA": "USDT", "tokenB": "IMC", "chain": "celo"}'
# => {"pair": "0xfE6A...4082"}

# 3. Fund pool with 500 USDT and 1000 IMC
curl -X POST http://localhost:8100/api/v1/admin/pools/fund \
  -H "X-Service-Token: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pair": "0xfE6A7492c21DE2C196B465A814A1FAf9B2564082", "amountA": 500000000, "amountB": 1000000000, "chain": "celo"}'

# 4. Verify pool exists
curl "http://localhost:8100/api/v1/swap/pools" \
  -H "X-Service-Token: YOUR_TOKEN"

Released under the MIT License.