Roles

Customer roles
RolePurpose
ownerMembership, recovery, support access and audit export rights. Must be explicitly assigned billing_admin to transact.
trust_adminTrust spaces, issuers and space policy; can do anything a service owner can.
service_ownerControls grants, invitations and gateways for services.
partner_adminAccepts the organization’s invitations, approves workload issuers and enrollments, manages its workloads.
billing_adminPlan, checkout, invoices and the Stripe portal. No trust configuration.
auditorRead-only inspection of configuration and evidence; export only with a separate grant.

Role matrix

Role matrix
Actionownertrust_adminservice_ownerpartner_adminbilling_adminauditor
Read the organization, members, workload issuers, spaces and services
org.read
YesYesYesYesYesYes
Assign member roles
org.members.manage
YesNoNoNoNoNo
Create trust spaces
space.create
YesYesNoNoNoNo
Manage issuers and space policy
issuer.manage
NoYesNoNoNoNo
Create services
service.create
YesYesYesNoNoNo
Manage services
service.manage
NoYesYesNoNoNo
Pair and disable gateways
gateway.pair
NoYesYesNoNoNo
Create and cancel invitations
invitation.create
NoYesYesNoNoNo
Accept invitations for the organization
invitation.respond
YesNoNoYesNoNo
Suspend, resume, withdraw, change scope; revoke sponsor-side leaves
grant.manage
NoYesYesNoNoNo
Read grants, invitations and receipts
grant.read
YesYesYesYesNoYes
Approve workload issuers; revoke the organization’s own leaves
workload.manage
NoNoNoYesNoNo
Approve sandbox enrollments
enrollment.approve
NoNoNoYesNoNo
Download certificate chains
certificate.read
YesYesYesYesNoYes
Checkout and billing portal
billing.manage
NoNoNoNoYesNo
Read billing summary and invoices
billing.read
YesNoNoNoYesYes
Read the audit log
audit.read
YesYesNoNoNoYes
Change approval policies on spaces, services and integrations
approval_policy.manage
YesYesNoNoNoNo
Export the audit log (needs a separate export grant)
audit.export
NoNoNoNoNoNo

Two rules sit outside the matrix: support access grants, export grants and role assignment require owner; and a caller with no role in an organization gets 404 (not 403) for its resources, so inaccessible tenants are concealed.

Bootstrap and membership

  • Creating an organization (POST /v1/organizations) makes you its owner, trust_admin, service_owner and partner_admin. It does not make you billing_admin.
  • Claiming a new partner organization through an invitation makes you owner and partner_admin.
  • Owners add roles with POST /v1/organizations/{id}/roles {"user_id":"usr_…","role":"auditor"}. Assignment is additive and idempotent; the target must already be a member.
  • GET /v1/organizations/{id}/members returns each member’s user_id, display_name, email and roles.

Remove roles and members

Owners manage membership under Organization > Members and roles, or through the API:

  • DELETE /v1/organizations/{id}/members/{user_id}/roles/{role} removes one role. API credentials the member created lose that role immediately, because a credential’s effective roles are re-checked against its creator’s current roles on every request. Removing a member’s last role ends their membership.
  • DELETE /v1/organizations/{id}/members/{user_id} removes every role at once.
  • When a membership ends, every API credential the member created in the organization is revoked and their audit export rights end, in the same batch. The response reports credentials_revoked.
  • An organization always keeps an owner: removing the last owner’s owner role, or removing the last owner (including yourself), returns 409 last_owner. Make another member an owner first.
  • Every change is written to the audit log as membership.role_removed or membership.removed.

Partner handle

Each organization has a partner handle such as harbor-logistics-7kq2m9xd, shown under Organization > Partner handle. Share this handle with API owners who want to invite you. Looking it up reveals only your organization name, and an invitation sent to it can be accepted only by your partner administrators or owners. See Invite an existing partner by handle.

Any member reads it with GET /v1/organizations/{id}/partner-handle. Owners replace it with POST /v1/organizations/{id}/partner-handle/rotate when it has been shared more widely than intended: the old handle stops resolving at once, and invitations already sent stay valid. Replacements are audited as organization.partner_handle_rotated.

Billing administration is explicit

Holding owner is not enough to spend money. To check out or open the billing portal, an owner assigns billing_admin to themselves or to a colleague:

Assign billing_admin
POST https://app.mutualtrust.io/v1/organizations/org_01jcqa7m2k9x4v8r3t6p5w2h7n/roles X-CSRF-Token: <csrf_token> Content-Type: application/json { "user_id": "usr_01jcq9z8y6x4w2v0t8s6r4q2p0", "role": "billing_admin" }
200 OK
{ "user_id": "usr_01jcq9z8y6x4w2v0t8s6r4q2p0", "role": "billing_admin" }

A billing admin can update a card or download invoices without gaining access to trust configuration, and a finance or support colleague at Sectigo cannot change your payment method at all.

API credentials

API credentials let automation act for you on https://api.mutualtrust.io with a subset of your roles in one organization. Create them from a signed-in portal session (a credential cannot mint another credential).

Create an API credential
POST https://app.mutualtrust.io/v1/organizations/org_01jcqa7m2k9x4v8r3t6p5w2h7n/api-credentials X-CSRF-Token: <csrf_token> Content-Type: application/json { "name": "terraform-ci", "roles": ["service_owner"], "expires_in_days": 90 }
201 Created
{ "id": "cred_01jcqd5h7j9k1m3n5p7q9r1s3t", "secret": "mtk_Jx4c…", "expires_at": "2027-02-12T16:20:00Z", "roles": [ "service_owner" ], "name": "terraform-ci", "note": "Shown once. Send as \"Authorization: Bearer <secret>\" to https://api.mutualtrust.io." }
  • roles must be a subset of the roles you hold in that organization, or the request fails with 403 naming the excess roles. expires_in_days is 1 to 365 (default 90).
  • Effective roles on each request are the credential’s roles intersected with the creator’s current roles: remove a role from the creator and every credential they made loses it too.
  • List with GET /v1/organizations/{id}/api-credentials (name, roles, prefix, purpose, expiry, last used, creator, active). The secret is never shown again.
  • Revoke with POST /v1/organizations/{id}/api-credentials/{cid}/revoke → {"id":"cred_…","active":false}. The creator or an owner can revoke.
  • Bearer credentials are not ambient, so they need no CSRF token. Credentials made for MCP clients have purpose: "mcp" and a 30-day lifetime (see MCP server).
  • API credentials cannot make high-risk changes in production unless they were created with "high_risk_allowed": true. Creating such a credential needs a step-up (a sign-in or passkey re-authentication from the last 15 minutes) and it expires within 30 days (expires_in_days 1 to 30, default 30). Without the flag the API answers 403 high_risk_credential_required.

Support access grants

Sectigo support staff have no standing access to your audit detail or invitations. An owner grants a narrow, reason-coded, expiring permission to one named staff member:

Grant support access (owner)
POST https://app.mutualtrust.io/v1/organizations/org_01jcqa7m2k9x4v8r3t6p5w2h7n/support-grants X-CSRF-Token: <csrf_token> Content-Type: application/json { "staff_email": "jane.doe@sectigo.example", "action": "audit.detail", "reason_code": "customer_request", "expires_in_hours": 24 }
201 Created
{ "id": "sg_01jcqd3f5g7h9j1k3m5n7p9q1r", "staff_email": "jane.doe@sectigo.example", "action": "audit.detail", "reason_code": "customer_request", "expires_at": "2026-11-15T16:25:00Z" }
Support grant fields
FieldValues
actionaudit.detail (view unredacted audit detail) or invitation.resend.
reason_codecustomer_request, incident, diagnostics.
expires_in_hours1 to 72.

List with GET …/support-grants and end early with POST …/support-grants/{gid}/revoke. Every staff request is logged with the staff identity, purpose and your tenant, and every staff write appears in your audit log with actor_type: "staff". Support cannot alter PKI keys, prices or payment methods.

Audit log and export rights

GET /v1/organizations/{id}/audit (owner, trust admin, auditor) returns 100 events per page, newest first; pass ?before=<next_cursor> for the next page.

200 OK
{ "data": [ { "id": "aud_01jcqm4x7h3j5k7m9n1p3q5r7s", "actor_type": "user", "actor_id": "usr_01jcq9z8y6x4w2v0t8s6r4q2p0", "action": "grant.revoke", "resource_type": "grant", "resource_id": "gr_01jcqb3p1n3p5q7r9s1t3v5w7x", "resource_revision": 4, "reason": "contract_ended", "operation_id": "op_01jcqm4x7f9g1h3j5k7m9n1p3q", "request_id": "req_8e1f3a9c2b7d4e60", "environment": "sandbox", "detail": null, "created_at": "2026-11-14T16:02:11Z" } ], "next_cursor": null }

Exporting is a separate right. An owner grants it, for a limited time, to a member who holds auditor, owner or trust_admin:

Grant export rights (owner)
POST https://app.mutualtrust.io/v1/organizations/org_01jcqa7m2k9x4v8r3t6p5w2h7n/export-grants X-CSRF-Token: <csrf_token> Content-Type: application/json { "user_id": "usr_01jcqc1d3e5f7g9h1j3k5m7n9p", "expires_in_hours": 48 }
201 Created
{ "id": "exg_01jcqd4g6h8j0k2m4n6p8q0r2s", "user_id": "usr_01jcqc1d3e5f7g9h1j3k5m7n9p", "expires_at": "2026-11-16T16:30:00Z" }
Export the audit log
GET https://api.mutualtrust.io/v1/organizations/org_01jcqa7m2k9x4v8r3t6p5w2h7n/audit-export?from=2026-11-01T00:00:00Z&to=2026-12-01T00:00:00Z Authorization: Bearer mtk_…

The export needs both audit.read and an active export grant (otherwise 403). It returns a canonical JSON document (mt.audit_export.v1, up to 50,000 rows) as an attachment with an X-Content-SHA256 header; a copy is retained in protected storage and the export itself is audited. See Wire formats.

Approval policies

An approval policy says how strict the second person in an approval must be. The same five settings apply to integration plan approvals, to approving partner grants on services that use sponsor review, and to the Mutual Trust staff approvals that affect your organization (credits and refunds, destroying a key in one of your spaces, and a policy epoch bump on one of your services).

Approval policy settings
SettingMeaningStricter when
min_approver_age_hours (0 to 720)How long the approver must have been a member of your organization (for staff: enrolled as staff).Higher
require_second_approverSomeone other than the requester must approve: not the person who submitted the plan, and not the person who created the invitation behind a pending grant.true
require_step_upThe approver must have signed in, or confirmed with their passkey, recently. API credentials can never step up.true
step_up_max_age_minutes (1 to 60)How recent that sign-in must be.Lower
independencestandard lets the only eligible member approve their own request; strict never does (the request waits for a second person).strict

Where values come from. Policies are set on nested scopes: the Mutual Trust platform, your organization, a space, a service and an integration. A scope sets only the values it needs; everything else is inherited. Mutual Trust sets the platform and organization scopes (on request, with two staff members involved); these are floors. Your owners and trust administrators set the space, service and integration scopes: the most specific scope that sets a value wins, but never below the floor. A value below the floor is refused with 422 approval_policy_below_floor, and a stored value that a later floor overtakes is shown as not applied.

Defaults. With nothing set, approvals behave as before: integration plans need a passkey or organization sign-in from the last 15 minutes and, when the integration requires it, a second approver; partner grants need no extra checks. These built-in rules are minimums no policy can relax.

Changing a policy. In the portal, open a service or integration and use Approval policy: it shows each effective value and where it comes from. Through the API, read the scope, then send a partial policy with the revision you read (0 when nothing is set yet); null clears a value so it is inherited again. Tightening is accepted from API credentials, so Terraform can manage it (mutualtrust_approval_policy). Relaxing a production scope needs a browser session that signed in or confirmed with a passkey within 15 minutes, otherwise 403 step_up_required. Every change is in the audit log (approval_policy.updated, with the effective policy before and after) and emits an approval_policy.updated event; every approval records the policy it was checked against and where each value came from.

Require a second approver who joined at least 3 days ago
PUT https://api.mutualtrust.io/v1/services/svc_01jcqb2n4p6r8t0v2x4z6b8d0f/approval-policy Authorization: Bearer mtk_… If-Match: "0" Content-Type: application/json { "require_second_approver": true, "independence": "strict", "min_approver_age_hours": 72 }

The same call works on /v1/spaces/{id}/approval-policy and /v1/integrations/{id}/approval-policy. GET /v1/organizations/{id}/approval-policy shows the floor and every scope in the organization that sets a value. Refused approvals return second_approver_required, approver_too_new or step_up_required, each with the approval_policy that applied.

Workload issuers

Partner admins approve the OIDC issuers their workloads may use; anyone in the organization can list them with GET /v1/organizations/{id}/workload-issuers. Partner admins also bind and rebind Kubernetes service account UIDs on workloads (PATCH /v1/workloads/{id}/binding). See Enrollment.

Domain and legal-entity verification

Production needs a verified organization (production onboarding). There are two separate verifications with two separate labels. They are never merged: a verified domain says nothing about the legal entity, and a verified legal entity says nothing about DNS.

Organization verifications
LabelWhat it provesHowLasts
Domain control verifiedThe organization controls a DNS domain.An owner adds the domain and publishes a TXT record at _mutualtrust-challenge.<domain>. Mutual Trust checks it over DNS-over-HTTPS through two resolvers that must agree (details).90 days, re-checked automatically.
Legal entity verifiedThe organization is the registered legal entity it says it is.An owner submits the legal name, registration number, jurisdiction and evidence notes. Mutual Trust staff review it and a second, independent staff member approves it.Until a newer submission is approved.
  • Domains: GET and POST /v1/organizations/{id}/domains, POST /v1/organizations/{id}/domains/{domain_id}/check (check now), DELETE /v1/organizations/{id}/domains/{domain_id}. Owners add and remove domains; any member can read them. Up to 10 domains per organization.
  • Legal entity: GET and POST /v1/organizations/{id}/legal-entity. One submission can await review at a time (409 verification_open). When staff reject a submission, the reason is shown so you can correct it and submit again.

Single sign-on and provisioning

Owners connect the organization's own identity provider, enforce SSO-only sign-in with break-glass owners, and provision members over SCIM with a group to role mapping. See Your organization's identity provider and SCIM provisioning.

Partner delegations

A reseller or managed service provider can administer your organization only through a delegation that an owner of your organization accepts (POST /v1/delegations/{id}/accept, fresh sign-in). The Partner delegations tab shows the partner, the roles, the effective dates and the state. A delegation never includes the owner role, never lets the partner mint API credentials or grant support access in your organization, and ends when either side revokes it (POST /v1/delegations/{id}/revoke) or its end date passes. Who pays for your organization (a reseller payer) and who referred you are separate records: they grant no access, and ending a delegation does not change them. See Resellers and MSPs.

Owner recovery

If every owner is locked out, a person at your organization can ask Mutual Trust to restore ownership with fresh DNS evidence and a contact attestation; two independent staff members decide and the named person claims it with a fresh sign-in. Current owners see open requests under Owner recovery and can object. See Owner recovery.

High-risk changes and step-up

These changes need a recent sign-in: a passkey re-authentication or a fresh sign-in with your organization identity provider within the step-up window (15 minutes, shorter if an approval policy says so). The portal asks you to confirm with your passkey and names the affected service, organization and environment before it proceeds. See Identity and access.

  • Withdraw or suspend a production grant, or widen the routes of a production grant.
  • Remove a member, or remove a role from a member.
  • Create an API credential with the owner or trust administrator role, or one with high_risk_allowed.
  • Relax a production approval policy.
  • Approve a workload issuer.
  • Rotate the partner handle.
  • Generate new recovery codes.
  • Create a production space or production service.
  • Disable a production gateway.
  • Turn off the second approver on a production integration. API credentials without high_risk_allowed cannot turn it off in any environment.
  • Change the organization identity provider, SSO-only sign-in or break-glass owners; create or rotate a SCIM token; accept a partner delegation; claim ownership after an approved recovery. These always need a fresh browser sign-in; API credentials cannot make them.
  • Change which roles SCIM groups grant (once the organization is production enabled).

Production changes always need step-up. Organization-wide changes (members, roles, credentials, workload issuers, partner handle) need it once the organization is production enabled: production access approved or a production space exists. Sandbox-only organizations are not asked. API credentials can make these changes only when created with high_risk_allowed; otherwise the API answers 403 high_risk_credential_required. A stale session gets 403 step_up_required with high_risk_action and max_age_seconds.