API reference
The Halyard API
A REST API over HTTPS with predictable, resource-oriented URLs. It accepts and returns JSON, uses standard HTTP verbs and status codes, and authenticates with a bearer key.
Base URL
Every endpoint lives under one host, and the major version is part of the path. All requests must use HTTPS; plain HTTP connections are refused rather than redirected, so a key is never sent in the clear.
Test and live data share the same URL. Which one you reach depends only on the key: hy_test_ keys talk to the sandbox, hy_live_ keys to production.
The official SDKs wrap this API with types, retries and automatic pagination. The examples in this reference show cURL, Node.js and Python side by side.
https://api.halyard.example/v3Client libraries
Authentication
Authenticate by sending your secret key in the Authorization header as a bearer token. Keys are created in the dashboard or with the API keys endpoints, and each one carries scopes that limit what it can do.
Keep secret keys on your servers. Requests without a key, or with a revoked one, fail with 401 Unauthorized; a valid key without the right scope gets 403 Forbidden. Read Authentication for key rotation and scopes.
curl https://api.halyard.example/v3/queues \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d"import { Halyard } from "@halyard/node"; const halyard = new Halyard(process.env.HALYARD_KEY); const queues = await halyard.queues.list();import osfrom halyard import Halyard halyard = Halyard(os.environ["HALYARD_KEY"]) queues = halyard.queues.list()Versioning
Breaking changes only ship in a new major version, which gets a new path such as /v4. Within v3, behavior that could affect existing code ships in dated releases.
Your account is pinned to the release that was current when you made your first request. To try a newer one, send the Halyard-Version header with a release date; to move the whole account, upgrade in the dashboard. Every response echoes the version it was rendered with.
curl https://api.halyard.example/v3/jobs/job_3Rk9Tq7Xw2 \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" \ -H "Halyard-Version: 2026-09-15"Current release
2026-09-15v3.4 · see the changelog
Requests and responses
- Request bodies are JSON with
Content-Type: application/json. Query parameters are used forGETfilters only. - Field names are
snake_case. Timestamps are ISO 8601 strings in UTC, such as2026-10-14T09:12:03Z. Durations are strings like10sor1h. - IDs are strings with a type prefix:
job_,que_,evt_,sch_,whk_,key_. Treat them as opaque, up to 64 characters. - Every response carries a
Request-Idheader. Include it when you contact support and we can find the request in seconds. - Rate limit headers follow the IETF
RateLimit-*draft. See Rate limits.
POST /v3/jobs HTTP/2Host: api.halyard.exampleAuthorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3dContent-Type: application/jsonHalyard-Version: 2026-09-15 HTTP/2 201Content-Type: application/json; charset=utf-8Halyard-Version: 2026-09-15Request-Id: req_6Tn2Vb9Kx4LmRateLimit-Limit: 100RateLimit-Remaining: 99RateLimit-Reset: 1Pagination
List endpoints return a list object and page with cursors, so results stay stable while new objects are created. Pass limit (1 to 100, default 20) to set the page size and starting_after with the last ID you received to get the next page. Use ending_before to go back.
Keep going while has_more is true. The SDKs can do this for you:
// The SDKs page for you: this loop follows has_more and starting_after.for await (const job of halyard.jobs.list({ queue: "emails", status: "failed" })) { await halyard.jobs.retry(job.id);}curl -G https://api.halyard.example/v3/jobs \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" \ -d queue=emails \ -d limit=50 \ -d starting_after=job_3Rk9Tq7Xw2import { Halyard } from "@halyard/node"; const halyard = new Halyard(process.env.HALYARD_KEY); const page = await halyard.jobs.list({ queue: "emails", limit: 50, starting_after: "job_3Rk9Tq7Xw2",});import osfrom halyard import Halyard halyard = Halyard(os.environ["HALYARD_KEY"]) page = halyard.jobs.list( queue="emails", limit=50, starting_after="job_3Rk9Tq7Xw2",){ "object": "list", "url": "/v3/jobs", "has_more": true, "data": [ { "id": "job_9Lc4Wd2Pq7", "object": "job", … }, … ]}Idempotency
Networks fail. To retry a POST safely, send an Idempotency-Key header with a value that is unique to the operation, such as signup-usr_8KfQ2. Halyard stores the first response for 24 hours and returns it again for any request with the same key, instead of doing the work twice.
Reusing a key with different parameters returns 409 idempotency_key_reused. GET, PATCH and DELETE are idempotent by definition and ignore the header.
curl https://api.halyard.example/v3/jobs \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" \ -H "Idempotency-Key: signup-usr_8KfQ2" \ -H "Content-Type: application/json" \ -d '{ "queue": "emails", "task": "send-welcome-email", "payload": { "user_id": "usr_8KfQ2" } }'import { Halyard } from "@halyard/node"; const halyard = new Halyard(process.env.HALYARD_KEY); const job = await halyard.jobs.create({ queue: "emails", task: "send-welcome-email", payload: { user_id: "usr_8KfQ2" },}, { idempotencyKey: "signup-usr_8KfQ2" });import osfrom halyard import Halyard halyard = Halyard(os.environ["HALYARD_KEY"]) job = halyard.jobs.create( queue="emails", task="send-welcome-email", payload={"user_id": "usr_8KfQ2"}, idempotency_key="signup-usr_8KfQ2",)Expanding objects
Some attributes hold the ID of a related object, such as a job's schedule. Pass expand[] with the attribute name to get the full object inline and save a round trip.
Expandable attributes are marked in each object's reference. You can expand up to four levels deep with dot notation, like data.schedule on list endpoints.
curl -G https://api.halyard.example/v3/jobs/job_8Vx1Lk5Pq3 \ -H "Authorization: Bearer hy_test_51Hq8Zx2rTmV9cKpL3d" \ -d "expand[]=schedule"{ "id": "job_8Vx1Lk5Pq3", "object": "job", "task": "build-daily-report", "schedule": { "id": "sch_2Mf6Xb8Qn4", "object": "schedule", "name": "nightly-report", "cron": "0 2 * * *", "timezone": "Europe/Amsterdam" }}Errors
Halyard uses conventional HTTP status codes: 2xx for success, 4xx when something about the request is wrong, 5xx when something failed on our side. Error bodies always have the same shape, with a stable code you can branch on. The Errors guide lists every code.
- 200 / 201OK / Created
- OK / Created. The request worked.
- 400Bad Request
- Bad Request. A parameter is missing or invalid. Check
param. - 401Unauthorized
- Unauthorized. No API key, or the key is invalid or revoked.
- 403Forbidden
- Forbidden. The key is valid but lacks the scope for this call.
- 404Not Found
- Not Found. The object doesn't exist, or belongs to the other mode.
- 409Conflict
- Conflict. The object's state doesn't allow this, or an idempotency key was reused.
- 429Too Many Requests
- Too Many Requests. Slow down; retry after
RateLimit-Resetseconds. - 5xxServer error
- Server error. Something failed on our side. Safe to retry with the same idempotency key.
{ "error": { "type": "invalid_request_error", "code": "parameter_missing", "message": "Missing required parameter: task.", "param": "task", "doc_url": "https://docs.halyard.example/docs/errors#parameter_missing", "request_id": "req_6Tn2Vb9Kx4Lm" }}Resources
Six resources cover the whole API. Each page documents the object and every endpoint, with examples you can copy or try in the explorer.
A unit of background work: a task name, a payload and a lifecycle you can follow.
- POST
/v3/jobs: Create a job - GET
/v3/jobs/{id}: Retrieve a job - GET
/v3/jobs: List jobs - POST
/v3/jobs/{id}/cancel: Cancel a job - POST
/v3/jobs/{id}/retry: Retry a job
Named lanes that set concurrency, priority, rate limits and retries for a kind of work.
- GET
/v3/queues: List queues - GET
/v3/queues/{id}: Retrieve a queue - PATCH
/v3/queues/{id}: Update a queue - POST
/v3/queues/{id}/pause: Pause a queue - POST
/v3/queues/{id}/resume: Resume a queue
Facts your services publish once and Halyard fans out to queues and webhook endpoints.
Cron expressions with time zones that create a job every time they fire.
- POST
/v3/schedules: Create a schedule - PATCH
/v3/schedules/{id}: Update a schedule - POST
/v3/schedules/{id}/pause: Pause a schedule - DELETE
/v3/schedules/{id}: Delete a schedule - GET
/v3/schedules: List schedules
HTTPS URLs that receive signed events from Halyard as they happen.
- POST
/v3/webhook_endpoints: Create a webhook endpoint - GET
/v3/webhook_endpoints: List webhook endpoints - DELETE
/v3/webhook_endpoints/{id}: Delete a webhook endpoint - POST
/v3/webhook_endpoints/{id}/rotate_secret: Rotate a webhook secret
Secret keys with scopes that authenticate requests from your servers and workers.
- POST
/v3/api_keys: Create an API key - GET
/v3/api_keys: List API keys - POST
/v3/api_keys/{id}/revoke: Revoke an API key