Rate Limiting
The Addepar API enforces rate limits at the firm level. Every API key and OAuth application within the same firm shares a single request budget. Understanding this shared model is essential before building any integration that makes concurrent or scheduled requests.
Budget model
Rate limits apply per firm, across all credentials. If your firm has three integrations (a CRM sync, a reporting pipeline, and an interactive dashboard), all three draw from the same pool.
| Window | Budget | Consequence |
|---|---|---|
| 15-minute rolling | 50 requests | 429 Too Many Requests when exceeded |
| 24-hour rolling | 1,000 requests | 429 Too Many Requests when exceeded |
| Per-request timeout | 60 seconds | 400 Bad Request if the server cannot respond in time |
The 15-minute window constrains burst patterns. The 24-hour window constrains total daily volume. Both run simultaneously.
When either limit is exceeded, the response includes an X-RateLimit-Retry-After header with the number of seconds until the next request will be accepted. Requests sent before that time will continue to receive 429.
If a single request takes longer than 60 seconds to process server-side (common with large portfolio queries or entity batch operations), the server cancels it and returns 400 Bad Request. This is not a rate limit violation. It does not consume budget. Restructure the request (reduce page[limit], narrow the date range, reduce query columns) and retry.
Proactive rate limit header
When your usage reaches approximately 85% of the rate limit threshold, the API includes a RateLimit response header conforming to the IETF draft standard:
RateLimit: limit=1000, remaining=150, reset=3600
| Field | Meaning |
|---|---|
limit | Maximum requests allowed in the current window |
remaining | Requests left before the limit is reached |
reset | Seconds until the window resets |
This header does not appear on every response. It activates only when you approach the threshold. The absence of the header means you are well within budget.
Implementation pattern:
import time
def make_request(url, headers):
response = requests.get(url, headers=headers)
if response.status_code == 429:
wait = int(response.headers.get('X-RateLimit-Retry-After', 60))
time.sleep(wait)
return make_request(url, headers)
ratelimit = response.headers.get('RateLimit')
if ratelimit:
remaining = parse_remaining(ratelimit)
if remaining < 10:
time.sleep(5) # Proactive backoff
return response
Exempt endpoints
GET requests to these endpoints do not count toward either rate limit window:
| Endpoint | Why exempt |
|---|---|
GET /v1/jobs | Job polling requires frequent requests to detect completion |
GET /v1/transaction_jobs | Same polling pattern for transaction batch jobs |
GET /v1/users/me | Identity verification is lightweight and common at session start |
GET /v1/imports | Import status polling follows the same pattern as jobs |
Only GET requests to these paths are exempt. POST, PATCH, and DELETE to the same resources consume budget normally.
This exemption exists because the Jobs async pattern requires polling. A typical job workflow makes 1 POST (submit) then 10-50 GETs (poll until complete). Without the exemption, a single batch operation would consume most of the 15-minute budget on polling alone.
Budget planning
Given the constraints (50/15min, 1000/24hr), plan your integration's request allocation:
| Integration pattern | Typical budget consumption | Guidance |
|---|---|---|
| Real-time dashboard | High burst, low daily | Implement caching. Re-query only on user action, not on interval. |
| Scheduled sync (hourly) | Low burst, moderate daily | 24 syncs × ~20 requests = 480/day. Leaves room for other integrations. |
| Bulk data export | High burst, high daily | Use Jobs for any query touching >30 portfolios. Jobs consume 1 request to submit + exempt polling. |
| Event-driven webhook consumer | Unpredictable burst | Buffer incoming events and batch API calls. Never fan-out 1:1. |
If multiple integrations share a firm, allocate budget explicitly. A runaway sync that consumes the full 15-minute window blocks every other integration at that firm for the remainder of the window.
Requesting higher limits
The default limits apply to all firms. If your integration requirements exceed 1,000 requests per day, contact Addepar Support with:
- The integration's purpose and request pattern
- Current daily request volume
- Requested limit and justification
Log in to your Addepar account, click the question mark icon on the Global Navigation bar, and select Contact Support.
Related
- Response Codes - Full 429 error response format and troubleshooting
- Jobs - Async pattern that minimizes rate limit consumption
- Pagination - Control result set size to avoid timeouts
Updated 4 days ago