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:

IdentityWhat it isSet by
Your accountThe auth boundary. One m8tes account, one API key.Your API key (m8_...)
Your end-userThe data-isolation boundary inside your account.The user_id field on a resource

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.

Python

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:

Python

When an end-user hits a cap, the run is rejected with a 402:

JSON

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:

JSON

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_id to 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:

StatusWhenWhat to do
429You already hold 50 active named keys ("API key limit reached (50); revoke an unused key first.")Revoke an unused key; that frees a slot immediately
403The account was created by agent signup and is still unclaimed (no password set and not verified)Claim it via the emailed link before minting or rotating keys

The 403 blocks minting and rotation only. Listing and revoking stay open on an unclaimed account.

Named-key details: expiry, revocation, listing
  • name is required, 1 to 100 characters, trimmed. expires_in_days is optional and must be between 1 and 3650.
  • active is false when 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 on active if you only want live keys.
  • last_used_at is 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 resets last_used_at. Rotating a revoked key returns 409: revocation is terminal, create a new key instead.
  • revoke(key_id) is idempotent. A key id belonging to another account returns 404.
  • 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:

Python

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_id set 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_id fails loudly: client.settings.get().require_end_user_id should be True unless you're deliberately single-tenant
  • per_end_user_run_limit and/or per_end_user_cost_limit_cents set
  • Your code handles 402 (cap reached) and 429 (concurrency / rate limit) gracefully
  • Headless runs either use autonomous or resolve awaiting_approval programmatically
  • 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

Was this page helpful?