API errors
Every HTTP error the Polyfence API can return: status code, the exact message string, 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 code tells you the failure class; the message tells you the specific cause.
| Status | Meaning |
|---|---|
| 200/201/204 | Success. 200 for GET, 201 for resource creation, 204 for successful deletion. |
| 400 | Bad request — validation failed, malformed body, or invalid parameter. |
| 401 | Unauthorized — missing or invalid API key / session. |
| 403 | Forbidden — authenticated, but key lacks the required scope. |
| 404 | Not found — resource missing OR not accessible to your tenant (intentionally indistinguishable). |
| 409 | Conflict — duplicate, stale state, or in-progress operation. |
| 413 | Payload too large. |
| 429 | Rate limit OR tier quota hit. Rate-limit responses include a Retry-After header; tier-limit responses currently don’t. |
| 500 | Internal server error. Report to hello@polyfence.io with the request ID. |
| 504 | Gateway timeout — typically a connector sync exceeded its time budget. |
Error envelope
Every error response shares the same envelope. Switch on the HTTP status for failure class; switch on the error string for the specific cause.
{
"success": false,
"error": "Zone not found"
}
// Some endpoints add a "details" array with field-level breakdowns:
{
"success": false,
"error": "Validation failed",
"details": [
{ "field": "radius_meters", "rule": "min", "value": 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 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.
Invalid API key- When
- The x-api-key value doesn't match any stored key (SHA-256 hash lookup miss).
- Fix
- Verify the key is copied correctly — keys have a `pf_` prefix. If unsure, regenerate at /account?tab=keys.
Unauthorized- 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.
Forbidden- 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.
Key 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.
Session 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. 429 responses include a Retry-After header.
Rate limit exceeded- When
- You exceeded 50 req/sec from a single API key or session.
- Fix
- Read the Retry-After header and back off. Implement exponential backoff with jitter for retries.
Too 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.
Too 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 429 with a tier-specific message. During Early Access most quotas are unlimited; pricing flip moves these to Free/Pro tier values.
Zone 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.
Zone limit exceeded- When
- Bulk import would push your zone count above the tier cap.
- Fix
- Reduce the import batch, delete unused zones, or upgrade.
API call limit reached- When
- Your monthly API call quota is exhausted for the billing period.
- Fix
- Wait until the next billing cycle, or upgrade for a higher cap.
API 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.
Validation
Request bodies are validated with Zod schemas. Validation failures return 400 with a specific message naming the offending field or rule.
Invalid JSON- When
- Request body isn't valid JSON.
- Fix
- Verify Content-Type: application/json and that the body is well-formed.
Validation failed- When
- Zod schema rejected one or more fields. Response includes a `details` array with per-field errors.
- Fix
- Inspect the `details` array; each entry names the field and the rule that failed.
Zone 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 `details` array on the error response.
Circle 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.
Polygon zones require polygon coordinates- When
- POST /zones with type=polygon but missing or empty polygon_coordinates array.
- Fix
- Provide a polygon_coordinates array with at least 3 [lng, lat] pairs.
Invalid 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).
Invalid zone ID format- When
- zone_id parameter isn't a valid UUID.
- Fix
- Zone IDs are UUIDs (e.g. `123e4567-e89b-12d3-a456-426614174000`).
Invalid 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).
Missing 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.
Not found
Polyfence returns 404 both for genuinely missing resources and for cross-tenant access attempts. The two are intentionally indistinguishable to avoid resource enumeration.
Zone 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.
Device not found- When
- Device ID doesn't exist or belongs to a different tenant.
- Fix
- Same — verify the device is provisioned to your account.
Profile 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.
Not 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.
Assignment 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. These responses signal a state collision rather than a malformed request.
Duplicate zone name- 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.
DevEUI 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.
Provisioning 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.
One 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).
Too many zones in bulk import- When
- Bulk import payload exceeds the 500-zone cap.
- Fix
- Split into multiple batches of ≤500 zones each.
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).
Only 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.
Cannot 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.
Cannot 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.
Cannot connect to private IP address (172.16-31.x.x)- When
- DNS resolved into 172.16.0.0/12.
- Fix
- Same — expose publicly.
Cannot connect to private IP address (192.168.x.x)- When
- DNS resolved into 192.168.0.0/16.
- Fix
- Same — expose publicly.
Cannot 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.
Cannot 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.
Invalid URL format- When
- URL didn't parse as a valid HTTP(S) URL.
- Fix
- Use a standard URL form: `https://host[:port]/path?query`.
Response 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.
Subscription
Billing-related endpoints (currently routed via Polar). Most users won't hit these during Early Access.
Invalid 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.
No 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.
You 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.
No 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.