OpenPoly logo
Operator Api

Staffs

Manage human Operator Dashboard staff accounts through staff-session APIs.

Staffs

Staff endpoints manage human Operator Dashboard accounts.

Staff management and password-change endpoints require a staff-session bearer token; broad API-key scopes cannot replace it. Login needs no existing bearer, and logout has the API-key exception documented below.

Auth paths

POST /api/operator/auth/staff-login
POST /api/operator/auth/staff-change-password
POST /api/operator/auth/logout

staff-login returns { ok: true, token, expires_at, operator, staff }, with a pmost. bearer and a 12-hour expiry. A successful login revokes all previous sessions for that staff account before issuing a new token. Activity does not extend the fixed expiry. Inactive operator, membership, or account status prevents login/session use.

staff-login does not require an existing bearer token. Its JSON body is:

FieldTypeRequirementNotes
emailstringRequiredValid email address.
passwordstringRequiredNon-empty password.

Login is limited to 5 failed attempts per normalized email and source identifier in a 15-minute window. A blocked attempt returns 429 RATE_LIMIT_EXCEEDED; wait for the window instead of retrying unchanged credentials.

staff-change-password is available only to staff sessions. newPassword is required and must contain at least 8 characters. oldPassword is conditionally required when must_change_password is false; it is optional for a forced first change. Successful change returns a replacement staff token and expiry because all previous sessions are revoked.

Password-change success is { ok: true, token, expires_at }; replace the old bearer immediately. An API-key call returns 400 PASSWORD_CHANGE_NOT_SUPPORTED. Invalid current password returns 400 INVALID_CREDENTIALS here (login uses 401). Login accepts only an unambiguous single active operator membership; otherwise it returns 409 MULTIPLE_OPERATOR_MEMBERSHIPS_UNSUPPORTED.

logout requires a valid bearer token and has no body. It revokes the current staff session when the bearer token is a staff token. An API-key call returns ok: true without revoking the key.

Staff management paths

GET /api/operator/staff
POST /api/operator/staff
PATCH /api/operator/staff/{membership_id}
POST /api/operator/staff/{membership_id}/reset-password

Required scopes:

  • staffs:read for list
  • staffs:write for create, patch, and reset password

All staff management paths also require auth_type = staff.

List query params

Both list query parameters are optional:

ParameterTypeRequirementNotes
searchstringOptionalCase-insensitive match against email or display name.
statusstringOptionalactive or disabled; an omitted or unrecognized value returns all.

List returns { items: [...] }, newest membership first, without pagination. status=active requires both active membership and active account; status=disabled includes either a disabled membership or inactive account. The returned status is the membership state, while account_status is the separate account state.

Staff fields

  • membership_id
  • admin_user_id
  • email
  • display_name
  • role
  • status
  • account_status
  • scopes
  • must_change_password
  • last_login_at
  • created_at
  • updated_at

Create body

{
  "email": "ops@example.com",
  "display_name": "Ops User",
  "scopes": ["users:read", "trades:read"]
}
FieldTypeRequirementNotes
emailstringRequiredValid, globally unused email.
scopesstringRequiredNon-empty assignable scope list; * is forbidden.
display_namestringOptionalStored display name.

Rules:

  • email must be valid and globally unused
  • scopes must be non-empty
  • * is not assignable in staff create/update
  • write scopes normalize to include the matching read scope when available
  • staff can assign only scopes they already hold, unless they have *
  • created memberships use role: "operator_viewer" with the explicit assigned scopes; do not infer permission from role alone
  • normalization adds matching read scopes before checking the caller's assignable permissions; a caller must hold those added read scopes too

Patch body

All fields are optional, but at least one update is required.

{
  "display_name": "Ops Lead",
  "status": "disabled",
  "scopes": ["users:read", "trades:read", "webhooks:read"]
}

A staff member cannot update their own access status or scopes. The implemented error is SELF_ACCESS_UPDATE_FORBIDDEN. A staff member also cannot reset their own password through the management endpoint; that returns SELF_PASSWORD_RESET_FORBIDDEN.

Patch path membership_id is required and must be a positive integer. display_name, status, and scopes are individually optional, but at least one must be supplied. Reset-password also requires the positive membership_id path parameter and has no body. Validation failures include INVALID_STAFF_ID, INVALID_STATUS, NO_UPDATES, and CANNOT_ASSIGN_SCOPE. Access or password mutations revoke active sessions for the target staff account; changing only the display name does not.

Password reset response

Create and reset responses include onboarding metadata:

{
  "ok": true,
  "item": {},
  "onboarding": {
    "email_sent": false,
    "email_error": "provider error",
    "temp_password": "returned-only-when-email-fails"
  }
}

If email sends successfully, temp_password is null.

The item above has the full staff fields listed earlier. On email failure the account/password change has already occurred: securely hand off the returned temporary password instead of repeating create and triggering EMAIL_EXISTS. Never log or place temporary passwords in support tickets. New/reset accounts require a password change; temp_password is not returned by the list route.

Staff-only private market requests use markets:read / markets:write, while support tickets require no extra resource scope. Neither workflow can be enabled by handing an operator API key to a browser.

Copyright © 2026