OpenAPI 3.1
La spec complète, prête à être importée dans Postman ou Insomnia. Mise à jour à chaque release.
Télécharger openapi.yamlEndpoints REST sous /v1/. Réponses JSON UTF-8. Authentification Bearer OAuth 2.0. Pagination cursor. Specification OpenAPI 3.1 complète téléchargeable.
La spec complète, prête à être importée dans Postman ou Insomnia. Mise à jour à chaque release.
La spec complète, prête à être importée dans Postman ou Insomnia. Mise à jour à chaque release.
Télécharger openapi.yamlToutes les requêtes nécessitent un access token Bearer. Échangez vos clés client (préfixe sk_test_ ou sk_live_) contre un token court via /v1/auth/token. Les scopes sont explicites : kyc:read, kyc:write, orders:write, audit:read, webhooks:manage.
Rotation : les secrets sont rotables sans downtime via le dashboard. L'ancien secret reste valide 24 h après émission du nouveau.
# 1. Exchange client credentials for an access token
curl -X POST https://api.getinopay.com/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "ino_client_8XK9R2",
"client_secret": "sk_live_4bX9...PqW2",
"scope": "kyc:read orders:write audit:read"
}'
# Response 200 OK
# {
# "access_token": "eyJhbGciOiJFZERTQSIs...",
# "token_type": "Bearer",
# "expires_in": 900,
# "scope": "kyc:read orders:write audit:read"
# }
# 2. Authenticated calls
curl -X GET https://api.getinopay.com/v1/orders/ord_9Pk2X \
-H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..."import os
import requests
# Exchange credentials for a Bearer token
resp = requests.post(
"https://api.getinopay.com/v1/auth/token",
json={
"grant_type": "client_credentials",
"client_id": os.environ["INOPAY_CLIENT_ID"],
"client_secret": os.environ["INOPAY_CLIENT_SECRET"],
"scope": "kyc:read orders:write",
},
timeout=5,
)
resp.raise_for_status()
token = resp.json()["access_token"]
# Use the Bearer for subsequent calls
order = requests.get(
"https://api.getinopay.com/v1/orders/ord_9Pk2X",
headers={"Authorization": f"Bearer {token}"},
timeout=5,
).json()// Node 20+ / Browser fetch
const tokenRes = await fetch('https://api.getinopay.com/v1/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.INOPAY_CLIENT_ID,
client_secret: process.env.INOPAY_CLIENT_SECRET,
scope: 'kyc:read orders:write',
}),
});
const { access_token } = await tokenRes.json();
const orderRes = await fetch(
'https://api.getinopay.com/v1/orders/ord_9Pk2X',
{ headers: { Authorization: `Bearer ${access_token}` } },
);
const order = await orderRes.json();Bodies JSON UTF-8. Dates ISO 8601 (ex. 2026-04-25T14:32:00Z, toujours UTC). Montants en centimes XOF (entiers). Identifiants pseudonymes (ino_xxx, ord_xxx, att_xxx).
# Example POST body — amounts in XOF cents (entiers)
{
"rcpt_to": "sgi_partner_001",
"kyc_attestation_id": "att_4XK9RZ",
"instrument": "SNTS.BRVM",
"side": "buy",
"qty": 10,
"limit_price_cents": 1250000, // 12 500 XOF
"submitted_at": "2026-04-25T14:32:00Z",
"client_metadata": {
"internal_ref": "BO-2026-04-25-0001"
}
}
# Example successful response (201 Created)
{
"id": "ord_9Pk2X",
"status": "routed",
"rcpt_to": "sgi_partner_001",
"instrument": "SNTS.BRVM",
"side": "buy",
"qty": 10,
"limit_price_cents": 1250000,
"filled_qty": 0,
"average_fill_price_cents": null,
"created_at": "2026-04-25T14:32:00.142Z",
"updated_at": "2026-04-25T14:32:00.142Z"
}Header Idempotency-Key obligatoire sur tous les POST mutateurs. UUID v4 recommandé. Les réponses sont mises en cache 24 h : un retry avec la même clé renvoie la réponse originale (même code HTTP).
# Both POSTs produce one order — second returns the cached 201
curl -X POST https://api.getinopay.com/v1/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 6c4b0b6e-1c2a-4d09-9b3a-7d1f4e5a6c2d" \
-H "Content-Type: application/json" \
-d @order.json
# Retry within 24h with the same key:
curl -X POST https://api.getinopay.com/v1/orders \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 6c4b0b6e-1c2a-4d09-9b3a-7d1f4e5a6c2d" \
-H "Content-Type: application/json" \
-d @order.json
# → 201 Created (same body, same id, replayed from cache)Pagination cursor opaque : passez ?cursor=<value>&limit=50 (max 200). Le header X-Cursor-Next contient le cursor de la page suivante (vide si fin).
# First page
GET /v1/orders?limit=50
# Response headers
HTTP/1.1 200 OK
X-Cursor-Next: eyJzIjoib3JkXzlQazJYIiwidCI6MTc2NDA4MjMyMH0
X-Cursor-Prev:
# Next page
GET /v1/orders?limit=50&cursor=eyJzIjoib3JkXzlQazJYIiwidCI6MTc2NDA4MjMyMH01000 req/min en sandbox, 10000 req/min en production (par client_id). Headers X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset (timestamp Unix). Au-delà : 429 Too Many Requests avec header Retry-After.
# Headers returned on every API call
HTTP/1.1 200 OK
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9847
X-RateLimit-Reset: 1764082380
# When exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 27
Content-Type: application/json
{
"code": "rate_limit_exceeded",
"message": "Rate limit reached. Retry in 27 seconds.",
"request_id": "req_a3f2b9c1"
}Vue d'ensemble des routes. Les schémas détaillés (paramètres, codes d'erreur) sont dans la spec OpenAPI.
| Méthode | Chemin | Description | Scope |
|---|---|---|---|
| POST | /v1/auth/token | Échange clés client → access token Bearer. | — |
| GET | /v1/kyc/attestations/{id} | Récupère une attestation et son statut. | kyc:read |
| POST | /v1/kyc/attestations | Crée une attestation KYC signée Ed25519. | kyc:write |
| GET | /v1/sgi/partners | Liste publique des SGI partenaires agréées. | public |
| POST | /v1/orders | Soumet un ordre routé vers une SGI agréée (rcpt_to). | orders:write |
| GET | /v1/orders/{id} | État courant d'un ordre, fills inclus. | orders:read |
| POST | /v1/webhooks/endpoints | Enregistre un endpoint pour réception d'événements. | webhooks:manage |
| GET | /v1/audit/snapshots/{date} | Récupère un snapshot Merkle ancré. | audit:read |
Codes HTTP standards. Body JSON normalisé : { code, message, details, request_id }. Le request_id est à fournir au support.
# 401 Unauthorized — invalid or expired token
{ "code": "unauthorized", "message": "Access token expired", "request_id": "req_a3f2b9c1" }
# 403 Forbidden — token lacks scope
{ "code": "forbidden", "message": "Missing scope: orders:write", "request_id": "req_a3f2b9c2" }
# 404 Not Found — unknown resource
{ "code": "not_found", "message": "Order ord_unknown not found", "request_id": "req_a3f2b9c3" }
# 422 Unprocessable Entity — validation failure
{
"code": "validation_error",
"message": "qty must be a positive integer",
"details": [{ "field": "qty", "rule": "min", "value": 0 }],
"request_id": "req_a3f2b9c4"
}
# 429 Too Many Requests — see Rate limiting above
# 500 Internal Server Error — transient; retry with same Idempotency-Key
{ "code": "internal_error", "message": "An unexpected error occurred", "request_id": "req_a3f2b9c5" }