Error Reference
All SpatialFlow API errors follow a consistent JSON format. This page documents every error code, HTTP status pattern, and common error scenarios.
Error Response Format
Every error response includes a detail field with a human-readable message:
{
"detail": "Human-readable error message",
"error_code": "MACHINE_READABLE_CODE",
"details": {}
}
| Field | Type | Required | Description |
|---|---|---|---|
detail | string or array | Yes | Human-readable message (string) or validation errors (array) |
error_code | string | No | Machine-readable identifier for programmatic handling |
details | object | No | Additional context (field errors, limits, etc.) |
Validation Errors
When request body validation fails (HTTP 422), the detail field is an array of error objects instead of a string:
{
"detail": [
{
"type": "missing",
"loc": ["body", "data", "name"],
"msg": "Field required"
},
{
"type": "value_error",
"loc": ["body", "data", "geometry", "type"],
"msg": "Value error, unsupported geometry type"
}
]
}
Each validation error object contains:
| Field | Description |
|---|---|
type | Error type (e.g., missing, value_error, string_too_long) |
loc | Location path as an array (e.g., ["body", "data", "name"]) |
msg | Human-readable error message |
HTTP Status Codes
| Status | Meaning | When It Occurs |
|---|---|---|
| 400 | Bad Request | Invalid input, malformed JSON, business rule violation |
| 401 | Unauthorized | Missing or invalid authentication token or API key |
| 403 | Forbidden | Valid auth but insufficient permissions, or no event plan to count uploads against |
| 404 | Not Found | Resource does not exist or is not accessible to the caller |
| 422 | Unprocessable Entity | Request body fails schema validation |
| 429 | Too Many Requests | Request rate limit exceeded |
| 500 | Internal Server Error | Unexpected server failure |
Error Codes Reference
The error_code field provides a machine-readable identifier for programmatic error handling. Not all errors include this field.
| Error Code | HTTP Status | Description |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Global rate limit exceeded (requests per hour) |
QUOTA_UNAVAILABLE | 403 | Location upload refused: the workspace has no event plan to count it against |
WORKFLOW_LIMIT_REACHED | 403 | Workspace is at its plan's workflow limit |
NOT_FOUND | 404 | Generic resource-not-found. Most endpoints emit domain-specific codes instead; see the note below. |
Most endpoints emit domain-specific error_code values for 404 responses rather than the generic NOT_FOUND. Examples include DEVICE_NOT_FOUND and INVITE_NOT_FOUND. Inspect the error_code field on a 404 response to identify exactly which resource was missing. The generic NOT_FOUND code only surfaces from a small number of endpoints (e.g., billing) where the missing resource is not domain-specific.
Not all errors include an error_code. The detail field is always present and provides a human-readable description. Use error_code for programmatic branching when available, and fall back to detail for display.
Common Error Scenarios
Authentication Failure (401)
curl -i https://api.spatialflow.io/api/v1/geofences \
-H "Authorization: Bearer expired-or-invalid-token"
{
"detail": "Unauthorized"
}
Solution: Refresh your JWT token or verify your API key is correct and not revoked.
Rate Limit Exceeded (429)
curl -i https://api.spatialflow.io/api/v1/geofences \
-H "X-API-KEY: YOUR_API_KEY"
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
Content-Type: application/json
{
"detail": "Rate limit exceeded",
"error_code": "RATE_LIMIT_EXCEEDED",
"details": {
"limit": 1000,
"reset_at": "2025-11-10T11:00:00Z",
"retry_after": 3600
}
}
Solution: Wait for the number of seconds specified in the Retry-After header before retrying. See Rate Limiting for retry patterns.
Validation Error (422)
curl -i -X POST https://api.spatialflow.io/api/v1/geofences \
-H "X-API-KEY: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"geometry": {"type": "InvalidType", "coordinates": []}}'
{
"detail": [
{
"type": "missing",
"loc": ["body", "data", "name"],
"msg": "Field required"
},
{
"type": "value_error",
"loc": ["body", "data", "geometry", "type"],
"msg": "Value error, unsupported geometry type"
}
]
}
Solution: Check the loc field in each error to identify which request fields need correction.
Common Errors by Feature
Authentication
An expired or invalid access token, and an unrecognized or revoked API key, both return a plain 401 with no error_code:
{ "detail": "Unauthorized" }
Solution: Refresh your JWT token, or check that your API key is correct and hasn't been revoked.
Geofences
Invalid geometry, for example a self-intersecting polygon:
{ "detail": "This shape crosses itself. Redraw it so the outline never crosses over. (<GEOS validity reason>)" }
The parenthesized part is the raw reason GEOS reported; the sentence before it is SpatialFlow's plain-language translation. Other shapes give a different sentence: too few points, a duplicated ring, or a hole that falls outside the outline.
Too many vertices:
{ "detail": "Geometry has 1023 vertices, exceeds maximum of 1000. Consider simplifying the geometry." }
The vertex limit is a flat 1,000 on every plan; see Geofences.
Workflows
Invalid step type:
{
"detail": "Step 0: Invalid step type: sms_v2",
"details": {
"problems": [
{
"message": "Step 0: Invalid step type: sms_v2",
"phase": "activation",
"error_code": null
}
]
}
}
Saving or activating a workflow checks its trigger and steps after the request body parses, so a definition problem is a plain 400 with a string detail, not a 422 validation array. detail is the first problem; details.problems lists every problem that blocked the request. A step's problems start with its zero-based position (Step 0:). phase is save for a rule about what may be written and activation for something a running workflow needs, and error_code is set where one applies, such as INVALID_GEOFENCE_TARGET for a missing or deleted geofence target. See Workflows for the accepted step types.
Webhooks
Invalid signature, on SpatialFlow's inbound webhook receiver (POST /webhooks/receive/{webhook_id}, verified via the X-Webhook-Signature header):
{
"success": false,
"error": "Unauthorized",
"message": "Invalid webhook signature"
}
Solution: Verify you're using the correct signing secret. This is a different header and contract from the X-SF-Signature on outbound deliveries; see Webhooks for both.
Handling Errors in Code
Python
import requests
response = requests.post(
"https://api.spatialflow.io/api/v1/geofences",
headers={"X-API-KEY": "YOUR_API_KEY"},
json={"name": "Test"},
)
if not response.ok:
error = response.json()
detail = error.get("detail")
# Validation errors return detail as an array
if isinstance(detail, list):
for err in detail:
print(f" {'.'.join(err['loc'])}: {err['msg']}")
else:
error_code = error.get("error_code")
if error_code == "RATE_LIMIT_EXCEEDED":
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Retry in {retry_after}s")
else:
print(f"Error: {detail}")
TypeScript
async function handleApiError(response: Response): Promise<never> {
const body = await response.json();
if (Array.isArray(body.detail)) {
// Validation errors
const fields = body.detail.map(
(e: { loc: string[]; msg: string }) => `${e.loc.join(".")}: ${e.msg}`,
);
throw new Error(`Validation failed:\n${fields.join("\n")}`);
}
if (body.error_code === "RATE_LIMIT_EXCEEDED") {
const retryAfter = response.headers.get("Retry-After") ?? "60";
throw new Error(`Rate limited. Retry in ${retryAfter}s`);
}
throw new Error(body.detail ?? "Unknown error");
}
See Best Practices for retry patterns and production error handling strategies.