SIMWAY
Partners

Authentication

One bearer header, one key, and a mode that belongs to the key rather than to the request. This page also states plainly what the IP allowlist is worth, because it is the part most often oversold.

The scheme

Every authenticated request
Authorization: Bearer simway_sk_test_ExampleKeyDoNotUse0000000000000000000000000

No custom header, no signature, no OAuth token exchange, no SDK. If you can set a header, you can call this API.

Key format

Shape
simway_sk_live_<43 base62 characters>    // live
simway_sk_test_<43 base62 characters>    // sandbox

^simway_sk_(live|test)_[0-9A-Za-z]{43}$

simway_sk_live_ExampleKeyDoNotUse0000000000000000000000000
simway_sk_test_ExampleKeyDoNotUse0000000000000000000000000
  • The sandbox prefix is simway_sk_test_test, not “sandbox”. It is the one place the two words differ.
  • 43 base62 characters is exactly 32 random bytes, or 256 bits. The width is fixed so your own configuration validation can be length-exact rather than a loose prefix check.
  • The sk_live_ shape is chosen to match what GitHub’s secret-scanning partner programme looks for, so a key committed to a public repository becomes an inbound revocation notice rather than a quiet breach.

Live and sandbox

Mode is a property of the key. Nothing about a request can change it — there is no header, no query flag and no body field. The two modes share a base URL and share every route, so the only difference between a sandbox integration and a live one is the string in the header.

Live keySandbox key
Base URL and routeshttps://api.simway.pro/partner/v1Identical
SpendsYour balanceA separate sandbox float
Reaches the supplierYesNever
Sees fixture plansNeverYes, in addition to the real catalogue
Statement rowsOne per movementNone — /ledger is always empty
Rate bandYour tier’sThe sandbox band, whatever your tier
Works while suspendedNoYes

The sandbox page covers the fixture plans and the full list of differences.

Account status, and which keys still work

Your account has a status and your key has a mode. They are different questions, and a key that is fine can still be refused because the account is not in a state to act.

Account statusLive keySandbox key
sandbox — signed up, not approved403 partner.not_liveWorks
pending — awaiting a human403 partner.not_liveWorks
liveWorksWorks
suspended403 partner.suspendedWorks
closed403 partner.closed403 partner.closed

How a key is stored, and what that means for you

The plaintext key is returned once, when it is minted, and is never stored, logged or put in an audit record. What we hold is a lookup hash for finding the row and a slow salted hash for verifying it.

Every log line and every error body we emit passes through a redactor that works on the shape of a key rather than on a list of known ones — so it redacts keys we have never seen, including one you paste into a support message by accident. That is a safety net, not a licence: do not paste keys.

Rotation

Rotation is designed to cost no downtime: a replacement is issued at the moment rotation begins, and the old key keeps working until its expiry. Both are valid in the window, so you deploy on your own schedule rather than during a cutover.

  1. Mint the replacement in the cabinet and give it a label that says which deployment it belongs to. The secret is shown once.
  2. Deploy it. Both keys work — nothing has been taken away yet.
  3. Revoke the old one, with a reason. It stops authenticating in the same request; the reason is the only account of why traffic on it stopped at 14:02.
  4. If you gave the old key an expiry instead of revoking it, it answers 401 auth.key_expired when that passes — distinguishable from every other authentication failure precisely because the fix (“use the replacement you already have”) is unguessable from “invalid”.

Revocation takes effect in the same request that performs it. A resolved credential is cached for 60 seconds as a fallback, so 60 seconds is the honest worst case if that invalidation ever failed — not the expected one. A revoked key then answers 401 auth.invalid_key, the same code as a key that never existed, so nobody can probe a list of stolen keys to find out which are real.

We record when each key was last used and from where, at most once every 60seconds per key. That is what makes “this key was last used at 09:14 from an address you do not recognise” an answerable question.

IP allowlists — and exactly what they are worth

A key can carry a list of source addresses or CIDR ranges. Empty means any source. It accepts IPv4 and IPv6, in CIDR or bare form; IPv4-mapped IPv6 forms are normalised; leading-zero octets are refused as ambiguous rather than guessed at; and an entry that cannot be parsed matches nothing rather than everything.

If a key has an allowlist and the request’s source address cannot be established, it is refused rather than waved through — 403 auth.ip_not_allowed, with the same message as an address that was simply outside the list. One message for both, because the fix is identical and a distinguishable answer would tell a caller which header to experiment with.

Failed authentication is budgeted

30 failures per 60 seconds per source address. It is checked before we look anything up and it is consumed only by failures — a successful request never spends any of it, so a healthy integration cannot reach it.

The budget is per source address, so several servers behind one NAT share it. That is known and accepted: it is the only control that works before we know who is calling.

What is checked, in what order

Cheapest and most-refusing first, which is why a bad key costs us almost nothing and a valid one costs a slow hash only on a cache miss.

  1. The source address’ failure budget.
  2. Header present, and the key’s shape.
  3. The credential cache.
  4. One indexed lookup.
  5. Revoked or expired.
  6. The slow hash, compared in constant time.
  7. Account status against the key’s mode.
  8. The key’s IP allowlist.

The last two run on the cached path as well. Everything before them answers which key is this, which never changes; those two answer may it act right now, which does — a suspension or a revoked allowlist entry bites immediately rather than at the end of a cache window.

Authentication errors

CodeStatusMeans
auth.missing401No Authorization header.
auth.invalid_key401Malformed, unknown, or revoked — one code for all three, on purpose.
auth.key_expired401Rotation finished. Switch to the replacement you were given when it began.
auth.ip_not_allowed403Outside the allowlist, or the source address could not be established.
partner.not_live403A live key on an account that has not been approved yet.
partner.suspended403Live key, account suspended.
partner.closed403Both modes refuse.

Every code, with what to do about it.