environment_id alongside organization_id. Test
data and live data are the same schema and never the same rows.
The difference between the two is what the rows mean, not what they can do.
Test is not a reduced copy: the same endpoints answer, the same webhooks fire,
the same emails send, the same limits apply. What separates them is that one
holds your real customers and the other holds whatever you needed while
building, and that nothing you do in test can be seen from live.
The credential decides
This is the rule the whole design turns on. On the machine surface, the environment is resolved from the stored key row on every request. Not from a header, not from a query parameter, not from a body field. There is no way to name one.- a live id fetched with a test key answers
404— and the 404 does not reveal that the row exists elsewhere; - a contact identified with a test key is unreachable from live;
- pointing staging at production is not a mistake you can make in a config file, because there is no field to get wrong.
GET /v1/me is the cheapest way to confirm which environment a key opens. Every
integration should call it once at start-up.The panel is the exception
Staff are authorized for the whole organization, so panel reads take the environment as an explicit view parameter:live. Anything other than live or test answers
400 invalid_request.
This is the opposite posture from the machine surface, and safe for exactly that
reason: the caller is already authorized for both environments, so letting them
choose adds no reach. A machine credential gets no such parameter, ever.
In the panel UI the active environment is a client-side view preference, marked
with the header switch and an amber banner whenever test mode is on.
The identity secret
Every environment holds an identity secret — the HMAC key your server signsexternal_id values with. It is per environment, minted on first
read, and reading it requires api_keys:write: this secret mints verified
sessions, so reading it is holding it.
501 federated_identity_unavailable when the server cannot
keep the secret encrypted safely.
What the two environments share
Almost nothing. Contacts, customers, conversations, events, subscriptions and messages are per environment, and nothing you do in test is visible from live. The exception is your help center. Articles belong to the organization, not to an environment: there is one of them, published once, and both environments read it. An article is something you wrote, reviewed and published, and it says the same thing whichever environment asks. Which matters for the AI auto-responder, because it answers from two kinds of material:
A resolved conversation is a transcript of whatever was typed while somebody was
building: a rehearsal, a widget tested with nonsense, a refund promised to a
contact that does not exist. That is knowledge about your test environment, not
about your product, so it never shapes an answer sent to a live customer — and
live’s transcripts never shape a test answer either.
A test environment with no resolved conversations of its own answers from your
published articles alone. With no published articles either, the auto-responder
records
no_knowledge, escalates the thread and leaves it in the inbox for a
person. That is the correct answer, not a misconfiguration: live’s transcripts
are live’s.