Skip to content

Access control & teams

In multi-tenant mode, org members are assigned roles, and operators can provision users automatically via SSO and SCIM. Backend automation uses management tokens. This page covers the role model, its current enforcement scope, and the provisioning flows.

Roles

merido defines five roles, ranked from least to most privileged:

RoleWhat it's for
ViewerRead-only — view usage. (Cannot read the audit log.)
MemberMake inference requests and view usage.
BillingSee spend, manage billing and budgets, export data. Cannot touch providers, keys, or members, and cannot make requests. (Orthogonal to the others.)
AdminDay-to-day operations — manage members, providers, keys, teams, budgets, audit, guardrails, policy, SSO, and export data. Cannot transfer/delete the org or manage billing.
OwnerFull control, including org lifecycle and billing.

Internally each role maps to a set of actions (ManageProviders, ManageKeys, ManageBudgets, ViewAudit, ManageGuardrails, ManageSso, ExportData, …). Every member can Proxy (make requests) except Billing and Viewer.

Enforcement scope

In multi-tenant mode the granular per-action role is enforced on the core control-plane resources: creating/updating/deleting/rotating keys (ManageKeys), adding/updating/deleting providers (ManageProviders), all budget mutations (ManageBudgets — Billing included), all virtual-model mutations (ManagePolicy), minting management tokens (admin), and reading the audit log (ViewAudit). A Member, Billing, or Viewer who attempts an action their role does not grant gets 403.

A few other admin surfaces (guardrails, routing policy, SSO, vault, settings) are still gated on org-admin status rather than a dedicated capability — equivalent for Owner/Admin vs everyone else, just coarser. The local single-user profile bypasses RBAC entirely (there is one user).

Management tokens

Management tokens (mdm_…) authenticate backend automation against the control plane (/api/*) without a browser session.

POST   /api/management-tokens       # create — returns the plaintext token ONCE
GET    /api/management-tokens       # list (metadata only)
DELETE /api/management-tokens/{id}  # revoke

A token carries a scope:

  • admin (default) — full control, can call mutating endpoints.
  • readonly — only safe methods (GET/HEAD/OPTIONS); mutating calls are rejected 403.

The plaintext is shown once at creation — only its hash is stored. Tokens can be created only from a logged-in session (not by another token), and revoked tokens are rejected immediately. Store them like any secret.

SSO (OIDC)

merido supports OIDC single sign-on so users log in through your identity provider:

GET /auth/oidc/login?org=<id>   # begins auth-code + PKCE, redirects to the IdP
GET /auth/oidc/callback         # completes the exchange, issues a merido session

On callback, merido finds-or-creates the user by their IdP subject and maps IdP groups to a merido role via a configurable group→role map. Configure the IdP per org:

GET    /api/sso/config   # current config (client secret shown only as a "set" flag)
PUT    /api/sso/config   # create/replace (the client secret is sealed at rest)
DELETE /api/sso/config   # remove
POST   /api/sso/test     # OIDC discovery probe (SSRF-guarded)

The OIDC flow state is carried in a sealed cookie so a callback can land on a different instance than the login began on.

SCIM provisioning

For automated user/group lifecycle, merido implements SCIM 2.0:

POST/GET/PUT/PATCH  /scim/v2/Users      # provision, fetch, replace, patch users
POST/PATCH          /scim/v2/Groups     # provision groups, add/remove members

SCIM callers authenticate with a dedicated bearer token (scim_…, created via the SSO settings), scoped to one org. Setting a user active = false deprovisions them: the user is locked out at session verify, their API keys are suspended, and their sessions are revoked.

Per-key limits

Every gateway key can carry its own guardrails, enforced by the gateway on each /v1 call:

LimitEffect when set
Allowed modelsOnly matching models may be requested; anything else is rejected. Globs: openai/*, *-mini, or an exact id. Empty = every model.
Requests / minPer-key RPM cap, overriding the global MERIDO_RATE_LIMIT_RPM.
Tokens / minPer-key TPM cap. Unset = no token-rate limit.
Session cap (USD)Per-session spend ceiling for this key.

Set them in the dashboard under Access Keys → Gateway keys → Create a key → Limits, or change them on a live key from the Edit limits action in the key's row (also reachable by clicking the limit chips under the key's name).

Through the API:

bash
# create
curl -X POST https://your-gateway/api/keys \
  -H "Authorization: Bearer $MERIDO_MGMT_TOKEN" -H 'Content-Type: application/json' \
  -d '{"name":"team-a","scope":"proxy","allowed_models":["openai/*"],
       "rate_limit_rpm":600,"rate_limit_tpm":2000000}'

# retune later — omitted fields keep their current value
curl -X PATCH https://your-gateway/api/keys/12 \
  -H "Authorization: Bearer $MERIDO_MGMT_TOKEN" -H 'Content-Type: application/json' \
  -d '{"rate_limit_rpm":900}'

PATCH merges: a field you omit is left untouched (including cost_center), [] clears the allow-list, and 0 clears a cap. Rotating a key carries its limits over to the new secret.

Credential lifecycle

A gateway key or management token is in exactly one of four states:

StatusHow it gets thereReversible
ActiveThe normal state — it authenticates.
ExpiredIts TTL lapsed (ttl_seconds at creation).No — rotate it to get a working replacement that inherits its name, scope, cost center and limits.
RevokedDELETE /api/keys/{id} (dashboard: Revoke key).No.
DisabledThe service account that owns the key was disabled.Yes — re-enable the service account.

Revoking is a soft delete: the row is disabled and stamped revoked_at, so it stays in GET /api/keys as an auditable tombstone rather than vanishing. Rotation is the exception — it hard-deletes the old row after the replacement exists, so rotating does not accumulate tombstones.

Because the list therefore contains dead credentials, the dashboard's Access Keys page shows only Active ones by default. A Show: Active / Inactive / All filter appears above the table as soon as anything is revoked, expired or disabled, and searching while filtered tells you how many matches are being held back. Dead rows carry no actions — the one exception is Rotate on an expired key, which is how you renew it.

© merido. All rights reserved.