Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.permitcore.io/llms.txt

Use this file to discover all available pages before exploring further.

PermitCore returns a uniform error envelope for every non-2xx response:
{
  "error": {
    "code": "missing_credentials",
    "message": "Missing Authorization: Bearer <key> header.",
    "detail": null
  }
}
detail may be null or an object with structured fields specific to the error code (e.g., rate_limited returns { limit_per_month, used, resets_at }).

Error catalog

401 Unauthorized

CodeCauseRetry?
missing_credentialsAuthorization header absentNo — fix client and resubmit
invalid_tokenKey is malformed, revoked, or unknownNo — rotate key via dashboard
expired_tokenMagic-link signin token expired (10 min default)No — request a new sign-in link

403 Forbidden

CodeCauseRetry?
insufficient_scopeKey is valid but lacks the scope required for this endpointNo — request a key with the needed scope
tier_restrictedEndpoint or query parameter requires a higher subscription tierNo — upgrade tier or remove parameter

404 Not Found

CodeCauseRetry?
http_404Path doesn’t exist OR jurisdiction is DEPLOY-PENDINGNo — verify slug + endpoint

429 Too Many Requests

CodeCauseRetry?
rate_limitedQuota exhaustedYes — respect Retry-After header
burst_limitedPer-second burst threshold exceeded (future, Bucket 2)Yes — exponential backoff
When Retry-After is large (multiple hours), the quota has been exhausted for the period. Bring the key up a tier or wait for the period to reset (monthly quotas reset on the first of each month UTC).

5xx Server errors

HTTPCodeCauseRetry?
500internal_errorUnexpected pipeline issueYes — exponential backoff, alert oncall after 3 retries
502upstream_gatewayAPI gateway can’t reach originYes — exponential backoff
503service_unavailableOrigin marked unhealthyYes — exponential backoff
504gateway_timeoutUpstream took too longYes — exponential backoff

Retry guidance

Retryable status codes: 429 (respect Retry-After), 500, 502, 503, 504. All 4xx codes other than 429 are application errors and should NOT be retried — fix the request and resubmit.
Node.js
const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);

class PermitcoreError extends Error {
  constructor(status, code, message) {
    super(message);
    this.status = status;
    this.code = code;
  }
}

async function callPermitcore(path) {
  const res = await fetch(`https://api.permitcore.io${path}`, {
    headers: {
      Authorization: `Bearer ${process.env.PERMITCORE_API_KEY}`,
      Accept: "application/json",
    },
  });
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new PermitcoreError(res.status, body?.error?.code, body?.error?.message);
  }
  return res.json();
}

async function withRetries(fn, maxAttempts = 4) {
  let lastErr;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastErr = err;
      const retryable = err instanceof PermitcoreError && RETRYABLE_STATUS.has(err.status);
      if (!retryable || attempt === maxAttempts) throw err;
      const delay = Math.min(2 ** attempt * 1000, 30000);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw lastErr;
}

// Usage:
const data = await withRetries(() => callPermitcore("/v1/jurisdictions/nyc/cohorts/distribution"));

Diagnosing in production

  • Every response includes a x-permitcore-request-id header (UUID). Include it in any support ticket — we can trace per-request.
  • Vercel deploy logs surface [cohort-distribution] <slug>: <error_type> for any caching layer failures.
  • For programmatic monitoring, the future /v1/keys/{prefix}/usage endpoint includes a recent_requests array with per-request status.