Penalty Tickets (p-tickets)

At a Glance

  • P-tickets are issued when an IP address (or subnet) exceeds an endpoint’s rate limit (listed in the endpoint’s documentation and in Rate Limits > Endpoint-Level Rate Limits).
  • P-tickets don’t come back as an error, but as an HTTP 200 OK with a p-ticket field in the body.
  • To resolve a p-ticket, wait the number of seconds in the response’s p-time, then resend the identical request body with the p-ticket value added as an extra field.
  • If p-captcha is true, you can’t answer the reCAPTCHA from a server-to-server integration. Wait about one hour for the endpoint’s count to clear, then resume normal calls.
  • P-tickets are different from user-level rate limits, which return an HTTP 429.

What a Penalty Ticket Is

When an IP address (or subnet) calls an endpoint after exceeding that endpoint’s rate limit, the system issues a penalty ticket (p-ticket). It’s how the API asks a caller to slow down when it has been calling a single endpoint too heavily.

A p-ticket is a REST or WebSocket response the API returns in the body of a 200 response, carrying a p-ticket token and a p-time rather than the result you asked for.

A p-ticket:

  • Is keyed to your IP’s /24 subnet, not to your user or API key, so callers on a subnet or a shared IP share a limit.
  • Is automatic and behavior-based, so it is not tied to an account’s standing and not turned on and off for specific partners.
  • Is a different system from the user-level rate limit that returns an HTTP 429, so it’s handled differently. See Penalty Tickets vs. 429 Responses.
  • Is the same for every integration: per-endpoint penalty-ticket limits are global.

The one thing set per partner is your overall traffic top limit, the user-level request rate that governs HTTP 429 responses. See Rate Limits.

However, p-tickets have limitations:

  • There is no warning that you are approaching a rate limit. P-tickets only tell you when you’re already over a rate limit, whether using REST or WebSocket.
  • There is no way to query how many calls you have left on an endpoint before you hit its rate limit.
  • Only waiting clears a penalty ticket. The count comes down on its own as your recent calls age out of the window. Support cannot clear an active penalty ticket, and a manual rate-limit reset does not touch an endpoint’s penalty state.

Since a penalty ticket comes back as an HTTP 200 OK instead of an error, you must check every 200 response body for a p-ticket field.

Penalty Ticket Fields

A penalty ticket can arrive on REST responses and over WebSocket. The response carries these fields:

FieldWhat it tells you
p-ticketAn encrypted token tied to your IP address. Include it in the request body when you retry.
p-timeHow many seconds to wait before retrying. Calculated using the back-off of an endpoint’s rate limit.
p-captchaWhen true, the challenge requires a reCAPTCHA and cannot be answered programmatically.
p-messageA description of the limit that was exceeded (for example, “more than 20 requests per hour”). It can be general or missing, because a penalty ticket reflects your overall call volume rather than one specific request. For the actionable detail, check the failed call’s violations and errorText fields.

What Triggers a Penalty Ticket

You get a p-ticket by calling an endpoint more than the endpoint’s hourly rate limit. What counts toward that limit depends on the endpoint:

  • Most endpoints count only failed calls:

    • Bad credentials
    • Malformed requests
    • Non-existent accounts
    • An HTTP 4xx or 5xx status
    • An HTTP 200 response that carries an error (usually business-level) in its body
  • A smaller set of sensitive endpoints counts every call, whether it succeeds or fails. These reach their limit quickly under heavy use, so pace your calls to them.

For a list of rate limits for each endpoint, see Rate Limits.

For how to read violations and errorText, see Error Handling.

Penalty Ticket Time

A p-ticket returns a p-time, which is the number of seconds to wait before you retry the call. The endpoint’s call count is tracked over a rolling 60-minute window, so your calls age out about an hour after you make them, rather than the count resetting at the top of the hour.

p-time is additive: it is calculated by adding a back-off to any remaining p-time. For your first p-ticket, your p-time is one back-off. Each additional p-ticket during that period adds another back-off to the remaining p-time.

For example, if an endpoint has a back-off of 15 and you call it enough times to receive three p-tickets, you have to wait 45 seconds from the time of the first p-ticket.

To see the rate limit and back-off of each endpoint, see Rate Limits.

When Your Count Falls Back Under the Limit

As the rolling window moves forward, your older calls age out and your count for that endpoint falls back under its limit. Once your count is back under the limit, even if you still have p-time remaining:

  • If you do not exceed the limit again, your next call succeeds.
  • If you exceed the limit again, the new p-time is added to whatever p-time remained.

Too Many Retries (HTTP 429)

If you retry too many times before the p-time elapses, you will eventually receive an HTTP 429, a user-level limit across all your endpoints.

If you receive an HTTP 429, you must wait one hour before calling any endpoint again.

If you retry before the hour has elapsed, each call returns another HTTP 429 (empty body, no wait time to read) and resets the one-hour clock. The window only clears once you stop calling for the full hour.

If Support lifts a 429, that does not clear your per-endpoint penalty state. When you resume calling, any endpoint whose rolling count is still over its limit keeps returning penalty tickets until that count drains, so keep including the p-ticket when you retry.

For more information, see Penalty Tickets vs. 429 Responses.

How to Handle a Penalty Ticket

Since a p-ticket indicates that you’re over that endpoint’s rate limit, you must wait the number of seconds listed in the HTTP 200 response’s p-time, then send the request again with the p-ticket value added.

When a response carries a p-ticket:

  1. Read the p-message, violations, and errorText fields. These tell you the limit you crossed and the reason so you know how to prevent future p-tickets.
  2. Check for the p-captcha field. If p-captcha is true, the operation can’t be retried from a server-to-server integration. Wait about one hour for the endpoint’s count to clear, then resume normal calls.
  3. Wait the full p-time. Wait the number of seconds in p-time before you retry. Do not retry early, because that will increase the amount of time you must wait.
  4. Retry with the p-ticket value. Resend the identical request body with the p-ticket value added as an extra field.

How to Prevent and Reduce Penalty Tickets

You can prevent and reduce p-tickets in several ways:

  • Review HTTP 200 responses for violations (business-logic problems) and errorText (non-business failures). Fixing underlying failures is what prevents p-tickets.

  • Look records up by ID instead of polling. Polling an endpoint to check whether something is ready sends a steady stream of calls that pushes you toward its limit. Instead, store the identifier you get back when you create a record and fetch that record directly by its ID to read its status, rather than polling getAccountTradingPermissions. For example, after you call createTradingPermission, keep the ID it returns mapped to your own user ID and fetch that permission by its ID.

  • Don’t route many users through one IP. Because limits are counted per subnet, funneling many users through a single outbound address makes them share one limit. Spread traffic across addresses where you can.

  • Use WebSocket subscriptions instead of REST polling. Subscribing to updates avoids the repeated calls that push you toward a limit.

Automate Using a Penalty Ticket Handler

You can automate p-ticket handling in your integration by building a p-ticket handler that inspects responses. The handler should do the following:

  • When an HTTP 200 response carries a p-ticket, wait p-time seconds, then resend the original request with the p-ticket added.
  • When an HTTP 200 response’s p-captcha is true, you can’t retry it from a server-to-server integration. Wait out the cooldown (about one hour) for the count to clear, then resume normal calls.
  • If an HTTP 429 is detected, stop all calls for one hour. Log and surface for visibility. See Penalty Tickets vs. 429 Responses.
  • If an errorText or violations field is detected, do not retry the call. Log and surface for a fix.

For more information, see Error Handling.

Handler Example

The following examples are starting points for a penalty ticket handler. makeRequest is a placeholder for your own HTTP client.

These examples have not been tested. Use them as a starting point. Do not implement them without testing first.

1const waitForMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2
3// Resend on a penalty ticket until the call goes through (or the cap is hit).
4const submit = async (endpoint, data, maxAttempts = 5) => {
5 let body = data;
6
7 for (let attempt = 1; attempt <= maxAttempts; attempt++) {
8 const response = await makeRequest(endpoint, body);
9 const ticket = response['p-ticket'];
10
11 // No penalty ticket: normal response.
12 if (!ticket) return response;
13
14 if (response['p-message']) console.log(`Rate limit reason: ${response['p-message']}`);
15
16 // A reCAPTCHA can't be answered from a server-to-server integration.
17 if (response['p-captcha']) {
18 throw new Error('reCAPTCHA required; cannot retry. Wait out the cooldown, then resume normal calls.');
19 }
20
21 const time = response['p-time'];
22 console.log(`Penalty ticket present. Waiting ${time}s before retry ${attempt}.`);
23 await waitForMs(time * 1000);
24
25 // Resend the original request with the latest p-ticket added.
26 body = { ...data, 'p-ticket': ticket };
27 }
28
29 throw new Error(`Still penalized after ${maxAttempts} attempts. Back off and try later.`);
30};

Penalty Tickets vs. 429 Responses

Penalty tickets and 429 responses are two different limits with two different recoveries. Handling one is not the same as handling the other.

Penalty ticket429 response
Limit typePer-endpoint call limitUser-level limit across all your endpoints
HTTP status200 OK429 Too Many Requests
How you knowA p-ticket field in the response bodyThe 429 status code
Wait timeProvided in p-timeOne hour (not reported anywhere)
How you recoverWait p-time, then retry with the p-ticketWait one hour from your most recent call, then retry
Consequence for retrying earlyNew p-time in response, with additional back-off added to existing p-timeEach early call returns another empty 429 and resets the one-hour clock

A 429 is the broader of the two: a user-level limit across all your endpoints, not one endpoint’s count, and usually what you hit if you ignore penalty tickets and keep hammering. Unlike a penalty ticket, it tells you nothing about how long to wait: there is no p-time, no header, no body to read. The only way out is to stop calling completely. The clock runs from your most recent request, so any call, even a test, restarts the hour. Wait it out, then try once.

For the 429 response details, see Rate Limits.