Error Handling¶
All API errors follow a consistent JSON format. Every error response contains a unique error ID, a machine-readable error code, a human-readable message, and a details array for field-specific validation errors.
Error Structure¶
{
"error_id": "err_f47ac10b-58cc-4372-a567-0e02b2c3d479",
"error_code": "ENTITY_NOT_FOUND",
"message": "PlantInstance with key 'pi_xyz' not found.",
"details": [
{
"field": "key",
"reason": "No PlantInstance with key 'pi_xyz'.",
"code": "ENTITY_NOT_FOUND"
}
],
"timestamp": "2026-03-17T10:30:00.000000+00:00",
"path": "/api/v1/plant-instances/pi_xyz",
"method": "GET"
}
| Field | Type | Description |
|---|---|---|
error_id | String | Unique UUID of the error event — use for support requests |
error_code | String | Machine-readable code (full list below) |
message | String | Short error description in English |
details | Array | Field-specific error details (may be empty) |
details[].field | String | Field name or path to the invalid value |
details[].reason | String | Explanation of the specific problem |
details[].code | String | Machine-readable detail code |
timestamp | String | Time of the error (ISO 8601, UTC) |
path | String | URL path of the failed request |
method | String | HTTP method of the failed request |
Use error_id for support
The error_id is logged on the server. Always include this ID when debugging or raising support requests — it allows the error to be traced precisely on the server side.
HTTP Status Codes¶
| Code | Meaning | When |
|---|---|---|
200 OK | Success | Read requests (GET) |
201 Created | Resource created | POST with new resource |
204 No Content | Success without body | DELETE |
400 Bad Request | Malformed request | Syntax error in request body |
401 Unauthorized | Not authenticated | Missing or invalid token |
403 Forbidden | No permission | Authenticated but missing role |
404 Not Found | Resource not found | Unknown ID or slug |
409 Conflict | Conflict | Duplicate entry or invalid state transition |
422 Unprocessable Entity | Validation error | Business rules violated (e.g. pre-harvest interval) |
423 Locked | Account locked | Too many failed login attempts |
429 Too Many Requests | Rate limit exceeded | Too many requests per minute |
502 Bad Gateway | External source unreachable | GBIF/Perenual timeout |
500 Internal Server Error | Internal error | Unexpected server error |
Error Codes¶
General Errors¶
| Error Code | HTTP | Description |
|---|---|---|
ENTITY_NOT_FOUND | 404 | The requested resource does not exist |
DUPLICATE_ENTRY | 409 | An entry with this value already exists |
VALIDATION_ERROR | 422 | Pydantic validation error (types, required fields) |
INTERNAL_ERROR | 500 | Unexpected internal error |
Authentication and Authorization Errors¶
| Error Code | HTTP | Description |
|---|---|---|
UNAUTHORIZED | 401 | No valid token present |
INVALID_TOKEN | 401 | Token expired or tampered |
FORBIDDEN | 403 | Authenticated but without sufficient role |
EMAIL_NOT_VERIFIED | 403 | Email address not yet confirmed |
ACCOUNT_LOCKED | 423 | Account locked after too many failed attempts |
Phase and State Errors¶
| Error Code | HTTP | Description |
|---|---|---|
PHASE_TRANSITION_INVALID | 422 | Invalid phase status transition (e.g. backward transition) |
INVALID_STATUS_TRANSITION | 422 | Invalid status change of an object |
INVALID_RUN_STATE | 409 | Operation not allowed in the current state |
Plant Protection and Harvest Errors¶
| Error Code | HTTP | Description |
|---|---|---|
KARENZ_VIOLATION | 422 | Harvest not possible — pre-harvest interval (PHI) not yet elapsed |
RESISTANCE_WARNING | 422 | Resistance risk — active ingredient applied too many times consecutively |
HST_VIOLATION | 422 | Training measure (HST) not permitted in this growth phase |
Substrate and Compatibility Errors¶
| Error Code | HTTP | Description |
|---|---|---|
SUBSTRATE_EXHAUSTED | 422 | Substrate batch has exceeded maximum reuse cycles |
ROTATION_VIOLATION | 422 | Plant family was grown in this slot too recently |
INCOMPATIBLE_COMPANION | 422 | Two plant species are incompatible as companion plants |
External Services¶
| Error Code | HTTP | Description |
|---|---|---|
EXTERNAL_SOURCE_ERROR | 502 | External data service (GBIF, Perenual) not reachable |
ADAPTER_NOT_FOUND | 404 | No adapter registered for the specified source |
RATE_LIMIT_EXCEEDED | 429 | Rate limit of the server or an external service exceeded |
Validation Errors (422)¶
FastAPI and Pydantic automatically produce structured validation errors for input mistakes. The details list contains one entry per invalid field:
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "not-a-valid-email",
"password": "short",
"display_name": ""
}
Response (422):
{
"error_id": "err_b5e3c8a1-...",
"error_code": "VALIDATION_ERROR",
"message": "The input data is invalid.",
"details": [
{
"field": "body.email",
"reason": "value is not a valid email address",
"code": "value_error"
},
{
"field": "body.password",
"reason": "String should have at least 10 characters",
"code": "string_too_short"
},
{
"field": "body.display_name",
"reason": "String should have at least 1 character",
"code": "string_too_short"
}
],
"timestamp": "2026-03-17T10:30:00.000000+00:00",
"path": "/api/v1/auth/register",
"method": "POST"
}
Pre-Harvest Interval Violation (KARENZ_VIOLATION)¶
The pre-harvest interval (PHI) gate prevents creating a harvest record while the waiting period of an applied pesticide has not yet elapsed.
Response (422) when PHI is active:
{
"error_id": "err_c7d2e5f3-...",
"error_code": "KARENZ_VIOLATION",
"message": "Cannot harvest: safety interval for 'Pyrethrin' has 5 days remaining.",
"details": [
{
"field": "active_ingredient",
"reason": "Safety interval not elapsed: 5 days remaining.",
"code": "KARENZ_VIOLATION"
}
],
"timestamp": "2026-03-17T10:30:00.000000+00:00",
"path": "/api/v1/t/my-garden/harvest/batches",
"method": "POST"
}
Pre-harvest intervals are legally relevant
The pre-harvest interval (Karenzzeit) between the last pesticide application and harvest is legally mandated by the PflSchG (German Plant Protection Act) and CanG (German Cannabis Act). Clients must never suppress or ignore this error.
Conflicts (409)¶
HTTP 409 is used when an operation cannot be performed due to the current state — for example, adding a plant to an already completed run:
{
"error_id": "err_a1b2c3d4-...",
"error_code": "INVALID_RUN_STATE",
"message": "Operation 'add_plant' not allowed in status 'completed'.",
"details": [],
"timestamp": "2026-03-17T10:30:00.000000+00:00",
"path": "/api/v1/t/my-garden/planting-runs/run_123/plants",
"method": "POST"
}
Rate Limiting (429)¶
When the rate limit is exceeded, the API responds with HTTP 429. The Retry-After header indicates how many seconds to wait before retrying:
Internal Errors (500)¶
For unexpected internal errors, the API never exposes server-side details. The error_id is the only instrument for error correlation:
{
"error_id": "err_9f8e7d6c-...",
"error_code": "INTERNAL_ERROR",
"message": "An internal error occurred. Please contact support with the reference ID.",
"details": [],
"timestamp": "2026-03-17T10:30:00.000000+00:00",
"path": "/api/v1/t/my-garden/plant-instances/",
"method": "GET"
}
Recommendations for API Clients¶
- Evaluate the error code, not just the HTTP status code. Multiple semantically distinct errors can share the same status code. For example,
KARENZ_VIOLATION,HST_VIOLATION, andROTATION_VIOLATIONare all HTTP 422. - Log the
error_id. Always include theerror_idin your own logs and in support requests. - Iterate the
detailsarray for field-specific error messages in form validation. - Respond to
401with a token refresh. When the access token expires (15 min), the API returns401 UNAUTHORIZED. The client should automatically attempt a refresh. - Do not retry
409 INVALID_RUN_STATE. This error indicates a business-level state conflict. The operation is only possible after a state change.
See Also¶
- API Overview — URL structure and endpoint groups
- Authentication — Auth errors in detail
- IPM / Pest Management — Pre-harvest interval concept explained