Going Live
Before you put m8tes in front of real users, set the guardrails that keep one of your end-users from draining your budget, exhausting compute, or seeing another end-user's data. This page is the production checklist.
The multi-tenancy boundary
Two identities matter, and they are not the same thing:
Every agent, run, task, and memory you create with a user_id is scoped to that end-user. Reads are strict: a run scoped to user_id="alice" can only see alice's data, never bob's, and never your account-level data created with no user_id. There is no fallback and no opt-out: when user_id is set, only that end-user's data is visible.
Cap each end-user so one can't drain the account
Your account has a plan/budget. Without a per-end-user cap, a single end-user can consume all of it. Set sub-caps so usage is shared fairly:
When an end-user hits a cap, the run is rejected with a 402:
The error code is END_USER_RUN_LIMIT_REACHED or END_USER_COST_LIMIT_REACHED. Your account-level plan limit still applies on top; the sub-cap only bounds each individual end-user.
Concurrency
Concurrent in-flight runs are capped per account to protect shared compute. When you're at the cap, new runs are rejected with a 429:
Treat it as back-pressure: wait for in-flight runs to finish, then retry. The SDK already retries 429s with backoff.
API key hygiene
- Never ship a key to a browser or to your end-users. Any key grants full account access. Keep it server-side and pass
user_idto isolate your end-users. - One named key per environment. Give production, staging, and each CI job its own key, so revoking one never takes down another.
- Rotate if a key may have leaked. The old secret dies immediately; the new one is returned once and is not retrievable later.
A named key is a labelled, independently revocable key with an optional expiry. Create one per environment:
Two failures will bite you in production, so handle them:
The 403 blocks minting and rotation only. Listing and revoking stay open on an unclaimed account.
Named-key details: expiry, revocation, listing
nameis required, 1 to 100 characters, trimmed.expires_in_daysis optional and must be between 1 and 3650.activeisfalsewhen the key is revoked or expired. An expired key stops authenticating on its own; you do not have to revoke it.list()returns all named keys, revoked ones included, newest first, capped at 100 rows. Filter onactiveif you only want live keys.last_used_atis stamped on authenticated requests but debounced to at most one write per minute per key, so treat it as a recency signal, not an exact timestamp.rotate(key_id)keeps the id, name, and expiry, and resetslast_used_at. Rotating a revoked key returns409: revocation is terminal, create a new key instead.revoke(key_id)is idempotent. A key id belonging to another account returns404.- The 50-key cap counts active keys only, so revoked keys never consume slots.
The account's default key
Every account also has one legacy default key, managed without an id. It authenticates exactly like a named key and is unaffected by named-key operations:
client.keys.revoke() does not touch named keys, and client.keys.revoke(key_id) does not touch the default key. Rotating the default key is subject to the same 403 on an unclaimed account.
Approvals for headless runs
Runs default to an approval model. For unattended (headless) runs there's no human to approve a risky tool call, so either:
- Run with
permission_mode="autonomous"(the agent acts without per-tool approval; appropriate for trusted, well-scoped agents), or - Keep approvals on and handle the pause: a run that needs approval enters
awaiting_approval; resolve it programmatically with the run's approve / answer endpoints.
See Human-in-the-Loop for the full model.
Going-live checklist
- Server-side API key only; never exposed to end-users or browsers
- A named key per environment via
client.keys.create(name=...), and the one you tested with rotated -
user_idset on every run/agent/task that belongs to one of your end-users - Strict multi-tenant mode still on (the default for new API accounts) so a forgotten
user_idfails loudly:client.settings.get().require_end_user_idshould beTrueunless you're deliberately single-tenant -
per_end_user_run_limitand/orper_end_user_cost_limit_centsset - Your code handles
402(cap reached) and429(concurrency / rate limit) gracefully - Headless runs either use
autonomousor resolveawaiting_approvalprogrammatically - Balance won't interrupt production: auto-reload or spend alerts set on the prepaid balance (see Billing & Usage)
- Webhook endpoints verify the signature (see Webhook Events)
- Channel branding decided: m8tes-branded inboxes are fine to launch on, or your own email domain, Slack app, and iMessage number arranged first (sales@m8tes.ai)
Next: Limits · Data Retention · Users
