INOPAY
Retour aux développeurs

Référence API REST v1

Endpoints REST sous /v1/. Réponses JSON UTF-8. Authentification Bearer OAuth 2.0. Pagination cursor. Specification OpenAPI 3.1 complète téléchargeable.

Spécification OpenAPI 3.1

La spec complète, prête à être importée dans Postman ou Insomnia. Mise à jour à chaque release.

OpenAPI 3.1

La spec complète, prête à être importée dans Postman ou Insomnia. Mise à jour à chaque release.

Télécharger openapi.yaml

Authentification

Toutes 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.

cURL
# 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..."
Python
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()
JavaScript
// 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();

Format requête / réponse

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"
}

Idempotency

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

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=eyJzIjoib3JkXzlQazJYIiwidCI6MTc2NDA4MjMyMH0

Rate limiting

1000 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"
}

Endpoints majeurs

Vue d'ensemble des routes. Les schémas détaillés (paramètres, codes d'erreur) sont dans la spec OpenAPI.

MéthodeCheminDescriptionScope
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/attestationsCrée une attestation KYC signée Ed25519.kyc:write
GET/v1/sgi/partnersListe publique des SGI partenaires agréées.public
POST/v1/ordersSoumet 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/endpointsEnregistre un endpoint pour réception d'événements.webhooks:manage
GET/v1/audit/snapshots/{date}Récupère un snapshot Merkle ancré.audit:read
Spec OpenAPI complète

Erreurs

Codes HTTP standards. Body JSON normalisé : { code, message, details, request_id }. Le request_id est à fournir au support.

Exemple d'erreur
# 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" }