Errors

.md

The shape of every failure, and the full list of codes you can branch on.

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.

TypeStatusWhat to do
authentication_error401Fix the credential
permission_error403The key needs a wider scope
invalid_request_error400, 404, 405, 415, 422Fix the request
idempotency_error409See Idempotency
rate_limit_error429Back off and retry after the window
api_error500Our 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 - no Authorization header 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 includes required_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_mailbox is the exception, for tidying up after somebody who left.
  • workspace_not_permitted - the Propper-Workspace header 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 as manage_mailbox for blocking senders. Signing in again changes nothing. On the admin endpoints the response includes required_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 the Propper-Workspace header.
  • invalid_folder - not one of inbox, 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's data is not base64, or it is empty.
  • attachment_too_large - the file is bigger than a draft may carry.
  • missing_since - the mail sync needs a since.
  • 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. See errors.
  • invalid_json - the body is not valid JSON.
  • unsupported_content_type - the body is not application/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 - limit is not a positive integer.
  • invalid_cursor - starting_after does not match any resource.
  • invalid_sort - sort names something this list does not sort on.
  • invalid_kind - kinds names a kind this list does not have, or a kind of notice the account does not receive.
  • invalid_period - period is not one of the periods the dashboard offers.
  • missing_idempotency_key - a POST without an Idempotency-Key header.
  • 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. consistency carries 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. A 422.
  • 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 in ticket, with id and subject. 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. A 400.
  • image_too_large - a picture over 10 MB once decoded. A 400.

Fraud

  • override_exists - creating a trader override for a trader who already has one on that detector. A 409, naming that override in override. 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 with 429, with state (session, week, none or platform) saying which, and resets_at saying when it comes back. resets_at is absent when waiting will not help.
  • tenant_in_maintenance - the firm is closed. Answered with 503; 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 with 503. Nothing changed, so asking again is fine.
  • internal_error - something broke on our side. Retry, and send us the request_id if it keeps happening.