Skip to content

Admin API ​

Administrative endpoints for pool, token factory, bridge, and PSP top-up management.

Two trust tiers live under /api/v1/admin/*:

  • Platform-operator only (Pool Management, Bridge) — always require X-Service-Token (or a super_admin/permissioned-AdminUser dashboard session). These touch shared, cross-tenant state — on-chain liquidity pools, the bridge — so a tenant's own credentials never work here, marked 🔒 below. Tenant and asset registry management live outside this page — see Tenants → and Assets →.
  • Tenant-reachable (Token Factory, PSP Top-up) — a tenant's own X-API-Key/X-API-Secret works here too, automatically scoped to that tenant's own tokens/transactions only. X-Service-Token and dashboard sessions also still work, for platform-operator cross-tenant access.

The PSP callback is the one exception to both tiers — the PSP itself calls it, verified by signature instead of either credential.

▸API ConfigurationNot configured

Pool Management ​

Create Pool ​

Creates a new Uniswap V2 pair and registers it.

Create Pool

🔒 Admin operation — always requires a Service Token, regardless of the panel above.

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

Fund Pool ​

Adds liquidity to an existing pool.

Fund Pool

🔒 Admin operation — always requires a Service Token, regardless of the panel above.

curl -X POST "http://localhost:8100/api/v1/admin/pools/fund" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_SERVICE_TOKEN" \
  -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

🔒 Admin operation — always requires a Service Token, regardless of the panel above.

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

Remove Liquidity ​

Remove Liquidity

🔒 Admin operation — always requires a Service Token, regardless of the panel above.

curl -X POST "http://localhost:8100/api/v1/admin/pools/remove-liquidity" \
  -H "Content-Type: application/json" \
  -H "X-Service-Token: YOUR_SERVICE_TOKEN" \
  -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/admin/tokens/create" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -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/admin/tokens?chain=celo" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET"
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/admin/tokens/4" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET"
Response200
{
  "token": {
    "id": 4,
    "chain": "celo",
    "symbol": "ACME",
    "tokenType": "utility",
    "totalSupply": 1000000
  }
}

Mint Token ​

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

Burn Token ​

Burn Token
curl -X POST "http://localhost:8100/api/v1/admin/tokens/4/burn" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -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/admin/bridge/wrap" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -d '{
  "user": "0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d",
  "primaryToken": "USDT",
  "amount": 5000000
}'
Response200
{
  "txHash": "0x...",
  "yshMinted": 5000000
}

Unwrap ​

Unwrap
curl -X POST "http://localhost:8100/api/v1/admin/bridge/unwrap" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -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/admin/bridge/multi-wrap" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -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/admin/bridge/positions/0xe3f53fe54f8e1b5e8c86f070e4c9c2c7fa641d0d" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET"
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-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -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-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -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-API-Key: YOUR_API_KEY" \
  -H "X-API-Secret: YOUR_API_SECRET" \
  -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.


Full worked example

See Examples: Liquidity Pool Setup for a complete walkthrough — register assets, create a pool, fund it, verify reserves.

Released under the MIT License.