API Reference
Base URL: https://developer.usepopup.com/api/v0
Authentication
All endpoints (except registration) require a Bearer token in the Authorization header:
Authorization: Bearer fc_live_your_key_here
fc_live_* — production keys
fc_test_* — sandbox keys (free, no credits consumed)
Keys are shown once at creation and stored as SHA-256 hashes. They cannot be retrieved later.
Response Envelope
Every response follows this structure:
{
"data": { ... },
"meta": {
"request_id": "req_abc123",
"timestamp": "2026-03-05T10:00:00Z",
"version": "v0",
"credits_remaining": 847
},
"errors": []
}List endpoints include pagination in meta:
{
"meta": {
"request_id": "req_abc123",
"cursor": "eyJpZCI6MTAwfQ",
"has_more": true
}
}Error Format
Errors follow RFC 7807 (Problem Details for HTTP APIs):
{
"type": "https://developer.usepopup.com/errors/invalid-api-key",
"title": "Invalid API Key",
"status": 401,
"detail": "The API key provided is not valid or has been revoked.",
"instance": "/v0/balance"
}| Parameter | Type | Description |
|---|---|---|
| 400 | Error | Bad request (missing/invalid parameters) |
| 401 | Error | Invalid or missing API key |
| 403 | Error | Insufficient permissions or plan |
| 404 | Error | Resource not found |
| 429 | Error | Rate limit exceeded (check Retry-After header) |
| 500 | Error | Internal server error (includes request_id for support) |
Pagination
All list endpoints use cursor-based pagination:
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Items per page (default 50, max 100) |
| cursor | string | Cursor from previous response |
GET /v0/payouts?limit=25&cursor=eyJpZCI6MTAwfQ
Rate Limits
| Plan | Credits/mo | Rate Limit |
|---|---|---|
| Trial | 100 | 60 req/min |
| Starter | 1,000 | 300 req/min |
| Growth | 5,000 | 600 req/min |
| Pro | 15,000 | 600 req/min |
| Scale | Custom | 6,000 req/min |
1 credit = 1 API request. Unused paid credits never expire. Webhook deliveries and sandbox requests are free.
Rate limit headers on every response:
X-RateLimit-Limit— requests allowed per minuteX-RateLimit-Remaining— requests remainingX-RateLimit-Reset— UTC epoch seconds when the window resets
Endpoints
Auth
POST/v0/auth/register
Create a new FlowCheck account. No authentication required.
Request body:
{
"email": "dev@example.com"
}Response (200):
{
"data": {
"token": "reg_abc123",
"checkout_url": "https://developer.usepopup.com/checkout?token=reg_abc123",
"expires_at": "2026-03-05T11:00:00Z"
}
}GET/v0/auth/register/status
Check registration status and retrieve API key after checkout.
| Parameter | Type | Description |
|---|---|---|
| token | string, required | Registration token from /v0/auth/register |
Response (200):
{
"data": {
"status": "active",
"api_key": "fc_test_abc123...",
"plan": "trial",
"credits_remaining": 100,
"trial_expires_at": "2026-03-12T10:00:00Z"
}
}api_key is only included when status is "active" and only on the first retrieval.Balance & Cash Flow
GET/v0/balance
Get current Stripe and bank balances in a single call.
Response (200):
{
"data": {
"stripe": {
"available": 142350,
"pending": 28900,
"currency": "usd"
},
"bank": {
"available": 138200,
"current": 145000,
"currency": "usd",
"institution": "Chase",
"last_updated": "2026-03-05T09:30:00Z"
}
}
}All amounts are in cents (USD).
GET/v0/cashflow
Get daily inflow/outflow breakdown.
| Parameter | Type | Description |
|---|---|---|
| window | string | One of "7d", "30d", "90d" (default "30d") |
Response (200):
{
"data": {
"window": "30d",
"total_inflows": 523400,
"total_outflows": 187600,
"net": 335800,
"daily": [
{
"date": "2026-03-05",
"inflows": 18200,
"outflows": 4300,
"net": 13900
}
]
}
}Payouts
GET/v0/payouts
List payouts from Stripe and Shopify with reconciliation status.
| Parameter | Type | Description |
|---|---|---|
| source | string | Filter by source: "stripe" or "shopify" |
| status | string | "paid", "pending", "in_transit", "failed", "canceled" |
| from | ISO date | Start date filter |
| to | ISO date | End date filter |
| limit | integer | Items per page (default 50) |
| cursor | string | Pagination cursor |
Response (200):
{
"data": [
{
"id": "po_abc123",
"source": "stripe",
"stripe_payout_id": "po_3k9xMnR2v7Lw",
"shopify_order_id": null,
"shopify_order_name": null,
"amount": 15000,
"currency": "usd",
"status": "paid",
"arrival_date": "2026-03-03",
"created_at": "2026-03-01T10:00:00Z",
"reconciliation": {
"status": "matched",
"confidence": 95,
"matched_transaction_id": "txn_xyz789"
}
},
{
"id": "po_shopify_1",
"source": "shopify",
"stripe_payout_id": null,
"shopify_order_id": "5678901234",
"shopify_order_name": "#1042",
"amount": 7500,
"currency": "usd",
"status": "paid",
"arrival_date": "2026-03-02",
"created_at": "2026-03-01T14:00:00Z",
"reconciliation": {
"status": "matched",
"confidence": 88,
"matched_transaction_id": "txn_abc456"
}
}
],
"meta": {
"cursor": "eyJpZCI6MTAwfQ",
"has_more": true
}
}GET/v0/payouts/:id
Get a single payout with matched bank transaction details.
| Parameter | Type | Description |
|---|---|---|
| id | string, required | Payout ID (Stripe or Shopify) |
Response (200):
{
"data": {
"id": "po_abc123",
"amount": 15000,
"net_amount": 14850,
"fee_amount": 150,
"currency": "usd",
"status": "paid",
"arrival_date": "2026-03-03",
"created_at": "2026-03-01T10:00:00Z",
"description": "STRIPE PAYOUT",
"reconciliation": {
"status": "matched",
"confidence": 95,
"matched_transaction": {
"id": "txn_xyz789",
"amount": -148.50,
"date": "2026-03-03",
"description": "STRIPE TRANSFER",
"institution": "Chase"
}
}
}
}Transactions
GET/v0/transactions
List bank transactions from Plaid.
| Parameter | Type | Description |
|---|---|---|
| from | ISO date | Start date filter |
| to | ISO date | End date filter |
| limit | integer | Items per page (default 50) |
| cursor | string | Pagination cursor |
Response (200):
{
"data": [
{
"id": "txn_xyz789",
"amount": -148.50,
"date": "2026-03-03",
"description": "STRIPE TRANSFER",
"category": "Transfer",
"institution": "Chase",
"account_id": "acc_123",
"pending": false
}
]
}Math.abs(amount) for comparison.Reconciliation
GET/v0/reconcile/summary
Get 30-day reconciliation health score.
Response (200):
{
"data": {
"health_score": 92,
"period_start": "2026-02-03",
"period_end": "2026-03-05",
"total_payouts": 47,
"matched": 43,
"unmatched": 3,
"pending": 1,
"total_payout_amount": 823400,
"matched_amount": 789200,
"open_discrepancies": 2
}
}GET/v0/reconcile/:payout_id
Get detailed reconciliation for a specific payout.
| Parameter | Type | Description |
|---|---|---|
| payout_id | string, required | Stripe payout ID |
Response (200):
{
"data": {
"payout_id": "po_abc123",
"payout_amount": 15000,
"status": "matched",
"confidence": 95,
"match_details": {
"amount_score": 40,
"date_score": 30,
"description_score": 20,
"bank_id_score": 5,
"total_score": 95
},
"matched_transaction": {
"id": "txn_xyz789",
"amount": -148.50,
"date": "2026-03-03",
"description": "STRIPE TRANSFER"
},
"candidates": []
}
}Confidence scoring: amount (40pts), date proximity (30pts), description match (20pts), bank ID (10pts). Auto-match at 80+ with 10pt gap to runner-up.
Discrepancies
GET/v0/discrepancies
List open discrepancies: missing deposits, amount mismatches, timing issues.
| Parameter | Type | Description |
|---|---|---|
| status | string | "open", "resolved", "dismissed" |
| type | string | "missing_deposit", "amount_mismatch", "timing" |
| limit | integer | Items per page (default 50) |
| cursor | string | Pagination cursor |
Response (200):
{
"data": [
{
"id": "disc_abc",
"type": "missing_deposit",
"status": "open",
"payout_id": "po_abc123",
"payout_amount": 15000,
"expected_date": "2026-03-03",
"description": "Stripe payout of $150.00 has no matching bank deposit after 5 business days.",
"created_at": "2026-03-05T10:00:00Z"
}
]
}Agent
GET/v0/agent/position
Financial summary designed for AI agents and automated systems.
Response (200):
{
"data": {
"as_of": "2026-03-05T10:00:00Z",
"balances": {
"stripe_available": 142350,
"stripe_pending": 28900,
"bank_available": 138200
},
"last_7_days": {
"payouts_count": 12,
"payouts_total": 187400,
"matched": 11,
"unmatched": 1,
"inflows": 203400,
"outflows": 52100
},
"health_score": 92,
"open_discrepancies": 2
}
}GET/v0/agent/alerts
Active alerts and discrepancies in agent-friendly format.
Response (200):
{
"data": {
"alerts": [
{
"id": "disc_abc",
"severity": "high",
"type": "missing_deposit",
"summary": "Payout of $150.00 (po_abc123) has no matching bank deposit after 5 days.",
"payout_id": "po_abc123",
"amount": 15000,
"created_at": "2026-03-05T10:00:00Z"
}
],
"total": 2,
"high": 1,
"medium": 1,
"low": 0
}
}Integrations
POST/v0/connect/stripe
Connect a Stripe account using a restricted API key.
Request body:
{
"restricted_key": "rk_live_..."
}The restricted key must have read access to Payouts and Balance. FlowCheck encrypts it with AES-256-GCM before storage.
Response (200):
{
"data": {
"connected": true,
"stripe_account_id": "acct_abc123",
"permissions": ["payouts.read", "balance.read"]
}
}POST/v0/connect/shopify
Connect a Shopify store. Orders and payouts will be synced automatically.
Request body:
{
"shop": "my-store.myshopify.com",
"access_token": "shpat_..."
}The access token must have read access to Orders and Payouts. FlowCheck encrypts it with AES-256-GCM before storage.
Response (200):
{
"data": {
"connected": true,
"shop": "my-store.myshopify.com",
"connected_at": "2026-03-05T10:00:00Z"
}
}POST/v0/connect/plaid/link-token
Create a Plaid Link token to initiate bank connection in the browser.
Response (200):
{
"data": {
"link_token": "link-sandbox-abc123",
"expiration": "2026-03-05T14:00:00Z"
}
}Use this token with the Plaid Link SDK in the browser to let the user select their bank.
POST/v0/connect/plaid/exchange
Exchange a Plaid public token (from Link) for a permanent access token.
Request body:
{
"public_token": "public-sandbox-abc123"
}Response (200):
{
"data": {
"connected": true,
"institution": "Chase",
"account_name": "Checking ****1234"
}
}Sync
POST/v0/sync
Trigger a full sync of all connected integrations and run the reconciliation engine. Rate limited to 1 sync per 5 minutes per tenant.
Request body: None required.
Response (200):
{
"data": {
"synced": ["stripe", "plaid"],
"skipped": ["shopify"],
"stripe": { "payouts_synced": 3 },
"shopify": { "orders_synced": 0, "refunds_detected": 0 },
"plaid": { "added": 7, "modified": 1, "removed": 0 },
"reconciliation": { "matched": 2, "issues_detected": 1 }
}
}synced lists integrations that were synced. skipped lists integrations not connected. At least one integration must be connected.
Billing
POST/v0/billing/topup
Buy 100 API credits for $5. Charges the card on file immediately.
Request body: None required.
Response (200):
{
"data": {
"credits_added": 100,
"credits_remaining": 947,
"amount_charged": 500,
"currency": "usd",
"invoice_id": "in_1abc..."
}
}Errors:
400 missing_parameter— No payment method on file402 internal_error— Payment declined
POST/v0/billing/upgrade
Create a Stripe Checkout session to upgrade your plan. Works at 0 credits (no credit deducted).
Request body:
{
"plan": "starter"
}| Parameter | Type | Description |
|---|---|---|
| plan | string, required | "starter", "growth", or "pro" |
Response (200):
{
"data": {
"checkout_url": "https://checkout.stripe.com/c/pay/cs_...",
"plan": "starter",
"credits": 1000,
"price_monthly": "$4.99"
}
}Webhooks
GET/v0/webhooks
List registered webhook endpoints.
Response (200):
{
"data": [
{
"id": "wh_abc123",
"url": "https://example.com/webhooks/flowcheck",
"events": ["payout.matched", "discrepancy.created"],
"active": true,
"created_at": "2026-03-01T10:00:00Z"
}
]
}POST/v0/webhooks
Register a new webhook endpoint. URL must be HTTPS.
Request body:
{
"url": "https://example.com/webhooks/flowcheck",
"events": ["payout.matched", "discrepancy.created", "balance.threshold"]
}Response (201):
{
"data": {
"id": "wh_abc123",
"url": "https://example.com/webhooks/flowcheck",
"events": ["payout.matched", "discrepancy.created", "balance.threshold"],
"signing_secret": "whsec_abc123...",
"active": true
}
}DELETE/v0/webhooks/:id
Remove a webhook endpoint.
| Parameter | Type | Description |
|---|---|---|
| id | string, required | Webhook endpoint ID |
Response (200):
{
"data": {
"deleted": true
}
}Webhook Signatures
FlowCheck signs webhook payloads using HMAC-SHA256 (same pattern as Stripe):
FlowCheck-Signature: t=1709640000,v1=5257a869...
Verification steps:
- Extract
t(timestamp) andv1(signature) from the header - Build signed payload:
${timestamp}.${raw_body} - Compute HMAC-SHA256 using your signing secret
- Compare computed signature to
v1(constant-time comparison) - Check that
tis within 5 minutes of current time to prevent replay attacks
Retry schedule: immediate, 5m, 30m, 2h, 8h, 24h (max 6 attempts). After 6 consecutive failures, the endpoint is automatically disabled.
Webhook Events
| Event | Description |
|---|---|
| payout.matched | A payout (Stripe or Shopify) was matched to a bank deposit |
| payout.discrepancy | A matched payout has an amount discrepancy |
| payout.missing | A payout could not be matched after the expected window |
| refund.detected | A Shopify refund was detected that may affect reconciliation |
| balance.threshold | A balance crossed a configured threshold |
Credit System
Every API response includes meta.credits_remaining so you can monitor your balance programmatically.
When credits reach zero, the API returns a 402 with actionable next steps based on your plan:
Trial / expired users (no subscription):
{
"data": {
"actions": {
"upgrade": {
"method": "POST",
"url": "/v0/billing/upgrade",
"body": { "plan": "starter | growth | pro" },
"plans": [
{ "id": "starter", "credits": 1000, "price_monthly": "$4.99" },
{ "id": "growth", "credits": 5000, "price_monthly": "$19" },
{ "id": "pro", "credits": 15000, "price_monthly": "$49" }
]
}
}
},
"errors": [{
"status": 402,
"code": "credits_exhausted",
"detail": "Your credit balance is zero. Upgrade your plan via POST /v0/billing/upgrade to continue."
}]
}Paid users (active subscription):
{
"data": {
"actions": {
"topup": {
"method": "POST",
"url": "/v0/billing/topup",
"description": "Add 100 credits for $5.00"
}
}
},
"errors": [{
"status": 402,
"code": "credits_exhausted",
"detail": "Your credit balance is zero. Top up via POST /v0/billing/topup or at /dashboard/billing."
}]
}Both POST /v0/billing/upgrade and POST /v0/billing/topup work programmatically. The upgrade endpoint works even at 0 credits.
Pricing
| Plan | Price | Credits/mo | Rate Limit | Plaid |
|---|---|---|---|---|
| Trial | Free (7 days) | 100 | 60/min | 1 included |
| Starter | $4.99/mo | 1,000 | 300/min | 1 included |
| Growth | $19/mo | 5,000 | 600/min | 1 included |
| Pro | $49/mo | 15,000 | 600/min | 1 included |
| Scale | Custom | Custom | 6,000/min | Custom |
Credit top-up: 100 credits for $5 (via API or dashboard). Unused paid credits never expire. Each plan has a max credit balance of 6x the monthly allocation. Additional Plaid connections: $1/mo each. Webhook deliveries are free. Sandbox usage is always free.
Support
Email us at support@usepopup.com. Include your request_id from the response meta for faster debugging.
SDK and MCP server source: github.com/eliaskress