Data API
Errors
Error response format and HTTP status codes for the Parcel Data API.
Data API errors use one of two JSON shapes, depending on where the error is raised.
Handler errors (authentication, plan and permission, not found, rate limiting, a missing workspace_id, an invalid search_after cursor, an unknown include key) use a consistent envelope:
{ "error": { "code": "<string>", "message": "<string>" }}code is a machine-readable identifier; message is a human-readable description.
Request-body validation errors (a malformed body, a value that fails schema validation such as limit over 100, an unknown filter or sort key, or an out-of-vocabulary value on an enum field) are caught by the schema validator before the handler runs. They return 400 with a different shape:
{ "success": false, "error": { "issues": [ { "code": "unrecognized_keys", "keys": ["bogus_key"], "path": ["filters"], "message": "Unrecognized key(s) in object: 'bogus_key'" } ], "name": "ZodError" }}An out-of-vocabulary value on an enum-backed filter (for example stage,
primary_use, signal_type, account_type, or role) fails validation with a
single clean issue naming the allowed values. Because a filter value may be a
single value or an array, the issue code is invalid_union, but the message
is the clean, actionable one (the same for a bad scalar and a bad array element):
{ "success": false, "error": { "issues": [ { "code": "invalid_union", "path": ["filters", "stage", "value"], "message": "Invalid value for this field. Provide one of the allowed values, or an array of them: pre_filing, filed, under_review, approved, permitted, under_construction, completed." } ], "name": "ZodError" }}Enum fields are matched exactly, so they do not accept exact_match; passing it
on an enum filter is also a 400.
When handling errors, check the HTTP status first, then branch on error.code (envelope) or error.issues (validation).
Status code reference
| Status | Code | When it occurs |
|---|---|---|
400 | bad_request (handler) or a ZodError (body validation) | A malformed body or a value that fails schema validation (e.g. limit over 100, an unknown filter or sort key, an out-of-vocabulary enum filter value) use the validation shape. An invalid search_after cursor, an unknown include key, or a missing workspace_id use the envelope. |
401 | unauthorized | Missing or invalid Authorization: Bearer token. |
403 | forbidden | API key is not permitted for this resource, or the workspace plan does not include Data API access. |
404 | not_found | Record ID not found (GET by ID), or unknown route. |
429 | rate_limited | Workspace has exceeded the 250,000-credit rolling 24-hour ceiling. |
400 Bad request
Handler-level 400s use the envelope with code bad_request (an invalid search_after cursor, an unknown include key, or a missing workspace_id on a user-token request):
{ "error": { "code": "bad_request", "message": "workspace_id is required for user tokens." }}Request-body schema failures (a malformed body, limit over 100, an unknown filter or sort key, or an out-of-vocabulary value on an enum-backed filter) instead return the ZodError validation shape shown at the top of this page.
401 Unauthorized
{ "error": { "code": "unauthorized", "message": "Missing bearer token" }}If you are using a Supabase user JWT (e.g. via the MCP server), include ?workspace_id=<uuid> on every request. Omitting it returns a 400 bad_request (not a 401): the token is valid, but the request is missing required attribution.
403 Forbidden
Plan access
The Data API requires the Pro plan. Free and Starter workspaces receive this response on every Data API call.
{ "error": { "code": "forbidden", "message": "The Data API requires the Pro plan." }}Key not permitted
An API key that has been restricted or revoked also returns 403 with code forbidden.
429 Rate limited
Returned when the workspace has used all 250,000 credits in the current rolling 24-hour window.
{ "error": { "code": "rate_limited", "message": "Workspace Data API credit ceiling reached (used/ceiling in the last 24h). Upgrade your plan or retry later." }}The response includes a Retry-After: 86400 header. Wait until the oldest credits in the window age out (up to 24 hours) before retrying, or upgrade your plan for a higher ceiling.
GET /data/usage is exempt from rate limiting. It returns your current credit consumption and the ceiling even when your workspace is throttled. Use it to check remaining before bulk operations.
curl https://api.parcelengineering.com/api/v1/data/usage \ -H "Authorization: Bearer pcl_your_api_key"Response fields: used_credits, ceiling, remaining, window_hours, by_tool.
404 Not found
{ "error": { "code": "not_found", "message": "Project not found" }}On GET /data/{dataset}/{id}, a 404 means no record with that UUID exists. The Data API serves a global index and uses workspace_id only for usage attribution, so 404 is not workspace-scoped.