Team and roles
Who can sign in, what viewers, approvers and operators may do, the workspace password, two-factor sign-in, API tokens and the audit log.
Each person signs in with their own email and password, and has one role. API tokens carry a role too. The roles are ordered, so each one can do everything the roles before it can:
| Role | Can |
|---|---|
viewer |
See orders, flows, approvals and the back office; use the assistant to read |
approver |
Also approve and reject approvals, including through the assistant |
operator |
Also start, cancel and restart orders, deliver events, create, edit and publish flows, run the stock review, have the assistant do those and draft flow changes, manage webhook subscriptions and integration keys, and manage the team and API tokens |
Every request checks the role on the server; the dashboard also hides controls a role can’t use.
The workspace password
Section titled “The workspace password”Besides each person’s own sign-in, a workspace can have a workspace password, set by your OrderLoop
administrator. It is a break-glass operator sign-in: on the sign-in page, choose Use the
workspace password instead and enter it to reach the Team page as an operator (the REST API’s
equivalent is the password as a bearer token). It has no identity, so its decisions are recorded as
operator. Use it to add the first operator, then sign in as yourself.
The Team page
Section titled “The Team page”Operators manage people and tokens at /team:
- Add person: email, optional name and role. The page shows a generated 20-character temporary password once, with a Copy button; send it to them privately.
- Role: change and save. You can’t lower your own role.
- Reset password: after you confirm, gives the person a new temporary password and signs them out everywhere.
- Disable / Enable: after you confirm, a disabled person can’t sign in, and their sessions end. You can’t disable yourself.
- Reset two-factor: for someone who has lost their phone and their recovery codes. After you confirm, it turns their two-factor sign-in off, so they sign in with their password alone and can set it up again. You can’t reset your own.
- In a workspace without a workspace password, the last active operator can’t be demoted or disabled, so someone can always manage the team.
Passwords must be 10 to 256 characters, not a common password and not contain the email address or its part before the @ (when that is 4 or more characters).
They are stored only as salted PBKDF2-SHA256 hashes.
A session is a __Host-ol_session cookie holding a random token, of which only a hash is stored.
It ends 30 days after sign-in, or after 7 days without use, whichever comes first. Pages and the order page’s
live connection both count as use, recorded at most every 5 minutes. Signing out deletes the
session.
Temporary passwords
Section titled “Temporary passwords”A password an operator sets (a new person’s, or a reset) is temporary. Signing in with it works,
but until the person chooses their own on the Account page, every other page sends them there,
and the REST API, MCP, A2A, the order page’s live connection and order documents answer 403.
Choosing a new password ends it, and the Team page marks people still on one with
“temporary password”.
Your sessions
Section titled “Your sessions”The Account page lists where you’re signed in: each session’s browser and system, address, sign-in time and last activity, with this browser marked. Sign out ends one other session; Sign out everywhere else ends all but this one. Changing your password also signs you out everywhere else. An operator’s Reset password or Disable on the Team page ends all of a person’s sessions.
Two-factor sign-in
Section titled “Two-factor sign-in”On the Account page, Set up two-factor sign-in shows a QR code and the same key as text.
Scan it with an authenticator app (1Password, Google Authenticator, Authy and others), then enter
the code the app shows and your password to turn it on. From then on, after a correct password,
sign-in goes to /login/two-factor and asks for the app’s current 6-digit code. You have 10
minutes to enter it.
Turning it on shows 10 recovery codes once, like k7mqp-3xw9d, with Copy and Download;
I’ve saved them moves on. Each code signs you in once instead of an app code, for a lost or
broken phone (on the sign-in step, Use a recovery code); the Account page shows how many are
left and warns at three. New recovery codes replaces them all, and Turn off removes
two-factor sign-in after a confirmation; each asks for your password. If you have no phone and no
codes left, an operator can Reset two-factor on the Team page.
Codes are time-based one-time passwords (RFC 6238: HMAC-SHA1, 30-second steps, 6 digits). The code for the step before or after the current one is accepted too, in case the phone’s clock is a little off, and each step’s code works once. Recovery codes are stored as hashes and deleted when used. The workspace password and API tokens don’t use two-factor sign-in.
Requiring it
Section titled “Requiring it”Operators choose who must use two-factor sign-in under Two-factor sign-in on the Team page:
nobody (the default), operators, approvers and operators, or everyone. The section counts the people
covered who haven’t set it up. Until they do, those people can only use their Account page: every
other page sends them there, and the REST API, the order page’s live connection and order documents
answer 403. They can’t turn it off while it is required, but can make new recovery codes. Changing
the setting takes effect within 30 seconds. Turn it on for your own role after setting up your own
two-factor sign-in, or you’ll be sent to your Account page to do it first.
Failed sign-ins
Section titled “Failed sign-ins”Failed sign-ins are counted in 15-minute windows, and an attempt over a limit is refused with
429 and a wait before any password or code is checked, even when it is right:
| Counted per | Failures allowed | Stops |
|---|---|---|
| Email and address together | 5 | Guessing one person’s password from one place |
| Address | 30 | One place trying many accounts |
| 50 | Many places trying one account; high, so nobody can easily lock a person out | |
| Person, at the code step | 5 | Guessing two-factor codes after a correct password |
| Person, on the Account page | 5 | Guessing the current password from a signed-in browser left open: changing the password, turning off two-factor sign-in, new recovery codes (429) |
A successful sign-in clears its email-and-address count. The workspace password counts as one
email. Separately, after 20 wrong guesses at the workspace password as an API bearer token from one
address in 15 minutes, further attempts are refused with 429. The address is the client’s IP as
Cloudflare sees it.
API tokens
Section titled “API tokens”Systems that call the REST API use a token:
curl https://orderloop.app/api/v1/orders -H "Authorization: Bearer ol_live_…"Create one on the Team page with a name (what will use it), a role and how long it lasts: 30 days,
90 days (the default), 1 year or never. The token is shown once and stored only as a hash; the
list shows its first 12 characters, who created it, when it was last used and when it expires,
highlighting tokens that expire within 14 days. Revoke it there. An expired token gets 401 with
This API token expired on <date>…; create a new one and switch the integration over before the
old one runs out. Tokens made before expiry existed never expire. A token’s role limits it like a person’s: an approver token can
decide approvals but not start orders. Approval decisions made with a token are recorded as
token:<name>.
Connected apps
Section titled “Connected apps”MCP clients such as Claude connect at /mcp, and A2A agents at /a2a; each asks you to approve it on a consent page, where
you sign in (two-factor included) and choose a role. Connecting needs your own account: the
workspace password belongs to no one, so it can’t stand behind an app, and the consent page asks
whoever is signed in with it to sign in as themselves. You can grant your own role or a lower one,
never a higher one. Account lists Connected apps, each with whether it is an AI client
(MCP) or an agent (A2A), the address its access goes to, its role and when it connected;
Disconnect ends it after a confirmation. Every request from a connected app re-reads you: if you are
disabled it stops working, and its role is the lower of what you granted and what you have now. What
it does is recorded as <email> via MCP or <email> via A2A. A grant is for one endpoint, so
an app that uses both is connected twice. See MCP server and
A2A endpoint.
Audit log
Section titled “Audit log”Operators see security events at Audit log (in the account menu and linked from the Team page, /audit), newest first,
filtered by kind, and through GET /api/v1/audit-events.
Each event records when, who acted, what they acted on, details and the address:
| Kind | Actions |
|---|---|
| Sign-in | auth.sign_in, auth.sign_in_failed (with the email tried and why: unknown email, wrong password, disabled, no password set, wrong workspace password), auth.sign_in_throttled, auth.sign_out, auth.password_changed, auth.session_ended (device and address), auth.other_sessions_ended (how many), auth.two_factor_enabled, auth.two_factor_disabled, auth.two_factor_failed (wrong code or wrong recovery code), auth.two_factor_throttled, auth.recovery_codes_replaced, auth.recovery_code_used (how many are left). A two-factor auth.sign_in records whether it used the app or a recovery code |
| Team | team.first_operator, team.user_added, team.role_changed (from and to), team.password_reset, team.user_disabled, team.user_enabled, team.two_factor_reset, team.two_factor_requirement (from and to) |
| API tokens | token.created (role, prefix and expiresAt), token.revoked |
| Integrations | integration.created, integration.rotated, integration.revoked (the target is the integration’s name; the detail has its id) |
| Connected apps | oauth.app_connected (role and client id), oauth.app_disconnected |
| API access | api.bearer_failed and api.bearer_throttled, for wrong guesses at the workspace password; api.token_expired, at most hourly per token, when an expired token is used |
A person signing in only hears that the email and password didn’t match; the reason is for operators. Passwords and tokens are never recorded. Events are kept 180 days. Writing an event never blocks the action it records. Approval decisions are recorded on the approval itself (below), not here.
Who decided
Section titled “Who decided”An approval’s by is the person who decided: their email for a signed-in person, token:<name>
for an API token. Through the assistant, MCP or A2A it is <email> via assistant, <email> via MCP or <email> via A2A. On the order page’s live connection, the name comes from the
signed-in session, never from what the page sends.
Changes reach pages that are already open. Each action on the order page’s live connection checks the person’s session again, so a lower role applies to the next action, and signing out, a password reset, being disabled or the session going idle ends it.
Cross-site requests
Section titled “Cross-site requests”A browser request that changes something (any method but GET, HEAD and OPTIONS), or opens the
order page’s live connection, is refused unless its Origin is the app’s own; without Origin,
its Sec-Fetch-Site must be same-origin or none. This holds for other *.orderloop.app
subdomains too. The answer is 403 { "error": "Cross-site request refused" }. Requests with a
bearer token are exempt, since they carry no cookie, and so are clients that send neither header.
Response headers
Section titled “Response headers”Every response carries X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, X-Frame-Options: DENY, a Permissions-Policy that disables
camera, microphone, geolocation and payment, and Strict-Transport-Security: max-age=31536000; includeSubDomains. HTML pages also get a Content-Security-Policy with a fresh nonce per
response: scripts run only from the app’s own origin or with that nonce, styles and fonts load only
from the app’s own origin (IBM Plex is served by OrderLoop itself), frame-ancestors 'none',
form-action 'self', connect-src 'self'. OrderLoop can’t be embedded in another site’s frame.