Regions for integrators
When SpeculaPro is hosted in more than one region, your workspace lives in one of them, and
your systems call that region’s address. This page tells you which address that is, what the
answers 421 and 503 mean, and describes the two small public endpoints the sign-in page uses.
If you self-host, or your SpeculaPro is a single installation, none of this applies: keep calling
the one address you have. You can tell by asking GET /api/v1/cell: a single
installation answers {"mode": "single"}.
For the idea behind regions, see Data residency and regions.
Which base URL to use
Section titled “Which base URL to use”Use your region’s own base URL for every API call: ingest, decisions and everything else. It
is the address your people sign in at once they have been sent to their region, and
GET /api/v1/cell there returns it as public_url.
Do not point integrations at the global sign-in address. It serves only the workspace look-up
and the sign-in page; every other /api/ path there answers 401 with sign in at your
workspace’s region. Calling your region directly also means your integration doesn’t depend on
the global address being up.
The region in an API key
Section titled “The region in an API key”A key created in a region carries the region’s key between spk and the prefix:
spk_gh_k3x9q2m7w4ab_Vn2…Q8 └┘ region(gh is an example.) The prefix shown in Settings → API keys includes it: spk_gh_k3x9q2m7w4ab.
Nothing else about a key changes: send it as Authorization: Bearer …, as described in
Authentication.
Keys created before regions were introduced, and every key of a single installation, have no region part and keep working where they were created.
If you run your own gateway in front of several workspaces in several regions, you can route on this part of the key without calling anything.
The answers to handle
Section titled “The answers to handle”| Status | Body | Meaning | What your system should do |
|---|---|---|---|
421 Misdirected Request |
code: wrong_region |
The key belongs to another region, or its workspace moved there | Don’t retry here. Switch to base_url and alert a person: configuration needs changing |
503 with Retry-After: 60 |
the workspace is being moved to another region; retry shortly | The workspace is between regions | Keep what you were sending and retry after the delay |
503 with Retry-After: 120 |
the workspace is read-only while it is moved to another region; try again shortly | A move is starting: reads work, changes don’t | The same |
401 |
invalid api key | As always: wrong, revoked or expired | Fix the key |
421: wrong region
Section titled “421: wrong region”{ "title": "Misdirected Request", "status": 421, "code": "wrong_region", "region": "eu", "base_url": "https://eu.speculapro.example", "detail": "this API key belongs to the eu region; send requests to https://eu.speculapro.example"}| Field | Meaning |
|---|---|
region |
The region the key belongs to |
base_url |
That region’s address. Left out when the region you called doesn’t know it; detail then reads this API key belongs to the eu region; this is not that region |
The response has the content type application/problem+json.
You get it in two situations:
- You called the wrong region. The key names another region. Nothing was looked up beyond the key’s own shape.
- Your workspace was moved. The old region answers its keys with the new region, once the key’s secret has been checked.
Treat base_url as a hint for a person, not as an instruction to follow blindly: have your system
stop and report it, and change the configured base URL deliberately. A request sent to the wrong
region was not processed: send it again to the right one.
503: the workspace is being moved
Section titled “503: the workspace is being moved”A move between regions is planned with you in advance. It has two phases:
| Phase | Reads (GET) |
Changes (POST, PUT, PATCH, DELETE) |
|---|---|---|
| Read-only | Work | 503, Retry-After: 120 |
| Unavailable | 503, Retry-After: 60 |
503, Retry-After: 60 |
Both use 503 and not a 4xx so that senders keep what they have and try again instead of
dropping it.
- Ingest. Nothing was accepted. Queue the batch and resend it; when the move is finished, resend to the new region’s base URL. Records are recognised if they arrive twice, so resending is safe.
- Decisions.
POST /api/v1/decisionsis a change and is refused in both phases. There is no answer to act on, so your payment flow needs its own fallback for the window, the same one it uses when SpeculaPro can’t be reached. See Real-time decisions.
After the move, existing keys work at the new region’s address. Their prefix still names the old region, so create new keys there and retire the old ones.
GET /api/v1/cell
Section titled “GET /api/v1/cell”Public, no authentication. The sign-in page asks it first to learn what kind of address it is on.
curl https://gh.speculapro.example/api/v1/cell{ "mode": "cell", "region": "gh", "name": "Ghana (Accra)", "public_url": "https://gh.speculapro.example", "directory_url": "https://speculapro.example"}| Field | Meaning |
|---|---|
mode |
single: one installation, and no other field is returned. cell: one region of several. directory: the global sign-in address, which returns only this field |
region |
This region’s key |
name |
This region’s name as people see it |
public_url |
This region’s base URL. Empty when the operator set none |
directory_url |
The global sign-in address. Empty when the operator set none |
The addresses and names in these examples are placeholders. Yours come from your operator.
POST /api/v1/directory/resolve
Section titled “POST /api/v1/directory/resolve”Public, no authentication, and served only at the global sign-in address. It turns a workspace slug into the region to sign in at. The sign-in page calls it for you; an integration rarely needs it.
curl -X POST https://speculapro.example/api/v1/directory/resolve \ -H "Content-Type: application/json" \ -d '{"workspace": "acme-pay"}'{ "workspace": "acme-pay", "region": "gh", "name": "Ghana (Accra)", "url": "https://gh.speculapro.example", "status": "active"}| Field | Meaning |
|---|---|
workspace |
The slug you sent, lower-cased and trimmed |
region, name, url |
The region’s key, name and base URL |
status |
active; draining (serving, takes no new workspaces); or down (the operator has declared it unavailable: don’t send people there) |
| Status | code |
Meaning |
|---|---|---|
400 |
invalid |
No workspace sent, or longer than 63 characters |
429 |
rate_limited |
Too many look-ups from your address (20 a minute unless the operator changed it). Retry-After: 60 |
503 |
no_regions |
No region is registered yet |
Errors from this endpoint have the shape {"code": "…", "error": "…"}, not the problem format the
rest of the API uses, because the directory is a separate small service.
Signing in through the API
Section titled “Signing in through the API”POST /api/v1/auth/login at a region that a workspace was moved away from answers 421 with
detail: "wrong_region"; the new region’s key is in errors[0].value and its address, when known,
in errors[0].message. The sign-in page follows it. The answer is the same for any email, whether
or not it has an account in that workspace. For any other workspace a region doesn’t
serve, sign-in fails as for a wrong password.