Skip to content

Error Codes

All errors return a JSON body with a consistent envelope:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "start_date and end_date are required.",
    "request_id": "a3f2c1d0-1234-5678-abcd-ef0123456789"
  }
}

error.code is a closed enum in the OpenAPI `ErrorBody` (VALIDATION_ERROR, INVALID_API_KEY, RESOURCE_NOT_FOUND, ROUTE_NOT_FOUND, METHOD_NOT_ALLOWED, RATE_LIMIT_EXCEEDED, AUTH_FAILURE_THROTTLE, SERVICE_UNAVAILABLE, INTERNAL_ERROR). Code generators should map these literals; treat any other value as a forward-compatible extension.

For rate-limit errors, retry_after_seconds is also present:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded.",
    "request_id": "...",
    "retry_after_seconds": 42
  }
}

For validation errors, details may be present with field-level context. Canonical shape: each field key maps to a string array (even when there is only one message):

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "limit cannot exceed 20.",
    "request_id": "...",
    "details": {
      "limit": ["limit cannot exceed 20."]
    }
  }
}

Some cap-exceeded errors add integer metadata keys alongside field errors:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "User has 1001 timesheets in range; maximum is 1000. Narrow start_date/end_date.",
    "request_id": "...",
    "details": {
      "timesheets_in_range": 1001,
      "timesheets_cap": 1000
    }
  }
}

Documented metadata keys: timesheets_in_range, timesheets_cap, log_count, log_cap. Most validation errors omit details entirely — read message first.

See validation-details section below.

request_id is always present and is unique per invocation. Include it when reporting issues.


Error Code Reference

INVALID_API_KEY — 401 Unauthorized

The X-APPLOYE-API-KEY header is missing, malformed, or references a revoked API key.

Common causes:

  • Header was not sent.
  • The API key was rotated or revoked. Keys are cached in-process for up to 120 seconds; wait up to 2 minutes after revocation before testing again.
  • The key belongs to a different organization than the one you are querying.

Response headers: None specific to auth errors.


RATE_LIMIT_EXCEEDED — 429 Too Many Requests

Your organization has exceeded the allowed request rate for this endpoint.

Response headers:

HeaderValue
Retry-AfterSeconds until the rate-limit window resets
RateLimit-LimitCurrent organization/resource limit
RateLimit-Remaining0
RateLimit-ResetUnix timestamp when the window resets

The global fallback is 30 requests per 60 seconds per organization, HTTP method, and canonical resource group. List and detail routes share a bucket. Overrides may apply.

How to handle: Read the Retry-After header and wait before retrying. Do not retry immediately — the counter does not reset until the window expires.


AUTH_FAILURE_THROTTLE — 429 Too Many Requests

Your IP address has exceeded the authentication-failure threshold (10 failures in 60 seconds). All further requests from this IP are blocked until the failure window resets, even if you supply a valid API key.

Response headers:

HeaderValue
Retry-AfterSeconds until the IP block expires

This error differs from RATE_LIMIT_EXCEEDED in that it is IP-based, not org-based, and fires before API key validation.


SERVICE_UNAVAILABLE — 503 Service Unavailable

The API authenticated the key but could not complete Redis quota accounting, so it failed closed. The response includes Retry-After: 5; wait, add jitter, and retry.


RESOURCE_NOT_FOUND — 404 Not Found

The requested resource does not exist within your organization. The route was recognised and the handler ran; this is not a mistyped URL.

Common causes and affected endpoints:

EndpointCause
GET /v1/clients/{client_id}/Client ID does not exist in this org
GET /v1/clients/{client_id}/projects/Client is inactive OR does not exist
GET /v1/projects/{project_id}/ and sub-resourcesProject does not exist in this org
GET /v1/teams/{team_id}/members/Team does not exist in this org
GET /v1/invoices/{invoice_id}/Invoice does not exist in this org
GET /v1/members/{user_id}/payment_settings/User ID does not exist
GET /v1/members/{user_id}/hourly_payment_logs/User ID does not exist
GET /v1/members/{user_id}/one_time_payment_logs/User ID does not exist
GET /v1/screenshots/User not in org, or date beyond 179-day lookback
GET /v1/tasks/{task_id}/members/Task does not exist in this org
GET /v1/time_activity_reports/ (with team_id)Team does not exist
GET /v1/manual_timesheet_reports/ (with team_id)Team does not exist
GET /v1/app_reports/ (with team_id)Team does not exist
GET /v1/url_reports/ (with team_id)Team does not exist

Note: Inactive clients and inactive projects do not return 404 from their primary endpoints (only client ID is checked for existence, not is_active status). See Filters guide.

Note: GET /v1/clockinouts/ with an unknown team_id returns 200 with empty results, not 404. This is intentional — see Filters guide.


ROUTE_NOT_FOUND — 404 Not Found

The URL path is not a valid Partner API endpoint for this Lambda (or API Gateway routed to the wrong function). No business logic ran.

{
  "error": {
    "code": "ROUTE_NOT_FOUND",
    "message": "Route not found.",
    "request_id": "..."
  }
}

Common causes: typo in path, trailing slash omitted or added incorrectly, calling an endpoint that lives on a different Lambda integration.

Do not confuse with `RESOURCE_NOT_FOUND`: if you GET /v1/clients/{client_id}/ with a valid path shape but a UUID that does not exist, you receive RESOURCE_NOT_FOUND with a resource-specific message.


METHOD_NOT_ALLOWED — 405 Method Not Allowed

The Partner API is read-only. Only GET (and OPTIONS for CORS preflight) are supported.

{
  "error": {
    "code": "METHOD_NOT_ALLOWED",
    "message": "Method not allowed.",
    "request_id": "..."
  }
}

Common causes: POST, PUT, PATCH, or DELETE to any /v1/... path.


VALIDATION_ERROR — 400 Bad Request

A request parameter failed validation.

Common messages:

ParameterMessage
start_date / end_date missing"start_date and end_date are required."
Invalid date format"Invalid date format. Use YYYY-MM-DD."
end_date < start_date"end_date must be on or after start_date."
Range > 31 days"Date range cannot exceed 31 days."
Invalid timezone"Invalid timezone." or "Invalid timezone. Use a valid IANA timezone string."
Invalid UUID"Invalid UUID format."
user_id required and missing"user_id is required." (screenshots)
date required and missing"date is required." (screenshots)
limit > max"limit cannot exceed 20." (or 100, depending on endpoint)
limit < 1"limit must be >= 1."
page < 1"page must be >= 1."
user_id + team_id together"Cannot combine user_id and team_id filters." (report endpoints)
Timesheet count exceeds cap"User has N timesheets in range; maximum is 1000. ..." (see details.timesheets_in_range)

#### VALIDATION_ERROR details shape

KindJSON shapeExample keys
Field errors (canonical)"field": ["message"]limit, page, start_date, client_id, status, id
Payload-cap metadata"key": integertimesheets_in_range, timesheets_cap, log_count, log_cap
Absentdetails omittedMost validation errors (message is sufficient)

Handlers normalize scalar strings to one-element arrays at the error boundary. Clients should accept field values as arrays of strings.


INTERNAL_ERROR — 500 Internal Server Error

An unexpected error occurred within the Lambda function.

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred.",
    "request_id": "..."
  }
}

request_id is the most useful field here — include it in any support request. Database errors, unhandled exceptions, and infrastructure outages are all surfaced as INTERNAL_ERROR.


Status Code Summary

HTTP StatusError codeWhen
400VALIDATION_ERRORInvalid or missing query parameters
401INVALID_API_KEYMissing, revoked, or invalid API key
404RESOURCE_NOT_FOUNDValid route; entity doesn't exist in this organization
404ROUTE_NOT_FOUNDUnrecognised path (no handler dispatch)
405METHOD_NOT_ALLOWEDNon-GET HTTP method (read-only API)
429RATE_LIMIT_EXCEEDEDPer-org per-endpoint rate limit hit
429AUTH_FAILURE_THROTTLEToo many auth failures from this IP
500INTERNAL_ERRORUnexpected server-side error
503SERVICE_UNAVAILABLEOrganization quota accounting temporarily unavailable