Delivery API

Rate limits and limits

Rate limits, query complexity and request size limits.

The delivery API has a few limits that keep it fast for everyone. Normal websites never get near them, especially ones that cache.

LimitValueWhen you go over
Requests25 a second per IP address429 with Rate limit exceeded.
Query complexity10,000 per requestGRAPHQL_VALIDATION_FAILED
Page size100 documents per listLarger limit values return 100
Request body256 KB413 with an empty body

Request rate#

Each IP address can send 25 requests in any one-second window. Every response has an X-RateLimit-Remaining header with how many requests you have left in the current window. Requests that were rejected count too, so retrying in a tight loop keeps you limited.

Response
HTTP/1.1 429 Too Many Requests
X-RateLimit-Remaining: 0
Content-Type: text/plain
Rate limit exceeded.

Retry with backoff#

On a 429, wait and try again, waiting longer each time:

JavaScript
async function fetchWithRetry(body, attempts = 3) {
for (let attempt = 1; ; attempt += 1) {
const response = await fetch('https://content.pmkin.io/graphql', {
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${process.env.PMKIN_API_KEY}`,
'Content-Type': 'application/json'
},
method: 'POST',
signal: AbortSignal.timeout(10_000)
})
if (response.status !== 429 || attempt === attempts) {
return response
}
// Wait 1, 2, 4 seconds… plus some jitter.
const delay = 1000 * 2 ** (attempt - 1) + Math.random() * 250
await new Promise((resolve) => {
setTimeout(resolve, delay)
})
}
}
Python
import os
import random
import time
import requests
def fetch_with_retry(body, attempts=3):
for attempt in range(1, attempts + 1):
response = requests.post(
'https://content.pmkin.io/graphql',
headers={'Authorization': f"Bearer {os.environ['PMKIN_API_KEY']}"},
json=body,
timeout=10,
)
if response.status_code != 429 or attempt == attempts:
return response
time.sleep(2 ** (attempt - 1) + random.random() / 4)

Cache responses#

The best way to stay under the limit is to not ask twice. Content changes when someone publishes, not on every page view, so:

  • Build static pages at deploy time, or cache rendered pages for a few minutes.
  • Fetch everything a page needs in one request: GraphQL lets you combine queries.
  • During a static build, run requests a few at a time instead of all at once.

Query complexity#

Before it runs a query, the API adds up what it costs. A query that costs more than 10,000 is rejected with GRAPHQL_VALIDATION_FAILED.

  • Most fields cost 1. html costs 10, because rendering it is the expensive part.
  • An object field like category or coverImage costs 1 plus its fields.
  • A list costs its limit, 100 by default, times the cost of one document.
1,200 of 10,000
query Blog {
documents(limit: 100) { # 100 ×
title # 1
slug # + 1
html # + 10
} # = 1,200
}
Keep html off lists

Lists rarely need the full text. Ask for excerpt on index pages and fetch html with documentBySlug on the document’s own page.

Request size#

Request bodies can be up to 256 KB, far more than any query needs. If you hit it, you’re probably inlining data into the query: pass it as variables instead.