Every failure returns the same shape:
{
"error": {
"type": "invalid_request_error",
"code": "email_already_registered",
"message": "This email address already has a reserved referral code.",
"param": "email",
"doc_url": "https://proppertrading.com/docs/api/errors#email_already_registered",
"request_id": "req_01kz4bkr1aj1gv63d8fv3133ey"
}
}
Branch on code, never on message
code is stable forever once it is published here. message is prose for a
human reading your logs and may be reworded at any time.
param is present when one specific input caused the failure.
Types
type is a coarse bucket, so an unfamiliar code can still be handled sensibly.
| Type | Status | What to do |
|---|---|---|
authentication_error | 401 | Fix the credential |
permission_error | 403 | The key needs a wider scope |
invalid_request_error | 400, 404, 405, 415, 422 | Fix the request |
idempotency_error | 409 | See Idempotency |
rate_limit_error | 429 | Back off and retry after the window |
api_error | 500 | Our problem. Safe to retry |
Validation
A failed validation returns 422 with every field error, not just the first:
{
"error": {
"type": "invalid_request_error",
"code": "validation_failed",
"message": "The email address is not valid.",
"param": "email",
"errors": { "email": ["The email address is not valid."] },
"request_id": "req_01kz4bkr1aj1gv63d8fv3133ey"
}
}
Codes
Authentication
missing_api_key- noAuthorizationheader was sent.invalid_api_key- the credential does not exist or the secret does not match.revoked_api_key- the key was revoked in the dashboard.expired_api_key- the key passed its expiry date.ip_not_allowed- the key has an IP allowlist and this address is not on it.
Authentication as a person
These only come back on the app endpoints. All of them mean the same thing to a client: send the person back to the sign-in screen.
invalid_token- not a token, or the secret does not match.revoked_token- the device was signed out, here or from another one.expired_token- ninety days without a request, or a set expiry passed.invalid_login- wrong address, wrong password, or no such account in that environment. The three are deliberately indistinguishable.account_deactivated- the account exists and has been switched off.invalid_two_factor_code- wrong code. The challenge survives. Confirming an authenticator on a signed-in account answers the same code as a 400.expired_two_factor_challenge- the challenge expired, was already used, or was burned by five wrong codes. Start again from the password.expired_workspace_choice- the choice between environments expired or was already used. Start again from the password.admin_access_required- signing in to the Propper app, or using its endpoints, with an account that has no part in running the environment: a trader, or staff whose role grants nothing.
Permission
insufficient_scope- the credential is valid but lacks the required scope. The response includesrequired_scope.two_factor_setup_required- the firm requires two-factor and this account has none. It has to be set up on the web first, except in the Propper app, which sets it up during sign-in.two_factor_method_unavailable- the account signs in with a passkey only, which an app cannot present yet.block_anonymizer- the firm blocks VPN, proxy and Tor connections.block_country- the firm blocks connections from this country.mailbox_not_permitted- the account may not open a mailbox at all. A permission of the environment, not a scope of the device.channel_not_allowed- an app asked to listen to a websocket channel that is not one of the five it may have.not_your_note- only the person who wrote a note may change or remove it.manage_mailboxis the exception, for tidying up after somebody who left.workspace_not_permitted- thePropper-Workspaceheader names an environment this account cannot act in, or one that does not exist. The two answer the same.forbidden- the account lacks the permission this action takes, such asmanage_mailboxfor blocking senders. Signing in again changes nothing. On the admin endpoints the response includesrequired_permission.platform_required- an admin endpoint of the platform itself, asked by somebody who is not our staff or from inside a firm.assist_unavailable- the environment has no AI for the writing help: none is connected, or it is switched off. Waiting does not change it.
Request
workspace_not_found- no environment is served from that address, or the environment picked after a sign-in was not one of those offered.workspace_required- an admin endpoint that works on one firm, asked by our own staff without naming one in thePropper-Workspaceheader.invalid_folder- not one ofinbox,archive,drafts,sent,spam,trash.not_in_trash- a message can only be erased out of the bin. Move it there first.not_a_draft-draft_id, or the draft in the path, is a message that is not a draft.invalid_sender- not an address or a domain that can be blocked, or a message with no sender address.own_sender- an environment cannot block its own addresses or domains.invalid_attachment- the file'sdatais not base64, or it is empty.attachment_too_large- the file is bigger than a draft may carry.missing_since- the mail sync needs asince.invalid_since- not a timestamp and not a handle we handed out.sync_window_expired- the client has been away longer than the window in which deletions are kept. Rebuild the cache rather than syncing.nothing_to_mark- marking notifications read without saying which.invalid_avatar- a picture that is not base64, is empty, or is not a JPEG, PNG or WebP.avatar_too_large- a picture over 2 MB.invalid_current_password- changing the password without the right current one.two_factor_already_enabled- setting up an authenticator on an account that has one. Turn it off first.two_factor_not_started- confirming an authenticator without asking for a secret first.two_factor_not_enabled- recovery codes for an account without an authenticator.last_two_factor_method- removing the only second factor of an account that must keep one.validation_failed- one or more fields are invalid. Seeerrors.invalid_json- the body is not valid JSON.unsupported_content_type- the body is notapplication/json.unknown_endpoint- that URL is not an endpoint.resource_not_found- the endpoint is fine, the id does not exist. Note that a resource belonging to another firm also returns this.method_not_allowed- wrong HTTP method for this endpoint.invalid_limit-limitis not a positive integer.invalid_cursor-starting_afterdoes not match any resource.invalid_sort-sortnames something this list does not sort on.invalid_kind-kindsnames a kind this list does not have, or a kind of notice the account does not receive.invalid_period-periodis not one of the periods the dashboard offers.missing_idempotency_key- a POST without anIdempotency-Keyheader.invalid_idempotency_key- the header is longer than 255 characters.
Payouts
Each one is a 409 unless it says otherwise: the request was well formed,
and the payout is no longer where the person deciding thought it was. Read it
again before deciding.
payout_already_reviewed- approving a payout that is no longer pending.payout_consistency_failed- approving a payout whose account fails the consistency rule as it is now.consistencycarries the best day, the total profit, the ratio, the limit and the profit at which it would pass.payout_already_settled- rejecting a payout that was already paid or rejected.payout_not_approved- marking paid a payout that is not approved.payout_balance_too_low- marking paid a payout larger than what the account holds now.ticket_not_traders- linking a ticket of another trader to a payout. A422.trader_gone- opening a ticket about a payout whose trader deleted their account.trader_has_open_ticket- opening a ticket about a payout while the trader has an open one. Link that one instead.
Tickets
Each one is a 409 unless it says otherwise: the request was well formed,
and the ticket or the firm does not allow it as things stand.
ticket_ended- answering, assigning, renaming, blocking the assistant on, resolving or closing a ticket that was already resolved or closed. Only its tags still change. A new question becomes a new ticket.assistant_unavailable- asking the assistant for a reply while the firm has switched it off for its team.trader_has_open_ticket- opening a ticket with a trader who has an open one, since a trader has one at a time. Opened directly, the error names that ticket inticket, withidandsubject. Opened from a payout, see Payouts above.invalid_image- a picture that is not base64, is empty, or is not a JPEG, PNG, GIF or WebP. A400.image_too_large- a picture over 10 MB once decoded. A400.
Fraud
override_exists- creating a trader override for a trader who already has one on that detector. A409, naming that override inoverride. Change that one instead.
Idempotency
idempotency_key_reuse- the key was already used with a different body.idempotent_request_in_flight- an earlier request with this key is still running. Retry shortly.
Affiliates
pre_registration_disabled- the firm has not enabled pre-registration.email_already_registered- that address already has a reserved code.pre_registration_not_pending- the reservation was already claimed or withdrawn.
Other
rate_limit_exceeded- see Rate limits.assist_limit_reached- the environment's AI allowance is used up. Answered with429, withstate(session,week,noneorplatform) saying which, andresets_atsaying when it comes back.resets_atis absent when waiting will not help.tenant_in_maintenance- the firm is closed. Answered with503; everyone belonging to it is locked out until it reopens.send_failed- the mail provider would not take the message. It is still saved as a draft, so offer to try again rather than saying it is gone.assist_failed- the model behind the writing help did not answer. Answered with503. Nothing changed, so asking again is fine.internal_error- something broke on our side. Retry, and send us therequest_idif it keeps happening.