QUANTM7 Docs
Headless APIAPI Reference

Errors & Rate Limiting

Error codes, rate limiting rules, and best practices for handling API errors.

Error format

Every error response follows the same structure:

{
  "error": {
    "code": "not_found",
    "message": "Product not found"
  }
}
FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description

Error codes

CodeHTTP StatusDescription
unauthorized401Missing, invalid, or revoked API key
forbidden403Endpoint requires a secret key but a publishable key was used
bad_request400Invalid request body or parameters
not_found404Resource does not exist
rate_limited429Too many requests (see below)
internal_error500Server error (contact support if persistent)

HTTP status codes

StatusMeaning
200Success
400Bad request (check the error message for details)
401Unauthorised (check your API key)
403Forbidden (use a secret key for this endpoint)
404Not found
429Rate limited (wait and retry)
500Server error

Rate limiting

Each API key has its own rate limit bucket. Limits are per key, not per IP address.

Key typeLimit
Publishable (q7_pk_)100 requests per minute
Secret (q7_sk_)30 requests per minute

Response headers

Every response includes a rate limit header:

X-RateLimit-Remaining: 87

This tells you how many requests you have left in the current window.

When you hit the limit

The API returns a 429 status with a Retry-After header:

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests"
  }
}
Retry-After: 12

Wait the number of seconds in Retry-After before sending another request.

Avoiding rate limits

Cache responses. Product data does not change every second. Cache API responses for 60 seconds or longer on your server or at the edge.

Use static generation. If you build with Astro, Next.js, or Nuxt, fetch data at build time instead of on every page view. One API call serves thousands of visitors.

Batch requests. Fetch a full page of products (up to 100 per page) instead of loading them one at a time.

Use ISR. With Next.js or similar frameworks, use Incremental Static Regeneration to rebuild pages in the background. Each rebuild makes one API call regardless of traffic.

Pagination

List endpoints return paginated results with a meta object:

{
  "data": [ ... ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 84
  }
}
ParameterDefaultMaxDescription
page1No limitPage number (1-indexed)
per_page25100Items per page

To fetch all items, increment page until page * per_page >= total.

async function fetchAllProducts(apiKey: string) {
  const products = []
  let page = 1
  let total = Infinity

  while ((page - 1) * 100 < total) {
    const res = await fetch(
      `https://api.quantm7.com/v1/products?page=${page}&per_page=100`,
      { headers: { 'Authorization': `Bearer ${apiKey}` } }
    )
    const json = await res.json()
    products.push(...json.data)
    total = json.meta.total
    page++
  }

  return products
}

Best practices

Handle errors gracefully

Always check the response status before reading the body.

const res = await fetch('https://api.quantm7.com/v1/products/classic-tee', {
  headers: { 'Authorization': `Bearer ${apiKey}` }
})

if (!res.ok) {
  const { error } = await res.json()

  if (res.status === 404) {
    // Product does not exist, show a 404 page
    return notFound()
  }

  if (res.status === 429) {
    // Rate limited, wait and retry
    const retryAfter = parseInt(res.headers.get('Retry-After') || '5')
    await new Promise(r => setTimeout(r, retryAfter * 1000))
    // Retry the request...
  }

  throw new Error(`API error: ${error.message}`)
}

const { data: product } = await res.json()

Use the right key for the job

TaskKey to use
Fetch products on a pagePublishable
Load collections at build timePublishable
Render blog postsPublishable
Create a cart from a server functionSecret
Process checkout from your backendSecret

Keep secret keys on the server

Never import or reference your secret key in client-side code. Use API routes or server functions to proxy cart and checkout operations.

// GOOD: Secret key stays on the server
// src/pages/api/create-cart.ts (Astro server endpoint)
export async function POST() {
  const res = await fetch('https://api.quantm7.com/v1/cart', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${import.meta.env.Q7_SECRET_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ currency: 'GBP' })
  })
  return new Response(JSON.stringify(await res.json()))
}
// BAD: Secret key exposed to the browser
const res = await fetch('https://api.quantm7.com/v1/cart', {
  method: 'POST',
  headers: {
    // This key is visible in the browser's network tab
    'Authorization': 'Bearer q7_sk_NEVER_DO_THIS'
  }
})

On this page