Test mode

om_<data|exec>_test_… · same base URL · "mode": "paper"

A test key reads the same real markets a live key reads, and cannot move real money. There is no separate environment to point at — same base URL, same data, a different credential.

What test mode is

Console → API keys → Test

Switch the console to Test and mint a key; it comes back as om_exec_test_… (or om_data_test_…). It reads exactly what your live key reads — the same leagues, contests, order books, depth and arbitrage — because building against a thin copy of the catalog would tell you nothing useful. What it cannot do is spend money, connect a real venue, or create a real user.

There is no sandbox host. Point at the same base URL and change the key.

A key's environment is fixed when it is minted. Going live means minting a live key, not promoting this one — a credential you have been pointing at a sandbox should never quietly start moving real money.

Orders: say paper explicitly

A test key places paper orders only. It will reject a leg that asks for a real fill — and it also rejects a leg that just leaves mode out.

{
  "orders": [
    {
      "liquidity_hash": "kalshi:...",
      "amount": 25,
      "max_price": 0.62,
      "mode": "paper"
    }
  ]
}

The omission is rejected rather than assumed, because on a live key that same body is a real-money order — mode defaults to "real". Silently rewriting it here would teach you the field is optional at exactly the point where it stops being.

Paper fills anchor to the best live price at a real venue and settle through the same resolution path as a real order, so what you see is the shape of a genuine fill. Balances are in ATLAS, the practice currency — see Practice wallet.

mode and environment are different axes. A live key may also place paper orders — that is the practice wallet, and it is real. Test mode constrains mode; mode never implies environment.

Connect: one venue, no credentials

A link session minted with a test key is a sandbox session. It offers exactly one venue — OpenMarkets Practice — which needs no credentials at all, so you can walk the entire hosted flow without a Kalshi or Polymarket account. Your branding, your requested scopes and your link mode all still apply; those are the parts you are testing.

Sandbox sessions also accept localhost return URLs without registering them first, so your first redirect works before you have configured anything:

curl -X POST https://api.openmarkets.ai/flow/v1/connect/link-sessions \
  -H "X-API-Key: om_exec_test_..." \
  -H "Content-Type: application/json" \
  -d '{"external_user_id":"dev_user_1","return_url":"http://localhost:3000/connected"}'

The hosted page shows a test-mode banner. It is otherwise identical to what your real users see, which is the point — and also why it has to announce itself.

Your test users are separate people

Users provisioned with a test key live alongside your real ones in the same workspace, under the same plan and the same Connect flow — but they are a separate set. The same external_user_id can exist once in each.

That matters at launch. Testing with user_1 does not consume the identity your real user_1 will need; provisioning them later creates a genuinely new account rather than handing a real person your test data.

Reads follow the credential. GET /flow/v1/connect/users with a test key lists your test users; with a live key, your real ones. The response shape is identical, so the reporting you build against test mode is the reporting you get in production. The console's Live / Test switch does the same for everything it shows.

Webhooks: one endpoint per environment

Console → organization → Connect → Webhooks

Webhook endpoints carry an environment, and an event is delivered by the environment of the user it is about. Register your staging consumer as a test endpoint and your production consumer as a live one, and neither ever sees the other's events. Endpoints are managed in the console, under your organization's Connect settings.

A test endpoint gets its own signing secret, shown once at registration. It is deliberately not the same secret as your live endpoint: a staging secret ends up in a repo or a chat thread far more often than a production one, and it must not verify production traffic.

Webhook URLs must be https in both environments — every delivery is signed, and plaintext would put the secret on the wire. Use a tunnel (ngrok or similar) to reach your machine. Return URLs are different: localhost is fine there, because a return URL is a redirect your own browser follows, while a webhook is a request we make outward.

Three console tools make webhooks debuggable without waiting for a user to do something:

  • Send a test event — a canned event through the real send path, so a signature that verifies here verifies in production.
  • The delivery log — every attempt, with the exact bytes we signed, which is what you need when verification fails.
  • Redeliver — send one again, re-signed with your current secret.

Verification itself is documented in the Connect integration guide.

Going live

Nothing to migrate. Mint a live key and change the credential.

  • Your Connect flow, branding and scopes are already the ones you tested.
  • Register your production return URL and a live webhook endpoint.
  • Your test users and their orders stay where they are, out of your live reads — keep the test key and keep building against it.
Once you are live, an order with no mode is a real-money order. That is the one behaviour that changes with the credential, and it is why test mode makes you write "paper" out in full.