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"
}
ParameterTypeDescription
400ErrorBad request (missing/invalid parameters)
401ErrorInvalid or missing API key
403ErrorInsufficient permissions or plan
404ErrorResource not found
429ErrorRate limit exceeded (check Retry-After header)
500ErrorInternal server error (includes request_id for support)

Pagination

All list endpoints use cursor-based pagination:

ParameterTypeDescription
limitintegerItems per page (default 50, max 100)
cursorstringCursor from previous response
Example
GET /v0/payouts?limit=25&cursor=eyJpZCI6MTAwfQ

Rate Limits

PlanCredits/moRate Limit
Trial10060 req/min
Starter1,000300 req/min
Growth5,000600 req/min
Pro15,000600 req/min
ScaleCustom6,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 minute
  • X-RateLimit-Remaining — requests remaining
  • X-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.

ParameterTypeDescription
tokenstring, requiredRegistration 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.

ParameterTypeDescription
windowstringOne 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.

ParameterTypeDescription
sourcestringFilter by source: "stripe" or "shopify"
statusstring"paid", "pending", "in_transit", "failed", "canceled"
fromISO dateStart date filter
toISO dateEnd date filter
limitintegerItems per page (default 50)
cursorstringPagination 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.

ParameterTypeDescription
idstring, requiredPayout 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.

ParameterTypeDescription
fromISO dateStart date filter
toISO dateEnd date filter
limitintegerItems per page (default 50)
cursorstringPagination 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
    }
  ]
}
Plaid reports deposits as negative amounts. Use 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.

ParameterTypeDescription
payout_idstring, requiredStripe 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.

ParameterTypeDescription
statusstring"open", "resolved", "dismissed"
typestring"missing_deposit", "amount_mismatch", "timing"
limitintegerItems per page (default 50)
cursorstringPagination 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 file
  • 402 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"
}
ParameterTypeDescription
planstring, 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"
  }
}
If you already have an active subscription, this returns a billing portal URL where you can change plans.

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.

ParameterTypeDescription
idstring, requiredWebhook 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:

  1. Extract t (timestamp) and v1 (signature) from the header
  2. Build signed payload: ${timestamp}.${raw_body}
  3. Compute HMAC-SHA256 using your signing secret
  4. Compare computed signature to v1 (constant-time comparison)
  5. Check that t is 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

EventDescription
payout.matchedA payout (Stripe or Shopify) was matched to a bank deposit
payout.discrepancyA matched payout has an amount discrepancy
payout.missingA payout could not be matched after the expected window
refund.detectedA Shopify refund was detected that may affect reconciliation
balance.thresholdA 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

PlanPriceCredits/moRate LimitPlaid
TrialFree (7 days)10060/min1 included
Starter$4.99/mo1,000300/min1 included
Growth$19/mo5,000600/min1 included
Pro$49/mo15,000600/min1 included
ScaleCustomCustom6,000/minCustom

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