Build integrations against the FlowPilot treasury execution platform. Manage runs, recipients, wallet balance, transactions, and audit events programmatically.
https://api.flowpilot.club/api/v1Overview
The FlowPilot Public API lets you automate treasury operations — from creating payment runs to approving candidates and querying transaction history. All endpoints return JSON and follow standard REST conventions.
API Keys
Authenticate with scoped API keys generated in the FlowPilot dashboard.
Paginated lists
Every list endpoint returns a unified envelope with total, limit, offset, and has_more.
Scoped access
Each operation requires a specific scope, ensuring least-privilege access control.
Authentication
All API requests must include a valid API key. Keys are prefixed with fp_live_ for production and fp_test_ for test mode. Generate keys in the FlowPilot dashboard under Settings → Developer.
Passing your key
# Option 1 — dedicated header X-API-Key: fp_live_xxxxxxxxxxxxxxxxxxxx # Option 2 — Bearer token Authorization: Bearer fp_live_xxxxxxxxxxxxxxxxxxxx
Available scopes
| Scope | Description |
|---|---|
runs:read | Read runs and their metadata |
runs:write | Create new payment runs |
recipients:read | List saved recipients |
recipients:write | Create or delete saved recipients |
wallet:read | Read wallet balance and AI credit balance |
transactions:read | Query transaction history |
audit:read | Access the audit event log |
Pagination
All list endpoints accept limit and offset query parameters. The maximum value for limit is 200. Defaults to limit=50&offset=0.
{
"data": [...],
"total": 247,
"limit": 50,
"offset": 0,
"has_more": true
}Use has_more: true to determine whether additional pages exist. Increment offset by limit to fetch the next page.
Errors
FlowPilot uses standard HTTP status codes. All error responses return a JSON object with a human-readable detail field.
{
"detail": "Invalid or expired API key."
}| Status | Meaning |
|---|---|
| 401Unauthorized | Missing or invalid API key. |
| 403Forbidden | Valid key but insufficient scope for this operation. |
| 404Not Found | The requested resource does not exist. |
| 422Unprocessable | Request body failed validation — check the detail field. |
| 429Rate Limited | Too many requests. Retry after the Retry-After header value. |
Runs
A Runrepresents a single payment batch processed by FlowPilot's AI agents — from candidate scoring to disbursement. Runs move through a defined lifecycle: pending → planning → scoring → awaiting_approval → completed | failed.
/public/runsReturn a paginated list of all payment runs for your organisation. Filter by status to narrow results.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Number of records to return (default 50, max 200) |
offset | integer | No | Number of records to skip for pagination |
status | string | No | Filter by status: pending | planning | scoring | awaiting_approval | completed | failed |
Response
{
"data": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"objective": "Monthly payroll disbursement for Lagos office — June 2026",
"status": "completed",
"risk_tolerance": 0.35,
"budget_cap": 50000000.00,
"candidate_count": 142,
"created_at": "2026-06-01T08:00:00Z",
"updated_at": "2026-06-01T08:47:23Z"
}
],
"total": 24,
"limit": 50,
"offset": 0,
"has_more": false
}/public/runs/{run_id}Fetch a single run by its UUID. Returns the same shape as the list endpoint item.
Response
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"objective": "Monthly payroll disbursement for Lagos office — June 2026",
"status": "completed",
"risk_tolerance": 0.35,
"budget_cap": 50000000.00,
"candidate_count": 142,
"created_at": "2026-06-01T08:00:00Z",
"updated_at": "2026-06-01T08:47:23Z"
}/public/runs201runs:writeCreate a new payment run. The AI planning and scoring agents are triggered automatically after creation.
Request Body
{
"objective": "Vendor payment batch — Q2 suppliers",
"risk_tolerance": 0.30,
"budget_cap": 10000000.00
}Response
{
"id": "7b8a9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
"status": "pending",
"objective": "Vendor payment batch — Q2 suppliers",
"created_at": "2026-06-15T10:30:00Z"
}Candidates
Candidates are the individual payment records within a run. Each candidate has a risk score, risk decision, and approval status. Candidates can be approved or rejected via the Approvals endpoints.
/public/runs/{run_id}/candidatesReturn a paginated list of candidates belonging to the specified run. Filter by approval status to see only pending, approved, or rejected records.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Number of records to return (default 50, max 200) |
offset | integer | No | Number of records to skip for pagination |
approval_status | string | No | Filter by approval_status: pending | approved | rejected |
Response
{
"data": [
{
"id": "a1b2c3d4-e5f6-7a8b-9c0d-e1f2a3b4c5d6",
"run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"beneficiary_name": "Adaeze Okonkwo",
"account_number": "0123456789",
"institution_code": "058",
"amount": 350000.00,
"currency": "NGN",
"risk_score": 0.12,
"risk_decision": "auto_approved",
"approval_status": "approved",
"execution_status": "success"
}
],
"total": 142,
"limit": 50,
"offset": 0,
"has_more": true
}Recipients
Saved recipients let you store frequently paid beneficiaries once and reuse them across multiple payout runs. Each recipient has a name, account number, bank code, and an optional default narration note.
/public/recipientsrecipients:readReturn a paginated list of saved recipients for your organisation. Use the search parameter to filter by name or account number.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
search | string | No | Filter by name or account number (partial match) |
limit | integer | No | Number of records to return (default 50, max 200) |
offset | integer | No | Number of records to skip for pagination |
Response
{
"data": [
{
"id": "d1e2f3a4-b5c6-7d8e-9f0a-1b2c3d4e5f6a",
"name": "Adaeze Okonkwo",
"account_number": "0123456789",
"institution_code": "058",
"bank_name": "GTBank",
"narration_note": "Monthly consulting fee",
"created_at": "2026-03-01T10:00:00Z"
}
],
"total": 18,
"limit": 50,
"offset": 0,
"has_more": false
}/public/recipients201recipients:writeCreate a new saved recipient. The institution_code is the CBN-assigned bank code (e.g. 058 for GTBank).
Request Body
{
"name": "Adaeze Okonkwo",
"account_number": "0123456789",
"institution_code": "058",
"bank_name": "GTBank",
"narration_note": "Monthly consulting fee"
}Response
{
"id": "d1e2f3a4-b5c6-7d8e-9f0a-1b2c3d4e5f6a",
"name": "Adaeze Okonkwo",
"account_number": "0123456789",
"institution_code": "058",
"bank_name": "GTBank",
"narration_note": "Monthly consulting fee",
"created_at": "2026-04-17T10:00:00Z"
}/public/recipients/{recipient_id}recipients:writePermanently delete a saved recipient. This does not affect past payout runs that referenced the recipient.
Response
{
"status": "deleted",
"id": "d1e2f3a4-b5c6-7d8e-9f0a-1b2c3d4e5f6a"
}Wallet
Your FlowPilot wallet is debited on each approved payout run. Fund it via bank transfer to your virtual account in the dashboard. The wallet endpoint also returns your remaining AI credit balance.
/public/wallet/balancewallet:readReturn the current wallet balance (NGN), currency, and remaining AI credit balance for your organisation.
Response
{
"balance": 12500000.00,
"currency": "NGN",
"ai_credit_balance": 47,
"updated_at": "2026-04-17T09:45:00Z"
}Transactions
Transactions represent the actual fund movements generated by approved candidates. Each transaction has a unique reference, amount, direction, and settlement status. Requires the transactions:read scope.
/public/transactionsReturn a paginated list of all transactions. Filter by run_id to retrieve transactions for a specific payment batch, or by status to view only successful, pending, or failed transfers.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Number of records to return (default 50, max 200) |
offset | integer | No | Number of records to skip for pagination |
run_id | string (UUID) | No | Filter transactions to a specific run |
status | string | No | Filter by status: SUCCESS | PENDING | FAILED |
Response
{
"data": [
{
"id": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
"run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reference": "FP-20260601-00142",
"amount": 350000.00,
"currency": "NGN",
"direction": "debit",
"status": "SUCCESS",
"channel": "NIP",
"created_at": "2026-06-01T08:45:12Z"
}
],
"total": 142,
"limit": 50,
"offset": 0,
"has_more": true
}Audit Log
The audit log records every action taken by FlowPilot's AI agents and human operators throughout a run's lifecycle. Useful for compliance reporting and debugging. Requires the audit:read scope.
/public/auditReturn a paginated list of audit events. Filter by run_id to retrieve the full event history for a specific run, or by action to query a specific event type.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Number of records to return (default 50, max 200) |
offset | integer | No | Number of records to skip for pagination |
run_id | string (UUID) | No | Scope results to a specific run |
action | string | No | Filter by action name, e.g. scored_candidates, approved_candidate |
Response
{
"data": [
{
"id": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
"run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agent_type": "risk",
"action": "scored_candidates",
"detail": {
"candidates_scored": 142,
"flagged": 3
},
"created_at": "2026-06-01T08:22:41Z"
}
],
"total": 89,
"limit": 50,
"offset": 0,
"has_more": false
}Webhooks
FlowPilot can push real-time event notifications to any HTTPS endpoint you register. Configure webhooks from the Developer → Webhooks tab in your dashboard. A signing secret (whsec_…) is generated on creation and shown exactly once — save it immediately.
Available events
| Event | When it fires |
|---|---|
run.completed | A payout run finished and all approved candidates were processed |
run.failed | A payout run encountered a fatal error |
approval.requested | A run moved to awaiting_approval — approvers need to act |
approval.completed | A run was approved or rejected and execution resumed |
payout.succeeded | An individual payout candidate was disbursed successfully |
payout.failed | An individual payout candidate failed to disburse |
candidate.flagged | A candidate was flagged with a high risk score during scoring |
webhook.test | Sent once when the webhook is first registered, to verify reachability |
Payload structure
Every delivery is a JSON POST with the following envelope. The data field varies by event type.
{
"event": "run.completed",
"timestamp": "2026-04-14T18:30:00.000Z",
"delivery_id": "d3f4a5b6-...",
"data": {
"run_id": "a1b2c3d4-...",
"objective": "Pay 50 field agents for April",
"status": "completed",
"approved_count": 48
}
}Request headers
| Header | Description |
|---|---|
X-FlowPilot-Event | The event name, e.g. run.completed |
X-FlowPilot-Signature | HMAC-SHA256 signature of the raw request body: sha256=<hex> |
X-FlowPilot-Delivery | Unique UUID for this delivery attempt |
Content-Type | application/json |
User-Agent | FlowPilot-Webhooks/1.0 |
Verifying signatures
FlowPilot signs every payload with HMAC-SHA256using the webhook's signing secret. Always verify the signature before processing a delivery.
const crypto = require("crypto");
function verifyWebhook(rawBody, signatureHeader, secret) {
const expected = "sha256=" +
crypto.createHmac("sha256", secret)
.update(rawBody) // rawBody must be the raw Buffer, not parsed JSON
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
// Express example
app.post("/webhooks/flowpilot", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-flowpilot-signature"];
if (!verifyWebhook(req.body, sig, process.env.FLOWPILOT_WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
console.log("Received:", event.event, event.data);
res.sendStatus(200);
});import hashlib, hmac
def verify_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
# FastAPI example
@app.post("/webhooks/flowpilot")
async def handle_webhook(request: Request):
raw_body = await request.body()
sig = request.headers.get("x-flowpilot-signature", "")
if not verify_webhook(raw_body, sig, FLOWPILOT_WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
event = await request.json()
return {"ok": True}Important
- Always read the raw request body bytes for signature verification — do not parse JSON first.
- Your endpoint must return a 2xx status within 10 seconds or the delivery is counted as a failure.
- After 5 consecutive failures FlowPilot will auto-disable the webhook. Re-enable it from the dashboard once your endpoint is healthy.