Developers

Flo Documentation

Everything you need to go from zero to production. SDK for on-chain primitives, REST API for off-chain data, sandbox parity on both.

01Get started#faq

FAQ

Short answers to the questions we hear most. Everything here is linked to the deeper treatment in the rest of the doc — jump straight there when the one-liner isn't enough.

What does Flo actually do?

One SDK call mints, redeems, borrows against, or bridges tokenized exposure to 8,000+ public-market instruments and a growing set of private-credit vehicles. Every token is 1:1 backed by real shares held at a regulated prime broker and issued as blockchain-based structured notes through a bankruptcy-remote Cayman SPV. See What Flo is.

SDK or REST?

On-chain primitives (mint, redeem, positions, borrow, bridge) ship as SDKs in Python and Node.js. Off-chain state (fee balances, webhook subscriptions, token metadata, customers, reconciliation) is exposed over a thin REST API. Same auth, same idempotency guarantees, same sandbox parity.

Do I need KYC to start?

No — sandbox is always open with sk_test_… keys and $1M of synthetic USDC. Live mode unlocks once your org-level KYB is approved in the dashboard. See Environments.

Does Flo run KYC on my end users?

No. Flo is not a KYC or KYB vendor — you keep your own vendor relationship (Jumio, Onfido, Persona, Sumsub, Middesk, Alloy, Veriff, or in-house). Post the resulting attestation to Flo's free attestation vaultand we'll tag it to the customer's wallet so auditors have one place to look.

Which chains are live?

Base, Arbitrum, and Ethereum. Polygon, Solana, Optimism, and HyperEVM are on the roadmap. If you mint an asset that hasn't been deployed on a given chain yet, Flo's factory auto-deploys the ERC-20 in the same transaction — see the Mint section.

What settlement currencies are supported?

USDC and USDT on every supported chain. Flo handles the stable↔stable FX if you redeem in a different currency than you minted.

How does pricing work?

A flat monthly floor per tier (billed via Stripe) plus cost-plus-markup on trades and a flat 6–9% APR band on borrow. Full breakdown at /pricing.

Is the US supported?

No — the United States is on the restricted-jurisdictions list. The interface and API are not offered to US persons; US-regulated partners route through Flo directly under Reg D / Reg S. See Security.

What happens if Flo shuts down?

Tokens remain redeemable through the SPV estate and Ankura Trust (the independent security agent). Holder claims are against the SPV — not any Flo operating entity. See SPV structure.

How do I test webhooks locally?

Three options: the flo webhooks listen CLI tunnel, the dashboard's Send test delivery button, or ngrok + a dedicated subscription. Full walkthrough at Webhooks.

Where do I report a security issue?

security@flo.finance — up to $500K for critical findings under the Flo-operated bug bounty program. Details in Support.

02Get started#overview

What Flo is

Flo is a programmable capital-markets API. One SDK call mints, redeems, borrows against, or bridges tokenized exposure to 8,000+ public-market instruments and a growing set of private-credit vehicles. Every token is 1:1 backed by real shares held at a regulated prime broker and issued as a blockchain-based structured note through a bankruptcy-remote Cayman SPV.

What ships as an SDK, what ships as REST

On-chain primitives — mint, redeem, positions, borrow, bridge — ship as typed SDKs in Python and Node.js. Off-chain state — fee balances, webhook subscriptions, token metadata, KYC records, reconciliation reports— lives only in Flo's database and is exposed over a thin REST API. Same auth, same idempotency guarantees, same sandbox parity.

Key facts

  • Chains (live): Base, Arbitrum, Ethereum. Polygon, Solana, Optimism, HyperEVM coming soon.
  • Settlement currency: USDC and USDT on every supported chain.
  • Prime brokers: Interactive Brokers (primary) and Alpaca Securities (secondary) behind a single routing layer.
  • Legal wrapper: Cayman SPV with independent director, SEC-registered BD custody, Ankura Trust as security agent.
  • Latency: p50 < 1s from mint call to on-chain confirmation on L2 routes.
  • Jurisdictions:live outside the United States. US persons are not served at Flo's layer; see Environments and Security.
03Get started#quickstart

Quickstart — your first mint in 10 minutes

Zero-to-mint on sandbox in under ten minutes. You need a wallet address (EVM), a terminal, and one of Python 3.10+ or Node 18+.

1. Grab a sandbox key

In the dashboard, head to Developers → API keys, click Create key, pick environment Sandbox, scope Full access, and copy the sk_test_…secret. Sandbox keys don't require KYC.

2. Install the SDK

pip install flo-sdk

3. Mint 10 shares of AAPL

import flo

client = flo.Client(api_key="sk_test_...", env="sandbox")

position = client.mint(
    asset="AAPL",
    quantity=10,
    settlement={"currency": "USDC", "chain": "base"},
)
print(position.id, position.tx_hash)
Sandbox tokens live on an isolated test network. The mint confirms onchain, but no real shares are held and no real balances move. Go to Sandbox for the full test matrix.

4. Ship to live

Complete KYC in the dashboard. Once approved, the Live toggle unlocks in the topbar. Swap sk_test_… for sk_live_… and env: "live", and the same call runs against a real prime-broker fill.

04Get started#sdks

SDKs — Python and Node.js

Flo ships two first-party SDKs. Both wrap every on-chain primitive, handle retries, pagination, and idempotency, and expose typed models generated from the same OpenAPI spec that backs the REST surface.

Installation

pip install flo-sdk

Initializing the client

import flo

client = flo.Client(
    api_key="sk_live_...",
    env="live",            # "sandbox" or "live"
    timeout=10.0,          # seconds; per-request, clamped to server limit
    max_retries=3,         # on 429 / 5xx with full jitter
)

Versioning

SDKs follow semver. Breaking changes land on a major. We publish release notes at Changelog. The SDK version is pinned in pyproject.toml / package.json; runtime version is also echoed on the User-Agent header for server-side analytics.

Type generation

Every resource (Mint, Redeem, Borrow, Bridge, Position) is typed end-to-end. In TypeScript, errors narrow by code:

typescripttry {
  await flo.mint({ asset: "AAPL", quantity: 10, settlement: { currency: "USDC", chain: "base" } });
} catch (err) {
  if (err instanceof Flo.LtvExceeded) {
    // type-narrowed: err.max_ltv_bps, err.requested_ltv_bps
  } else if (err instanceof Flo.RateLimited) {
    // retry_after_seconds
  } else {
    throw err;
  }
}
05Get started#sandbox

Sandbox — test without KYC

Sandbox is a full-fidelity copy of the production surface running on a parallel test network. Same endpoints, same response shapes, same webhook events. Every Startup customer gets unlimited sandbox access without KYC.

Base URLs

  • Production: https://api.flo.finance
  • Sandbox: https://sandbox.flo.finance

Sandbox-only conveniences

  • Time travel: the X-Flo-Clock header lets you advance sandbox-wall-clock up to 30 days forward on a single request. Useful for testing yield accrual and webhook replay.
  • Error injection: set X-Flo-Error: ltv_exceeded (or any documented error code) on a mutating request to deterministically trigger that failure.
  • Idempotent resets: POST /v1/sandbox/reset wipes your sandbox state back to a clean slate. No production analog.
  • Seeded assets: sandbox carries the same 8,000-ticker universe with synthetic price feeds. Every ticker has a ~$100 nominal baseline to simplify test arithmetic.

Sandbox funding

New sandbox tenants spin up with $1,000,000 of synthetic USDC at their sandbox settlement address. Drain it by minting, refill any time with POST /v1/sandbox/faucet.

Helper examples

# Sandbox helpers are only on the sandbox client.
client = flo.Client(api_key=SANDBOX_KEY, env="sandbox")

# Time travel — request-scoped header:
pos = client.positions.get("AAPL", wallet="0x...", headers={"X-Flo-Clock": "+7d"})

# Error injection:
try:
    client.mint(
        asset="AAPL",
        quantity=10,
        settlement={"currency": "USDC", "chain": "base", "wallet": "0x..."},
        headers={"X-Flo-Error": "insufficient_funds"},
    )
except flo.InsufficientFunds as e:
    print(e.available_usdc, e.required_usdc)

# Reset + faucet:
client.sandbox.reset()
client.sandbox.faucet(currency="USDC", amount=500_000)
06Get started#authentication

Authentication

Every call — SDK or REST — carries a Bearer token in the Authorization header. The SDK sets this automatically from your api_key; for raw REST, attach it yourself.

import os, uuid, httpx

r = httpx.get(
    "https://api.flo.finance/v1/tokens",
    headers={
        "Authorization": f"Bearer {os.environ['FLO_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    timeout=10.0,
)
r.raise_for_status()
tokens = r.json()

Key shapes

  • sk_live_… — production. Requires KYC approval. Must be rotated every 180 days; we auto-flag keys older than that.
  • sk_test_… — sandbox. No KYC. Unlimited, never expires, safe to commit to git in test-fixture files.

Scopes

  • full — mint, redeem, borrow, bridge, and every read. Required for mutating operations.
  • readGET only. Use for dashboards, BI jobs, and log shippers.

Rotation

Rotate from Developers → API keys. Rotation is atomic — the old key continues to work for five minutes while your deployment rolls forward. Revocation is immediate. We monitor GitHub, npm, and pastebin for leaked prefixes and auto-revoke on detection.

IP allowlisting

Available on Scale and above. Attach an IPv4 CIDR or IPv6 prefix to a key; requests from outside the allowlist return 403 ip_not_allowed without consuming rate-limit budget.

07Get started#environments

Environments — Sandbox vs Live

Every Flo account has two environments. Sandbox is always available. Live unlocks only after KYC is approved. Keys, webhooks, IDs, and settlement balances are fully separate between the two — sandbox state can never leak into live and vice-versa.

FieldTypeDescription
Base URLstringhttps://api.flo.finance vs https://sandbox.flo.finance
Key prefixstringsk_live_ vs sk_test_
SettlementbooleanReal prime-broker fills vs synthetic test network
KYC requiredbooleanYes for live · No for sandbox
Rate limitstieredPer your tier in live · effectively unlimited in sandbox
WebhooksseparateSubscribe per-environment; events don't cross over
The dashboard topbar toggle enforces this server-side too. A sk_test_… key will return 401 key_lacks_live_scope against api.flo.finance, and vice-versa.
08Core · SDK#mint

Mint — tokenize any asset

mint tokenizes a public or private market asset 1:1 and delivers the resulting ERC-20 to the settlement wallet you specify. Flo routes to the prime broker, buys the underlying, and books it against the SPV vault before the response returns.

position = client.mint(
    asset="AAPL",
    quantity=10,
    settlement={
        "currency": "USDC",
        "chain": "base",
        "wallet": "0x4a8B70fa12cC3d9d3c9b22Ac019bE40a7d11",
    },
    developer_fee={"amount_bps": 25, "destination": "0x9f2c..."},
    idempotency_key="mint_2026_04_21_0001",
)

Parameters

FieldTypeDescription
assetrequiredstringTicker — AAPL, NVDA, SPY, TLT. See /v1/tokens for the full list.
quantityrequirednumberFractional supported down to 10⁻⁶ shares. Non-fractional for some private-credit assets; the SDK validates client-side.
settlement.currencyrequired"USDC" | "USDT"Both accepted on every supported chain.
settlement.chainrequired"base" | "arbitrum" | "ethereum"Destination chain for the minted token.
settlement.walletrequiredaddressDestination EVM address. KYC-gated for non-retail assets.
developer_feeobjectOptional platform fee in bps. See Developer fees.
idempotency_keystringDedup window 24 h. SDK auto-generates a UUIDv4 if omitted.

Response

json{
  "id": "mnt_0a4921pQ9mR3nV",
  "object": "mint",
  "status": "settled",
  "asset": "AAPL",
  "quantity": "10.000000",
  "notional_usdc": "1842.05",
  "flo_fee": { "bps": 10, "amount": "1.84" },
  "cost": { "onramp": "0.18", "broker": "0.09", "spread": "0.46" },
  "contract": {
    "address": "0x7c1f...aE09",
    "auto_deployed": false
  },
  "settlement": {
    "currency": "USDC",
    "chain": "base",
    "wallet": "0x4a8B70fa12cC3d9d3c9b22Ac019bE40a7d11",
    "tx_hash": "0xa3b1...9e4c"
  },
  "settled_at": "2026-04-21T14:33:02Z"
}

Auto-deploy on first mint

Flo runs an ERC-20 factory contracton every supported chain. If you mint an asset whose token contract hasn't been deployed yet on that chain (say, the first time anyone mints NVDA on Arbitrum), Flo automatically deploys it inside the same transaction. You do nothing — no pre-deploy step, no upfront gas, no contract-address lookup before your first call.

  • The first mint for a given (asset, chain) pair pays a one-time gas premium for the CREATE2 deployment on top of the usual mint gas. Typical premium: ~180k gas on L2s, ~800k gas on Ethereum L1. Charged to the caller, not the end user.
  • Every subsequent mint for that (asset, chain) hits the normal mint gas — the contract is reused.
  • The response includes contract.address and contract.auto_deployed: true when a deployment happened, so you can cache the address or index it downstream. A separate mint.contract_deployed webhook fires the first time each contract lands on chain.
  • Deterministic addresses: contracts deploy via CREATE2 with a Flo-controlled salt, so the address for (AAPL, base) is the same no matter who triggers the first deploy. Safe to hard-code in downstream integrations after the first mint.
Want to pre-warm gas costs before your product launch? Call client.tokens.ensure_deployed(asset, chain)once per (asset, chain) pair. It's idempotent and only pays the deploy premium if the contract is missing.

State machine (sync + async)

During core market hours and on fractional-capable venues, the mint call returns status: "settled" inline. Outside core hours or on non-fractional assets the broker may fill asynchronously — in those cases the call returns quickly with an intermediate status and the rest of the lifecycle arrives via webhooks.

FieldTypeDescription
queuedintermediateAccepted by Flo, not yet routed to the broker. Typical when you call outside session hours. Webhook: mint.queued.
filledintermediateBroker acknowledged the fill; on-chain settlement tx is pending. Webhook: mint.filled.
settledterminalERC-20 credited on-chain, SPV ledger updated, reconciled. Webhook: mint.settled.
failedterminalBroker rejected or settlement reverted. Any held funds are auto-refunded to the source wallet. Webhook: mint.failed with failure_reason.

Every state carries forward the original mint.id (mnt_…) so you can poll client.mints.retrieve(id) at any time, or subscribe to the webhook lane for push updates. Trade fees are refunded automatically on failed.

Errors

  • asset_not_found — ticker not in the universe or delisted mid-session.
  • market_closed_non_fractional — this asset requires live-market fills and the venue is shut. Retry during session hours.
  • insufficient_funds— source wallet balance doesn't cover notional + cost.
  • jurisdiction_restricted — the settlement wallet resolves to a restricted jurisdiction. See Security.
09Core · SDK#redeem

Redeem — burn tokens, receive stablecoins

redeem burns any Flo token and routes a stablecoin payout to the wallet you specify. The SPV instructs the prime broker to offset the underlying at the best available price; the on-chain stablecoin payout follows once that offset fills.

Stablecoin payout is asynchronous. It does not land in the same block as the redeemcall. When there's sufficient stablecoin liquidity in the payout pool the settlement can happen inside a few blocks; when liquidity is tight, Flo fills as soon as possible — typically in under an hour. Subscribe to redeem.settled (or poll client.redeems.retrieve(id)) for the terminal state instead of blocking on the call's return.
# The redeem call returns the moment Flo accepts the burn — the stablecoin
# payout follows asynchronously. Use webhooks or a retrieve-poll loop.
result = client.redeem(
    position_id="mnt_0a4921pQ9mR3nV",
    quantity=10,
    payout={"currency": "USDT", "chain": "arbitrum", "wallet": "0x9f2c...e41a"},
)
# result.status is likely "queued" or "filled" — watch for "settled" via webhook.
FieldTypeDescription
position_idrequiredstringThe mint ID or a Flo token balance record.
quantityrequirednumberCan be less than the original mint. Partial redemption is free.
payout.currencyrequired"USDC" | "USDT"Flo does the stable↔stable swap if you redeem in a different currency than you minted.
payout.chainrequiredchainCan differ from the chain the token lives on — Flo bridges internally.
payout.walletrequiredaddressDestination for proceeds. Same KYC rules apply as at mint.

Settlement timing

FieldTypeDescription
queuedintermediateBurn accepted, broker offset instructed. Typical window under normal conditions: seconds to a few minutes.
filledintermediateBroker has offset the underlying; Flo is waiting on stablecoin liquidity in the payout pool.
settledterminalStablecoin payout landed on-chain at payout.tx_hash. Webhook: redeem.settled.
failedterminalBroker rejected the offset or the burn was unwound. Tokens are restored to the holder; webhook: redeem.failed.

In practice, queued → settled lands in well under an hour for every mainstream asset class. If the payout pool is temporarily short on the requested currency-chain pair, Flo holds the fill and releases as soon as liquidity is available — no action needed on your side.

Pricing

Redeem fees follow the schedule on the pricing page— cost + your tier's bps markup. See the tier comparison table for the current per-tier trade rate; the authoritative schedule lives there so you never have to cross-reference the docs against pricing.

10Core · SDK#positions

Positions — live NAV and yield

Read any tokenized position by token ticker and holder wallet. Returns live NAV (oracle-pegged), accrued yield net of fees, and the underlying broker attestation URL for audit trails. Free on every tier.

pos = client.positions.get("AAPL", wallet="0x4a8B...")
# or list every position your key can read
for p in client.positions.list(limit=100):
    print(p.token, p.quantity, p.nav_usdc)

Response fields

FieldTypeDescription
tokenstringTicker.
quantitydecimalFractional holding.
nav_usdcdecimalLatest NAV × quantity, in USDC.
yield.gross_apy_bpsintegerTrailing-30-day gross yield annualized, in basis points.
yield.net_apy_bpsintegerGross minus management and performance fees. Already net of Flo's take.
nav.stalebooleanTrue when the NAV tick is older than the venue's stale threshold (usually 120 s).
attestation_urlstringCurrent SPV / broker attestation report for this ticker.
11Core · SDK#bridge

Bridge — move tokens across chains

Bridge runs on LayerZero v2 with a 3-of-4 DVN quorum. Four independent Decentralized Verifier Networks attest every cross-chain message; any three must agree before the destination mint can land. Gas is pass-through; Flo takes no fee on the message itself.

bdg = client.bridge(
    token="AAPL",
    quantity=50,
    from_chain="base",
    to_chain="arbitrum",
    destination="0x4a8B...3c9D",
)

Route matrix

Live: Base ↔ Arbitrum, Base ↔ Ethereum, Arbitrum ↔ Ethereum. All routes are bidirectional. L2 ↔ L2 hops settle in ~30 seconds, L1 legs in ~1 minute.

Failure semantics

  • If the DVN quorum doesn't form within 10 minutes, the source burn is unwound automatically — no manual intervention. Webhook: bridge.reverted.
  • If the destination mint fails post-DVN (rare), the relayer retries every 60 s for 30 min before opening an incident. Source burn is not unwound in this case; tokens are recoverable via the SPV.
12Borrow · SDK#borrow-overview

Borrow overview

Post any Flo token as collateral and receive USDC or USDT in the same transaction. APR is a flat 6–9% band across every tier and every asset class; Max LTV varies by asset class. Liquidation is triggered on a 30-second TWAP, preceded by a 12-hour grace window and three webhook-driven health-factor alerts.

Max LTV and liquidation LTV by asset class

FieldTypeDescription
Large-cap equities70% / 78%AAPL, NVDA, TSLA, MSFT, S&P 500 constituents
Blue-chip ETFs75% / 82%SPY, QQQ, VTI, VOO — broad-market index ETFs
Government bonds85% / 92%TLT, SGOV, IEF — US Treasury-backed
Corporate credit65% / 75%LQD, HYG — investment-grade and high-yield corporates
Private credit40% / 55%ARCC, BXSL — BDC and direct-lending tokens
13Borrow · SDK#borrow-open

Open a borrow position

loan = client.borrow.open(
    collateral={"token": "AAPL", "quantity": 500},
    borrow={
        "amount": 42000,
        "currency": "USDC",
        "chain": "base",
        "wallet": "0x4a8B...3c9D",
    },
)

Response

json{
  "id": "brw_3hQ8mL2pV6wY",
  "status": "funded",
  "collateral": { "token": "AAPL", "quantity": "500.000000", "value_usdc": "93710.00" },
  "borrow":     { "amount": "42000.000000", "currency": "USDC", "tx_hash": "0xa3b1...9e4c" },
  "ltv_bps": 4482,
  "liquidation_ltv_bps": 7000,
  "apr_bps": 612,
  "health_factor": "1.56",
  "funded_at": "2026-04-21T14:33:02Z"
}
14Borrow · SDK#borrow-repay

Repay, top-up, close

# Partial repay
client.borrow.repay("brw_3hQ8mL2pV6wY", amount=10_000)

# Top-up collateral to raise health factor
client.borrow.top_up("brw_3hQ8mL2pV6wY", collateral={"token": "AAPL", "quantity": 50})

# Close — pays off full principal + accrued interest, releases all collateral
client.borrow.close("brw_3hQ8mL2pV6wY")
Interest accrues per block; you only pay for the blocks you held the loan. No origination fee on the platform rate; a developer-fee amount_bps can be attached for your origination markup.
15Borrow · SDK#borrow-liquidations

Liquidation mechanics

Health factor

Every open borrow position carries a health_factor— a single dimensionless number that tells you how close to liquidation the loan is. It's computed as:

texthealth_factor = (collateral_value_usdc × liquidation_ltv) / debt_usdc

Where:
  collateral_value_usdc = live NAV × collateral quantity
  liquidation_ltv       = asset-class liquidation threshold (see table below)
  debt_usdc             = outstanding principal + accrued interest
  • health_factor > 1.0 — healthy; the collateral covers the debt at the liquidation LTV.
  • health_factor == 1.0 — liquidation is imminent. Any further adverse move triggers the 30-second TWAP breach.
  • health_factor < 1.0 — underwater on the liquidation LTV. Grace window and partial liquidations kick in.

Worked example. 500 AAPL @ $187.42 posted as collateral, 42,000 USDC borrowed, large-cap liquidation LTV of 78%:

textcollateral_value = 500 × 187.42     = 93,710.00 USDC
debt             = 42,000.00 USDC
health_factor    = (93,710 × 0.78) / 42,000
                 = 73,093.80 / 42,000
                 = 1.740

AAPL would need to drop roughly 43% (to ~$107.69) before this position becomes liquidatable. Subscribe to borrow.health_low to be woken up at 1.30 / 1.15 / 1.05, well before that point.

Triggers

Liquidation triggers when the loan's LTV crosses the asset-class liquidation threshold against a 30-second TWAP, not a single tick. This protects borrowers from flash-crash wipeouts.

Grace window + health alerts

  • Webhook borrow.health_low fires at health factors 1.30, 1.15, and 1.05.
  • Once the liquidation TWAP is breached, a 12-hour grace window opens. Top up or repay during that window and no liquidation is enforced.
  • After grace, liquidators can close up to 50% of the position at a time — partial liquidations protect the borrower from a total wipeout on temporary dislocation.

Liquidation penalty

Flat 5%across every tier. The penalty accrues to the liquidation insurance fund, not Flo's P&L.

16Borrow · SDK#borrow-rates

Live rate lookup

rates = client.borrow.rates()
for r in rates:
    print(r.asset_class, r.apr, r.max_ltv)

Rates are indicative, subject to market conditions. Flo publishes an effective-APR snapshot every 60 s; SDKs cache it for 5 s client-side to avoid hot-loop blowout.

17REST · off-chain#rest-fees

Developer fees — REST

Pass a developer_feeon any mint / redeem / borrow call to accrue a platform fee to a destination wallet. Balances are ledgered in Flo's database; settlement runs daily at 00:00 UTC (minimum $10) or on-demand via REST.

# Balance by destination wallet
balances = client.fees.balance()
for b in balances:
    print(b.destination, b.accrued_usdc, b.pending_usdc, b.settled_usdc)

# Manual settle — only sweeps destinations whose accrued balance >= minimum.
result = client.fees.settle(minimum_usdc=10)
print(result.settled_count, result.total_usdc)
18REST · off-chain#rest-webhooks

Webhooks — subscribe, replay, verify

Subscribe to any lifecycle event. Deliveries are HMAC-SHA256 signed against a per-endpoint secret and a request timestamp to prevent replay. Retries use exponential backoff over ~72 hours with up to 10 attempts. Replay windows extend to 2 years on the paid add-on.

Create a subscription

sub = client.webhooks.create(
    url="https://api.your-app.com/flo-webhook",
    events=["mint.*", "redeem.*", "borrow.*", "bridge.*"],
    secret="whsec_replace_with_your_signing_secret",
)
print(sub.id, sub.url)

Event families

  • mint.*queued, filled, settled, failed, contract_deployed
  • redeem.*queued, filled, settled, failed
  • borrow.*funded, health_low, repaid, liquidated
  • bridge.*queued, sent, delivered, reverted
  • position.*nav_updated, yield_accrued
  • fee.*accrued, settled

Delivery headers

FieldTypeDescription
X-Flo-SignaturestringHMAC-SHA256 hex digest of timestamp + "." + raw_body, signed with your endpoint secret.
X-Flo-Signature-TimestampintegerUnix seconds at which Flo signed the request. Reject deliveries whose timestamp is more than 300 seconds off from your server clock — this kills replay attacks.
X-Flo-Delivery-Idstringwh_.... Persist it in your handler to deduplicate at-least-once deliveries.
X-Flo-EventstringEvent type, e.g. mint.settled. Mirrors body.type.
X-Flo-Webhook-IdstringSubscription ID. Useful when a single handler serves multiple subscriptions.

Signature verification (replay-safe)

import hmac, hashlib, time

SKEW_SECONDS = 300  # 5 minutes

def verify(body: bytes, secret: str, signature: str, timestamp: str) -> bool:
    # 1. Reject stale events to kill replay attacks.
    ts = int(timestamp)
    if abs(time.time() - ts) > SKEW_SECONDS:
        return False
    # 2. HMAC is over "${timestamp}.${raw_body}", never just the body.
    signed_payload = f"{timestamp}.".encode() + body
    expected = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# Framework-agnostic usage
# verify(request.body, "whsec_...", request.headers["X-Flo-Signature"], request.headers["X-Flo-Signature-Timestamp"])
Always use the raw request body— don't JSON-parse then re-stringify before verifying. Express users: app.use("/flo-webhook", express.raw({ type: "application/json" })). Next.js route handlers: use await req.text() before JSON.parse.

Retry schedule

When your endpoint returns a non-2xx or times out (10 s), Flo retries on this schedule with full jitter. After the 10th failed attempt (~72 hours later) the delivery is marked abandoned and appears in the dashboard as a failed delivery until you replay it manually.

FieldTypeDescription
Attempt 1t + 0sImmediate — counts as the first delivery.
Attempt 2t + 30s
Attempt 3t + 5m
Attempt 4t + 30m
Attempt 5t + 2h
Attempt 6t + 6h
Attempt 7t + 12h
Attempt 8t + 24h
Attempt 9t + 48h
Attempt 10t + 72hFinal. After this the delivery is abandoned.

Flo treats any 2xx status as a successful delivery — including 200, 201, 202, 204. A slow 200(> 10 s) still counts as timeout and triggers a retry. Respond fast, do heavy work async.

Idempotent handler pattern

At-least-once means the same event can arrive twice. Persist X-Flo-Delivery-Id on first processing and drop duplicates.

seen: set[str] = set()  # use Redis/DB in prod

async def handle(req):
    delivery_id = req.headers["X-Flo-Delivery-Id"]
    if delivery_id in seen:
        return Response(status=200)  # idempotent drop
    if not verify(req.body, SECRET, req.headers["X-Flo-Signature"], req.headers["X-Flo-Signature-Timestamp"]):
        return Response(status=401)
    seen.add(delivery_id)
    await enqueue_for_processing(json.loads(req.body))
    return Response(status=200)

Replay from the dashboard

Every delivery attempt is captured for 30 days (2 years on the add-on). From Webhooks → deliveries you can:

  • View the raw request body, headers, response status, and latency of every attempt.
  • Replay a single delivery to the original endpoint or redirect it to a different URL (useful when rolling forward a fix).
  • Bulk-replay every abandoned delivery within a time range after a rollout fixes a handler bug.

Testing webhooks locally

You have three options. In order of what we'd reach for:

  1. Flo CLI tunnelflo webhooks listen --forward-to localhost:3000/flo-webhook. Installs a short-lived endpoint on Flo's side that streams deliveries to your machine over HTTPS. Prints the effective signing secret so your verifier works unchanged. Sandbox-only.
  2. Dashboard trigger — from Webhooks pick any event type and click Send test delivery. Fires a synthetic event through the full signing + retry stack so you can exercise your handler without a real mint.
  3. ngrokngrok http 3000, paste the public URL into the subscription URL, done. Works in sandbox and live; remember to use a separate subscription so live keys never touch your laptop.
19REST · off-chain#rest-tokens

Token catalog

The /v1/tokens surface is a read-only catalog of every instrument Flo has tokenized: supply, NAV, per-chain distribution, SPV identifiers, audit URLs. Updated continuously — do not cache beyond 60 s.

# List the full universe (auto-paginates).
for token in client.tokens.list(limit=100):
    print(token.ticker, token.supply, token.nav_usdc)

# Retrieve one — full SPV record + latest attestation URL.
aapl = client.tokens.retrieve("AAPL")
print(aapl.spv_id, aapl.attestation_url)
20REST · off-chain#rest-customers

Customers & attestations

Flo does not run KYC or KYB. You use any vendor you want — Jumio, Onfido, Persona, Sumsub, Middesk, Alloy, Veriff, or your own in-house stack — and post the resulting attestationto Flo. Flo normalizes it, tags it to the customer's wallet + external ID, tamper-hashes it, and indexes it against that customer's on-platform activity. Raw PII storage is opt-in (encrypted behind a customer-managed key) — the default is hash-only. Included free in every tier.

Submit an attestation

customer = client.customers.upsert(
    external_id="acme-user-1294",
    wallet="0x4a8B70fa12cC3d9d3c9b22Ac019bE40a7d11",
    attestation={
        "kind": "kyc",
        "provider": "jumio",
        "vendor_reference": "JMO-2026-04-19-aX9k",
        "attested_at": "2026-04-19T10:22:00Z",
        "expires_at": "2028-04-19T10:22:00Z",
        "signals": ["document_verified", "liveness_verified", "sanctions_clear"],
        "attestation_hash": "0x91fe...a04c",
        "pii": "hash_only",
    },
)
print(customer.id, customer.attestation.id)

Attestation schema

FieldTypeDescription
kindrequired"kyc" | "kyb"Individual (kyc) or business (kyb). Drives reporting + dashboard filtering.
providerrequiredenum | "custom"One of jumio, onfido, persona, sumsub, middesk, alloy, veriff, or custom. Unrecognized values must use custom + a provider name in provider_name.
vendor_referencerequiredstringStable ID returned by your vendor (Jumio txn ID, Onfido check ID, Middesk business ID). Used for audit back-trace.
attested_atrequiredISO-8601When the vendor signed off. Used to compute staleness.
expires_atISO-8601Optional. Flo fires customer.attestation_expiring 30 days out and customer.attestation_expired on the day.
signalsrequiredstring[]Any of document_verified, liveness_verified, sanctions_clear, pep_check_clear, accreditation_verified, ubo_verified, source_of_funds_verified, addresses_verified. Query-filterable.
attestation_hashrequiredhex (0x…)SHA-256 of the raw attestation payload. Lets auditors prove the record wasn't tampered with.
pii"hash_only" | "encrypted"Default hash_only — Flo stores metadata + hash, never the raw payload. Switch to encrypted and include encrypted_payload to store a ciphertext blob behind your KMS key.
encrypted_payloadbase64 ciphertextOptional. Present only when pii: "encrypted". Encrypted at-rest with the key referenced by kms_key_id.
kms_key_idstringOptional AWS KMS / GCP KMS / HashiCorp Vault key alias for the encrypted blob. Flo never holds the key — decryption is always customer-driven.

Response

json{
  "id": "cus_k01m84",
  "object": "customer",
  "external_id": "acme-user-1294",
  "wallet": "0x4a8B70fa12cC3d9d3c9b22Ac019bE40a7d11",
  "attestation": {
    "id": "att_0f9a21",
    "kind": "kyc",
    "provider": "jumio",
    "vendor_reference": "JMO-2026-04-19-aX9k",
    "attested_at": "2026-04-19T10:22:00Z",
    "expires_at": "2028-04-19T10:22:00Z",
    "signals": ["document_verified", "liveness_verified", "sanctions_clear"],
    "attestation_hash": "0x91fe...a04c",
    "pii": "hash_only",
    "stored_at": "2026-04-19T10:22:04Z"
  }
}

List & query by signal

for c in client.customers.list(signal="accreditation_verified", min_notional=250_000):
    print(c.id, c.wallet, c.attestation.expires_at)

Query surface

The attestation index is designed to answer the questions auditors actually ask. A few examples you can GET directly:

  • GET /v1/customers?signal=accreditation_verified&min_notional=250000 — every customer with active accreditation whose mint history tops $250k.
  • GET /v1/customers?attestation.expires_before=2026-06-01— every attestation that'll expire before your next audit cycle, so you can chase re-verifications early.
  • GET /v1/customers?provider=sumsub&attested_after=2026-01-01 — provenance query for a specific vendor (useful when a vendor has a data-breach incident and you need exposure count).
Attestation storage is free on every tier. Flo is not in the KYC business — we're the reconciliation layer that stitches your compliance records to your on-chain activity so your auditor has one place to look.
21REST · off-chain#rest-reconciliation

Reconciliation reports

Daily cash / position break reports between the prime-broker statements and onchain supply. The reconciliation job runs at 00:00 UTC; diffs above $100 automatically page on-call and pause mints for the affected ticker until resolved.

report = client.reconciliation.daily(date="2026-04-21")
print(report.status, report.summary.diff_usdc)
for line in report.lines:
    if line.status == "break":
        print("BREAK:", line.token, line.chain, line.diff)

Response schema

json{
  "object": "reconciliation_report",
  "date": "2026-04-21",
  "status": "matched",
  "generated_at": "2026-04-22T00:04:12Z",
  "summary": {
    "tokens_checked": 8124,
    "breaks": 0,
    "total_onchain_usdc": "412,038,217.55",
    "total_broker_usdc":  "412,038,217.55",
    "diff_usdc":          "0.00"
  },
  "lines": [
    {
      "token": "AAPL",
      "chain": "base",
      "onchain_supply":   "18421.084523",
      "broker_position":  "18421.084523",
      "diff":             "0.000000",
      "nav_usdc":         "187.42",
      "onchain_value_usdc": "3,452,307.61",
      "broker_value_usdc":  "3,452,307.61",
      "status": "matched",
      "attestation_url": "https://attest.flo.finance/ATT-2026-04-21-AAPL.pdf"
    }
  ],
  "pagination": { "has_more": false, "next_cursor": null }
}

Field reference

FieldTypeDescription
dateISO dateUTC calendar day the report covers.
status"matched" | "break" | "pending"matched = every line matched; break = one or more diffs > $100; pending = job still running.
summary.breaksintegerCount of lines whose diff exceeded the $100 threshold.
summary.diff_usdcdecimalAggregate absolute-value USD difference across all lines. Should round to 0 on a clean day.
lines[].tokenstringTicker.
lines[].chainchainWhich chain this line is for. A ticker with distribution across 3 chains produces 3 lines.
lines[].onchain_supplydecimalSum of ERC-20 balances across all holder wallets at 00:00 UTC.
lines[].broker_positiondecimalBroker-of-record share count for the SPV vault at 00:00 UTC (T+0 cash-basis).
lines[].diffdecimalonchain_supply − broker_position. Positive = tokens over-issued. Negative = under-issued. Should be 0.000000.
lines[].status"matched" | "break"Per-line status. break auto-pauses mint for (token, chain) until resolved.
lines[].attestation_urlstringPDF attestation for that line from Accountable, our independent third-party attestor.
If status === "break" your affected tickers will return mint_paused_reconciliation on any new mint until operations clear the break. Redeems are not paused — holders can always exit.
22Conventions#idempotency

Idempotency

Every mutating SDK call and REST endpoint accepts an Idempotency-Key. Flo dedupes against the key for 24 hours; retries after the first successful response return the cached result verbatim, including the original tx hash.

  • Keys must be <= 128 bytes, case-sensitive.
  • If the cached response is a 5xx, the next retry re-runs the request. We do not pin transient failures.
  • Cross-environment collisions are impossible — sandbox and live have independent idempotency stores.
  • SDKs generate a UUIDv4 automatically when you don't pass one; override only when you want app-side control.
23Conventions#rate-limits

Rate limits

Per-key requests-per-second, by tier:

  • Startup — 500 RPS
  • Growth — 1,000 RPS
  • Scale — 2,500 RPS
  • Enterprise — 5,000 RPS
  • Sandbox — effectively uncapped (soft 10,000 RPS)

Responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. On a hard 429 we include Retry-After in seconds. SDKs back off automatically with full jitter up to max_retries.

Burst allowance

Every tier allows a 2× burst for the first 10 seconds of a cold window. After that, sustained traffic is shaped to the tier cap. Read endpoints (GET) do not consume the mutating-budget quota on Scale/Enterprise.

24Conventions#pagination

Pagination

All list endpoints are cursor-paginated. Pass ?limit=100&starting_after=obj_…; responses return has_more and next_cursor. Max limit is 100.

# SDK auto-paginates
for position in client.positions.list():
    ...
25Conventions#errors

Errors — Problem-JSON

Every non-2xx returns a Problem-JSON envelope (RFC 7807 + Flo extensions). The envelope carries the standard fields plus error-specific context you can switch on without string-matching.

FieldTypeDescription
typeURICanonical URL for the error code. Dereference at docs.flo.finance/errors/<code>.
titlestringShort human-readable summary.
statusintegerHTTP status (echoed for offline log analysis).
detailstringLonger human-readable explanation with concrete values where applicable.
instancepathThe request path that triggered it.
request_idstringreq_…. Include this in support tickets — it indexes every log line end-to-end.
codestringMachine-readable code — switch on this, not on the HTTP status.
retryablebooleanWhether the same request with the same idempotency key may succeed on retry.

Retry / idempotency matrix

Rule of thumb. 4xx with retryable: false is permanent — fix the request first. 5xx and 429 are transient and safe to retry with the same idempotency key. 409 idempotency_mismatch is the special case: use a fresh key, not a retry.

Full error catalog

FieldTypeDescription
unauthorized401 · permanentMissing or malformed Bearer token.
forbidden403 · permanentKey is valid but lacks scope for this resource.
key_lacks_live_scope403 · permanentUsing a sk_test_ key against api.flo.finance or vice-versa. Switch env.
ip_not_allowed403 · permanentRequest origin outside the IP allowlist attached to this key.
kyc_required403 · permanentLive operation attempted before KYC approval. Response includes kyc_status and kyc_flow_url.
jurisdiction_restricted403 · permanentSettlement wallet or requester country is on the restricted list. Response includes country and category.
asset_not_found404 · permanentTicker not in the universe or delisted mid-session.
position_not_found404 · permanentposition_id doesn't resolve on this account.
market_closed_non_fractional409 · retry-after-windowNon-fractional asset, venue is shut. Response includes next_session_at (ISO).
idempotency_mismatch409 · permanentSame Idempotency-Key with different body. Use a new key.
mint_paused_reconciliation409 · transientReconciliation break; retry after the break is resolved (status page updated).
rate_limited429 · retryableOver your tier RPS. retry_after_seconds in body, Retry-After header mirrors it.
ltv_exceeded422 · permanentRequested borrow above the asset-class max LTV. Body: max_ltv_bps, requested_ltv_bps.
health_too_low422 · permanentPosition would open underwater. Post more collateral or borrow less.
insufficient_funds422 · permanentSource wallet balance below required. Body: required_usdc, available_usdc, shortfall_usdc.
guardrail_triggered422 · retry-after-windowPer-block circuit breaker. Body: limit, limit_value, current_value, retry_after_seconds.
bridge_route_unsupported422 · permanentFlo doesn't support this (from_chain, to_chain) pair yet.
bridge_dvn_timeout504 · auto-recoveredDVN quorum didn't form in 10 min. Source burn is unwound automatically; listen for bridge.reverted. Do NOT retry the same bridge — the tokens are back on the source chain already.
internal_error500 · retryableFlo-side. Retry with the same idempotency key.
service_unavailable503 · retryableScheduled maintenance or degraded upstream. Retry with backoff.

Example bodies

rate_limited (429) — standard SDK catches this and auto-retries up to max_retries.

json{
  "type": "https://docs.flo.finance/errors/rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Exceeded 1000 RPS on key sk_live_••a19c.",
  "instance": "/v1/mint",
  "request_id": "req_0b1204",
  "code": "rate_limited",
  "retryable": true,
  "retry_after_seconds": 2,
  "limit_rps": 1000,
  "observed_rps": 1184
}

insufficient_funds (422) — permanent. Surface shortfall_usdc to your user.

json{
  "type": "https://docs.flo.finance/errors/insufficient_funds",
  "title": "Insufficient USDC to settle this trade",
  "status": 422,
  "detail": "Need 1,842.05 USDC, available 1,200.00 USDC.",
  "instance": "/v1/mint",
  "request_id": "req_0b1205",
  "code": "insufficient_funds",
  "retryable": false,
  "required_usdc":  "1842.05",
  "available_usdc": "1200.00",
  "shortfall_usdc":  "642.05"
}

guardrail_triggered (422) — per-block circuit breaker. Body names the limit you hit so you can cool down or back off.

json{
  "type": "https://docs.flo.finance/errors/guardrail_triggered",
  "title": "Mint rejected by per-block guardrail",
  "status": 422,
  "detail": "Your address exceeded mint_max_notional_per_address_per_block at block 8102443.",
  "instance": "/v1/mint",
  "request_id": "req_0b1206",
  "code": "guardrail_triggered",
  "retryable": true,
  "limit": "mint_max_notional_per_address_per_block",
  "limit_value": "250000.00",
  "current_value": "268420.50",
  "retry_after_seconds": 12
}

bridge_dvn_timeout (504) — source burn auto-unwound. The tokens are already back on the source chain by the time you see this response; do not re-submit the same bridge request.

json{
  "type": "https://docs.flo.finance/errors/bridge_dvn_timeout",
  "title": "Bridge reverted — DVN quorum did not form within 10 min",
  "status": 504,
  "detail": "Source burn has been auto-unwound. Tokens are restored on base at tx 0x9f...c1.",
  "instance": "/v1/bridge",
  "request_id": "req_0b1207",
  "code": "bridge_dvn_timeout",
  "retryable": false,
  "bridge_id": "brg_9xK4pT1mR7nZ",
  "unwind_tx": "0x9fae...c14b",
  "webhook_fired": "bridge.reverted"
}

idempotency_mismatch (409) — same key, different body. Use a fresh idempotency key.

json{
  "type": "https://docs.flo.finance/errors/idempotency_mismatch",
  "title": "Idempotency-Key already used with a different request body",
  "status": 409,
  "detail": "Key 'mint_2026_04_21_0001' was first used with a different asset or quantity.",
  "instance": "/v1/mint",
  "request_id": "req_0b1208",
  "code": "idempotency_mismatch",
  "retryable": false,
  "original_request_id": "req_0af9d5",
  "action": "Retry with a different Idempotency-Key."
}

market_closed_non_fractional (409) — wait for the session. next_session_at is an ISO timestamp in venue timezone-agnostic UTC.

json{
  "type": "https://docs.flo.finance/errors/market_closed_non_fractional",
  "title": "Underlying venue is closed and this asset requires live fills",
  "status": 409,
  "detail": "NYSE is closed; the earliest next fill window opens at 13:30 UTC.",
  "instance": "/v1/mint",
  "request_id": "req_0b1209",
  "code": "market_closed_non_fractional",
  "retryable": true,
  "asset": "NVDA",
  "venue": "NYSE",
  "next_session_at": "2026-04-22T13:30:00Z",
  "seconds_until_session": 41280
}

kyc_required (403) — hand the user kyc_flow_url for a hosted flow, or call /v1/customers to create a link programmatically.

json{
  "type": "https://docs.flo.finance/errors/kyc_required",
  "title": "Live operation requires an approved KYC record",
  "status": 403,
  "detail": "Your organization has not completed KYC. Sandbox remains available.",
  "instance": "/v1/mint",
  "request_id": "req_0b120a",
  "code": "kyc_required",
  "retryable": false,
  "kyc_status": "not_started",
  "kyc_flow_url": "https://dashboard.flo.finance/kyc"
}
Type-narrow on code, not on HTTP status. Two 422s can need very different UX — ltv_exceeded needs a collateral suggestion, insufficient_funds needs a top-up CTA, guardrail_triggered is a backoff.
26Conventions#versioning

Versioning

The URL carries the current major version (/v1/…). Breaking changes cut a new major; we commit to supporting the previous major for 18 months after a new one ships. Pin your SDK version in production — we publish non-breaking additions weekly.

Opt into dated non-breaking variants via the Flo-Version header (ISO date). When a header is absent, your account is pinned to the version on the first successful live call; you can upgrade the pin from the dashboard.

Security posture

Flo enforces defense-in-depth across four boundaries: API edge, SDK guardrails, on-chain contracts, and prime-broker custody. Each boundary is independently auditable and independently pauseable.

  • API edge: IP geofencing, sanctions screening, KYC attestation, rate limiting, and the per-block guardrails below.
  • Onchain: LayerZero v2 with a 3-of-4 DVN quorum, 72-hour timelock on parameter changes, single-member pause (any security council member), 4-of-7 multisig unpause.
  • SPV: Cayman SPV with a trust-like shareholding arrangement and an independent director on the board; Ankura Trust holds first-priority security interest.
  • Custody: segregated omnibus at IBKR + Alpaca; daily onchain supply ≤ custodied underlying, attested monthly.

Per-block guardrails

Every mint, redeem, borrow, and bridge call passes through a set of conservative circuit breakers. Exact thresholds live on the Trust page and are tuned per-asset; the categories below are the shape of what's enforced. A request that would breach any breaker is rejected with guardrail_triggered.

Mint

  • mint_max_notional_per_address_per_block — per-wallet notional cap within a single block. Prevents fat-finger and MEV-style stuffing.
  • mint_max_total_per_block — platform-wide cap per block. Protects prime-broker fill capacity.
  • mint_min_per_tx — floor on gas-wasting dust (e.g. $10).
  • mint_daily_notional_per_asset — rolling-window cap per ticker; gates concentration.

Redeem

  • redeem_max_per_block — mirrors the mint cap; prevents run-on-the-bank behavior under stress.
  • redeem_cool_down — optional per-wallet cooldown when the daily redemption ratio exceeds a tunable threshold; configurable by the API customer at Scale and above.

Borrow

  • borrow_max_notional_per_address — concentration cap per wallet.
  • borrow_max_total_outstanding— systemic cap on the borrow book. Protects the liquidation engine's ability to unwind in adverse markets.
  • Max LTV per asset class is published in Borrow overview.

Bridge

  • bridge_min_amount_per_tx — dust floor.
  • bridge_max_amount_per_tx — per-message risk cap inside the 3-of-4 DVN quorum.
  • bridge_max_total_per_block — per-route rate limit.
Exact values are published as order-of-magnitude bands to enterprise partners under NDA. If you hit a guardrail in production, the error body includes the limit you breached.
29Security#security-dvn

LayerZero 3-of-4 DVN

Every bridge message is attested by four independent Decentralized Verifier Networks. Any three must agree before the destination mint can land. No single verifier can sign a fraudulent bridge. If the quorum doesn't form within 10 minutes, the source burn is auto-unwound and bridge.reverted fires.

DVN providers

  • LayerZero Labs (default)
  • Google Cloud
  • Polyhedra
  • Nethermind
Enterprise accounts can pin a custom 3-of-5 or 4-of-6 configuration with an additional verifier of their choosing. Contact enterprise@flo.finance.
30Security#security-spv

SPV structure & legal wrapper

  • Operating entity: Flo Technologies Ltd., incorporated in the British Virgin Islands.
  • Issuance vehicle: bankruptcy-remote SPV incorporated in the Cayman Islands, with a trust-like shareholding arrangement and an independent director on the board. Single-purpose note issuance charter; perfected security interests are current and enforceable; rotated monthly for attestation.
  • Security agent: Ankura Trust — independent, holds first-priority perfected security interest, authorized to initiate liquidation on LTV breach.
  • Holder claim: direct legal claim on the underlying asset inside the SPV — not a claim on any Flo operating entity. If Flo shuts down, tokens remain redeemable through the SPV estate and Ankura.

Custody & attestations

Underlying securities are held in segregated omnibus at Interactive Brokers and Alpaca Securities. SIPC coverage applies on the IBKR side ($500K). Daily reconciliation guarantees that onchain supply never exceeds custodied underlying; violations pause mints automatically and page on-call.

Audit cadence

  • Smart contract audits: Sherlock, Hellborn, and Cantina — three independent firms. Every contract audited, zero critical findings to date.
  • Formal verification: mint / redeem / bridge logic mathematically proven correct.
  • Reserve attestation: monthly, issued by Accountable — our sole independent attestor.
  • Proof of Reserves: Merkle-tree-based, onchain contract. Root recomputable from a JSON leaf.
  • SOC 2 Type II: annual; currently in progress.
32Operations#status

System status

Live uptime, per-region latency, and incident feed at status.flo.finance. Incidents post within 60 seconds of detection. Historical uptime for the past 90 days is published per component (mint, redeem, borrow, bridge, webhooks, dashboard).

SLAs: Startup 99.5% best-effort · Growth 99.9% · Scale 99.95% + credits schedule · Enterprise 99.99% + custom credits. Credits auto-apply to the next invoice when downtime crosses a threshold.
33Operations#changelog

API & SDK changelog

Non-breaking additions ship weekly; breaking changes cut a new major with 18 months of parallel support. Every rate and product change is logged at the pricing changelog; the SDK release notes and API changelog are mirrored on GitHub:

34Operations#postman

Postman collection

Every REST endpoint is pre-baked in our Postman collection with sandbox base URLs and example bodies. Swap the key env var and hit Send — no SDK install required for exploratory work.

  • Download collection — or paste into Postman Web via Import → Link: https://flo.finance/postman/flo-api.postman_collection.json.
  • Download environment — switches between sandbox and production with one env flip.
35Operations#support

Support & contact

Missing something? Open an issue on GitHub or email developers@flo.finance.