Polyfence is in Early Access — APIs and limits may still change. Your feedback shapes what we build.

API errors

Every error the Polyfence API can return: the machine code to branch on, the HTTP status, the human message, when it fires, and what to do. Use this when writing retry logic, error handling, or onboarding integrations.

HTTP status codes

Polyfence uses standard HTTP semantics. The status tells you the failure class; the code field tells you the specific, stable cause — branch on code, never on the human message.

StatusMeaning
200/201/204Success. 200 for GET, 201 for resource creation, 204 for successful deletion.
400Bad request — validation_error / invalid_json / invalid_id.
401Unauthorized — unauthenticated / invalid_key.
403Forbidden — forbidden (scope/role), quota_exceeded (tier quota), or feature_unavailable.
404Not found — resource missing OR not accessible to your tenant (intentionally indistinguishable).
409Conflict — duplicate, stale state, or in-progress operation.
413Payload too large — payload_too_large.
422Unprocessable — unsupported_format (understood but not parseable, e.g. no zones in an import).
429Rate limited — rate_limited, per-second cap. Includes a Retry-After header. (Tier quotas are 403 quota_exceeded, NOT 429.)
500Internal server error — internal_error. Report to hello@polyfence.io with the request ID.
502Upstream error — upstream_error, an external fetch/sync failed (e.g. connector source).
504Gateway timeout — typically a connector sync exceeded its time budget. Infrastructure-level (no JSON envelope).

Error envelope

Every error response is the same flat shape. Branch on the machine-readable code (a closed enum) — never parse the human message. error is a short title (the category); message is the specific, human-readable detail (free to change/localise).

{
  "success": false,
  "code": "not_found",
  "error": "Not found",
  "message": "Zone not found"
}

// Validation errors add an "errors" array with field-level detail:
{
  "success": false,
  "code": "validation_error",
  "error": "Validation error",
  "message": "Zone data validation failed",
  "errors": [
    { "field": "radius_meters", "rule": "min", "value": 0 }
  ]
}

// Quota errors carry tier context on the root for the upgrade flow:
{
  "success": false,
  "code": "quota_exceeded",
  "error": "Quota exceeded",
  "message": "Zone limit reached",
  "current": 100, "limit": 100, "tier": "free", "upgradeUrl": "/account?tab=plan"
}

The full list of code values is a closed enum — see the typed ErrorCode in the OpenAPI spec and the generated SDKs (v2.0.0+).

Authentication

Most endpoints accept either a session cookie or an `x-api-key` header (dual auth). A few are session-only (e.g. device provisioning, account deletion) and a few are key-only — the row below names the case where it matters. Authentication is checked BEFORE body/parameter validation, so a malformed request with no/invalid key returns an auth error, not a validation error.

401unauthenticatedAuthentication required. Provide either a valid session token or x-api-key header.
When
Request hit a dual-auth endpoint with no session cookie AND no x-api-key header.
Fix
Pass your API key via x-api-key (case-insensitive). Generate one at /account?tab=keys.
401invalid_keyInvalid API key.
When
The x-api-key value doesn't match any stored key (SHA-256 hash lookup miss), or is malformed (no prefix).
Fix
Verify the key is copied correctly — keys have a `pf_`/`pt_` prefix. If unsure, regenerate at /account?tab=keys.
401unauthenticatedUnauthorized
When
Generic auth failure on a single-mode (API-key-only or session-only) endpoint.
Fix
Same as above — check your auth method matches the endpoint's requirement.
403forbiddenInsufficient permissions. Required scopes: …
When
Your key authenticated, but doesn't have the scope required for this endpoint (e.g. attempted DELETE with a `zones:read` key).
Fix
Issue a new key with the required scope. Scopes are immutable on existing keys.
403forbiddenKey not authorised for this device
When
Device-bound key attempted to access a device_id it isn't bound to.
Fix
Use the device-bound key only for its target device. Use a tenant-scope key for cross-device operations.
401unauthenticatedSession required. Sign in on the dashboard to provision a device.
When
Provisioning endpoint hit with an API key instead of a dashboard session.
Fix
Provisioning is dashboard-only — sign in at polyfence.io/auth/login first.

Rate limits

Polyfence enforces 50 requests per second per identifier during Early Access. `rate_limited` responses are 429 and include a Retry-After header. NOTE: monthly tier quotas are a SEPARATE failure class — they return 403 `quota_exceeded` (see Tier limits), not 429. A 429 always means “slow down,” never “out of quota.”

429rate_limitedRate limit exceeded (… req/sec on … tier). Upgrade your plan for higher limits.
When
You exceeded the per-second request cap from a single API key or session.
Fix
Read the Retry-After header and back off. Implement exponential backoff with jitter for retries.
429rate_limitedToo many requests. Please try again shortly.
When
Per-endpoint stricter limit hit (e.g. checkout endpoint has a tighter cap to prevent abuse).
Fix
Wait per Retry-After and retry.
429rate_limitedToo many checkout attempts. Please try again shortly.
When
Checkout endpoint specifically — capped tighter than the general rate limit.
Fix
Wait at least 60s before retrying. If checkout repeatedly fails, contact support.

Tier limits

Polyfence's tier system enforces quotas on zones, API calls, and active API keys. Hits return 403 `quota_exceeded` (distinct from 429 `rate_limited`, so clients can tell “out of quota” from “slow down”). During Early Access most quotas are unlimited; the pricing flip moves these to Free/Pro tier values.

403quota_exceededZone limit reached
When
You attempted to create a zone when your account is at the tier's zone cap.
Fix
Delete unused zones, or contact hello@polyfence.io to upgrade.
403quota_exceededZone limit would be exceeded …
When
Bulk import would push your zone count above the tier cap.
Fix
Reduce the import batch, delete unused zones, or upgrade.
403quota_exceededMonthly API call limit exceeded (…/…). Upgrade your plan for more API calls.
When
Your monthly API call quota is exhausted for the billing period. (Was 429 before 2026-06-12 — now 403 to distinguish from per-second rate limiting.)
Fix
Wait until the next billing cycle, or upgrade for a higher cap.
403quota_exceededAPI key limit reached
When
You already have the maximum number of active API keys (10 by default).
Fix
Revoke an unused key at /account?tab=keys before creating a new one.
403feature_unavailableBulk import is not available during Early Access at your current usage level. Contact hello@polyfence.io if you need this feature.
When
A feature gated by your tier/Early-Access flag was used (e.g. POST /zones/bulk-import without the bulkImportUI feature).
Fix
The feature isn't enabled for your tier yet — contact hello@polyfence.io. Distinct from quota_exceeded (you're not out of quota; the capability is gated off).

Validation

Request bodies are validated with Zod schemas. Validation failures return 400 `validation_error` (or a more specific code like `invalid_json` / `invalid_id`) with a message naming the offending field or rule.

400invalid_jsonRequest body is not valid JSON.
When
Request body isn't valid JSON.
Fix
Verify Content-Type: application/json and that the body is well-formed.
400validation_errorValidation failed
When
Zod schema rejected one or more fields. Response includes an `errors` array with per-field detail.
Fix
Inspect the `errors` array; each entry names the field and the rule that failed.
400validation_errorZone data validation failed
When
Zone payload failed deep validation (e.g. polygon self-intersects, radius out of range, coordinate out of [-90, 90] or [-180, 180]).
Fix
POST the payload to /api/zones/validate first to surface the exact violation, or inspect the `errors` array on the response.
400validation_errorCircle zones require center coordinates and radius
When
POST /zones with type=circle but missing center_lat, center_lng, or radius_meters.
Fix
Include all three fields for circle zones.
400validation_errorPolygon zones require polygon coordinates
When
POST /zones with type=polygon but missing or empty polygon coordinates.
Fix
Provide a polygon with at least 3 [lng, lat] pairs.
400validation_errorInvalid dev_eui — must be 16 hexadecimal characters
When
Device provisioning payload had a malformed DevEUI.
Fix
DevEUI must be exactly 16 hex characters (case-insensitive).
400invalid_idZone ID must be a valid UUID
When
zone_id parameter isn't a valid UUID.
Fix
Zone IDs are UUIDs (e.g. `123e4567-e89b-12d3-a456-426614174000`).
400validation_errorInvalid scopes provided
When
API key creation request included scopes not in the allowed set.
Fix
Use scopes from: zones:read, zones:write, zones:delete, zones:* (and admin:* for admin keys).
400validation_errorMissing Idempotency-Key header (required for deduplication)
When
An idempotent endpoint (e.g. provisioning) was called without an Idempotency-Key header.
Fix
Generate a UUID per logical operation and pass it as Idempotency-Key — retries with the same key are deduplicated.
422unsupported_formatNo zones found in the data
When
POST /zones/import fetched the source successfully but found no parseable zones in it.
Fix
Check the source returns GeoJSON/KML with at least one feature. 422 means the body was understood but unprocessable.
400validation_errorOne or more zone_ids are not accessible to this account
When
Bulk operation referenced zone IDs that don't belong to your tenant.
Fix
Filter the list to zones your account owns. The error doesn't reveal which IDs failed (no enumeration).
400validation_errorToo many zones in bulk import
When
Bulk import payload exceeds the 500-zone cap.
Fix
Split into multiple batches of ≤500 zones each.

Not found

Polyfence returns 404 `not_found` both for genuinely missing resources and for cross-tenant access attempts. The two are intentionally indistinguishable to avoid resource enumeration.

404not_foundZone not found
When
Zone ID doesn't exist, or exists under a different tenant (`profile_user_id` filter doesn't match).
Fix
Verify the zone belongs to your account. Listing GET /zones shows everything your key can access.
404not_foundDevice not found
When
Device ID doesn't exist or belongs to a different tenant.
Fix
Same — verify the device is provisioned to your account.
404not_foundProfile not found for authenticated user
When
The authenticated user has no row in the profiles table (rare — usually a sync issue with Supabase trigger).
Fix
Sign out and sign back in to trigger the profile-creation hook. If it persists, contact support.
404not_foundNot found
When
Generic 404 for unmatched routes or missing related resources (e.g. an event whose parent zone was deleted).
Fix
Check the URL path and any referenced IDs.
404not_foundAssignment not found
When
Device-to-zone assignment lookup missed.
Fix
Verify the device is currently assigned to the zone via GET /v1/devices/{deviceId}.

Conflict / idempotency

Mostly relevant to device provisioning and bulk operations. 409 `conflict` responses signal a state collision rather than a malformed request.

409conflictYou already have a zone named “…”. Pick a different name to keep your zones distinguishable.
When
POST /zones with a name that's already used in your account (the unique constraint is per-tenant).
Fix
Pick a unique name. Zone names don't need to be globally unique — only within your account.
409conflictDevEUI collision — retry the provisioning request
When
The auto-generated DevEUI collided with an existing device (rare; ~2^-64 odds per attempt).
Fix
Retry with the same Idempotency-Key — provisioning will pick a fresh DevEUI.
409conflictProvisioning already in progress for this device — retry
When
A concurrent provisioning request for the same device is still completing.
Fix
Wait ~1s and retry with the same Idempotency-Key.

Connector / SSRF

Data connector endpoints reject URLs targeting private networks, localhost, link-local, and cloud metadata services. Validation runs both at config-save time AND at fetch time (DNS rebinding protection). These reject with 400 `validation_error`; an oversized fetched body returns 413 `payload_too_large`.

400validation_errorOnly HTTP and HTTPS protocols are allowed
When
Connector URL uses a non-HTTP scheme (file://, ftp://, gopher://, etc.).
Fix
Use https:// in production. http:// is allowed but discouraged.
400validation_errorCannot connect to localhost
When
Resolved DNS pointed at 127.0.0.1, ::1, or localhost.
Fix
Use a publicly addressable host. Connectors run from Polyfence's infrastructure — your localhost isn't reachable.
400validation_errorCannot connect to private IP address (10.x.x.x)
When
Resolved DNS pointed at the 10.0.0.0/8 RFC1918 block.
Fix
Expose the service publicly (Cloudflare Tunnel, ngrok, or a public ingress) and use that hostname.
400validation_errorCannot connect to private IP address (172.16-31.x.x)
When
DNS resolved into 172.16.0.0/12.
Fix
Same — expose publicly.
400validation_errorCannot connect to private IP address (192.168.x.x)
When
DNS resolved into 192.168.0.0/16.
Fix
Same — expose publicly.
400validation_errorCannot connect to link-local address
When
DNS resolved into 169.254.0.0/16 (link-local) or fe80::/10 (IPv6 link-local).
Fix
Use a routable address.
400validation_errorCannot connect to cloud metadata service
When
DNS resolved into 169.254.169.254 (AWS/GCP/Azure metadata IP).
Fix
These IPs are blocked unconditionally — they're an SSRF risk regardless of cloud provider.
400validation_errorInvalid URL format
When
URL didn't parse as a valid HTTP(S) URL.
Fix
Use a standard URL form: `https://host[:port]/path?query`.
413payload_too_largeResponse too large. Maximum size is 10MB.
When
Connector source returned a response body larger than 10MB.
Fix
Paginate at the source or use a connector that streams.
502upstream_errorFailed to fetch URL: … (upstream status)
When
The connector/import source returned a non-2xx response or was unreachable (e.g. POST /zones/import against a URL that 5xx'd or timed out).
Fix
Verify the source URL is reachable and returns 2xx. 502 means the failure is upstream of Polyfence, not in your request.

Subscription

Billing-related endpoints (currently routed via Polar). Most users won't hit these during Early Access.

400validation_errorInvalid tier for checkout
When
Checkout request named a tier that doesn't have a Polar product configured.
Fix
Use `pro` as the tier. Other tiers aren't wired for self-service checkout yet.
500internal_errorNo product configured for this tier.
When
POLAR_PRO_PRODUCT_ID env is unset on the server.
Fix
Contact hello@polyfence.io — this is a configuration issue on our side, not yours.
409conflictYou are already on this plan.
When
Checkout attempted for the tier the account is currently on.
Fix
No action needed — the API is preventing a redundant subscription.
404not_foundNo subscription to reactivate.
When
Reactivation endpoint hit but the account has no canceled subscription on record.
Fix
Start a fresh checkout via POST /api/checkout if you want to subscribe.

Looking for the rest of the docs? Back to developer documentation.

Polyfence API errors — catalog