Platform
Errors
Halyard uses conventional HTTP status codes and returns a JSON error object with a stable type, a specific code and a request ID, so your code can decide what to do and support can find what happened.
On this page
Overview
2xx means success. 4xx means the request can't succeed as sent: fix it before retrying. 5xx means something went wrong on our side and the request is safe to retry. Branch on the error's type, which is stable, not on the human-readable message, which can change.
curl -i https://api.halyard.example/v3/jobs \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" \ -H "Content-Type: application/json" \ -d '{ "task": "send-welcome-email", "payload": {} }' HTTP/2 400Halyard-Request-Id: req_4Tn8Wc2Ke7The error object
Every error response has the same shape, wrapped in a top-level error key.
{ "error": { "type": "invalid_request", "code": "parameter_missing", "message": "Missing required parameter: queue.", "param": "queue", "request_id": "req_4Tn8Wc2Ke7", "doc_url": "https://docs.halyard.example/docs/errors#codes" }}Attributes
typestringrequiredcodestringrequiredparameter_missing or queue_paused. New codes can appear in minor releases, so always have a fallback.messagestringrequiredparamstringretry.max_attempts.request_idstringrequiredHalyard-Request-Id header.doc_urlstringerrorsarrayvalidation_failed: one entry per failing field, each with a JSON Pointer path and a message.HTTP status codes
| Status | Name | Meaning |
|---|---|---|
| 200 | OK | The request succeeded. |
| 201 | Created | A job, event or other resource was created. |
| 202 | Accepted | Accepted for asynchronous processing, like a batch create. |
| 204 | No Content | Succeeded with nothing to return, like a delete. |
| 400 | Bad Request | The request is malformed or a parameter is invalid. |
| 401 | Unauthorized | The API key is missing or not valid. |
| 403 | Forbidden | The key isn't allowed to do this. |
| 404 | Not Found | The resource doesn't exist. |
| 409 | Conflict | State conflict or idempotency key reuse. |
| 413 | Payload Too Large | The body is over the size limit. |
| 422 | Unprocessable | Valid JSON that fails a queue's schema. |
| 429 | Too Many Requests | Rate limited; see Retry-After. |
| 5xx | Server errors | Our fault. Safe to retry with the same idempotency key. |
Error types
There are a small, fixed number of types. Handle the ones that matter to you and treat the rest by status code family.
| Type | Status | Meaning | What to do | Retry |
|---|---|---|---|---|
invalid_request | 400 | A parameter is missing, has the wrong type or an invalid value. | Fix the request. param names the field; don't retry unchanged. | No |
authentication_failed | 401 | No API key, a revoked key, or a test key used against live data. | Check the Authorization header and the key's environment. | No |
permission_denied | 403 | The key is valid but its scopes don't allow this call, or the IP isn't allowlisted. | Use a key with the needed scope or add the IP to the allowlist. | No |
not_found | 404 | The ID doesn't exist, was deleted, or belongs to another project. | Check the ID and that the key belongs to the right project. | No |
conflict | 409 | The resource is in a state that doesn't allow this, like canceling a finished job. | Fetch the resource, check its status, then decide. | No |
idempotency_mismatch | 409 | An idempotency key was reused with a different request body. | Use a new key for a new request, or resend the original body. | No |
payload_too_large | 413 | The job payload or event data is over 256 KB. | Store large data elsewhere and pass a reference, like a URL or an ID. | No |
validation_failed | 422 | The payload doesn't match the JSON Schema attached to the queue. | Read errors[] for each failing path and fix the payload. | No |
rate_limited | 429 | Too many requests in a short window. | Wait for Retry-After seconds, then retry with backoff. | Yes |
internal_error | 500 | Something went wrong on our side. We're paged automatically. | Retry with backoff and the same idempotency key. | Yes |
service_unavailable | 503 | A region is shedding load or in maintenance. | Retry with backoff. Check the status page if it persists. | Yes |
Validation errors
If a queue has a JSON Schema attached, payloads that don't match are rejected with validation_failed before the job is stored. The errors array lists every problem at once, so you can fix them in one pass.
{ "error": { "type": "validation_failed", "code": "payload_schema_mismatch", "message": "Payload does not match the schema for queue \"emails\".", "request_id": "req_9Qa2Hd5Vn3", "errors": [ { "path": "/user_id", "message": "is required" }, { "path": "/locale", "message": "must be one of: en, de" } ] }}SDK exceptions
The official SDKs turn each error type into its own exception class, or a typed error value in Go. All of them expose type, code, param and the request ID.
| Type | Node.js | Python | Go | Ruby |
|---|---|---|---|---|
| invalid_request | InvalidRequestError | InvalidRequestError | ErrInvalidRequest | Halyard::InvalidRequestError |
| authentication_failed | AuthenticationError | AuthenticationError | ErrAuthentication | Halyard::AuthenticationError |
| permission_denied | PermissionDeniedError | PermissionDeniedError | ErrPermissionDenied | Halyard::PermissionDeniedError |
| not_found | NotFoundError | NotFoundError | ErrNotFound | Halyard::NotFoundError |
| conflict, idempotency_mismatch | ConflictError | ConflictError | ErrConflict | Halyard::ConflictError |
| rate_limited | RateLimitError | RateLimitError | ErrRateLimited | Halyard::RateLimitError |
| network failure | APIConnectionError | APIConnectionError | *url.Error | Halyard::ConnectionError |
import { HalyardError, NotFoundError } from "@halyard/node"; try { await halyard.jobs.cancel("job_3Rk9Tq7Xw2");} catch (err) { if (err instanceof NotFoundError) return; // already gone: fine if (err instanceof HalyardError && err.type === "conflict") { console.info("Job already finished"); // nothing to cancel return; } throw err;}import halyard as hy try: halyard.jobs.cancel("job_3Rk9Tq7Xw2")except hy.NotFoundError: pass # already gone: fineexcept hy.ConflictError: print("Job already finished") # nothing to cancel_, err := jobs.Client.Jobs.Cancel(ctx, "job_3Rk9Tq7Xw2") var apiErr *halyard.Errorif errors.As(err, &apiErr) { switch apiErr.Type { case halyard.ErrNotFound: return nil // already gone: fine case halyard.ErrConflict: log.Println("job already finished") // nothing to cancel return nil }}return errbegin Halyard.jobs.cancel("job_3Rk9Tq7Xw2")rescue Halyard::NotFoundError # already gone: finerescue Halyard::ConflictError Rails.logger.info("Job already finished") # nothing to cancelendRequest IDs
Every response, successful or not, has a Halyard-Request-Id header like req_4Tn8Wc2Ke7. Log it next to your own errors. When you contact support, the request ID lets us find the exact request in seconds, including its timing and which region served it.
Request IDs are also listed in the dashboard under Logs, where you can search by ID for up to 30 days, and every job records the ID of the request that created it.
Retrying safely
Retry 429 and 5xx responses, and connection errors, with exponential backoff. Don't retry other 4xx responses unchanged: they will fail the same way.