Eleven sections, nineteen primitives and the full token table, rendered from the files the pages import.
Tokens
Every value below comes from src/theme.css. Change it there and the whole system follows, in both modes.
--background#f3f7fe · #0a0e15
--foreground#0f141c · #e5ecf7
--card#ffffff · #10161f
--primary#1a47c7 · #7ba5f5
--secondary#e2eaf6 · #1a2331
--muted#ebf0f8 · #131a24
--accent#8a5a0f · #e3b778
--border#ccd4e2 · #2b3646
--ring#1a47c7 · #7ba5f5= --primary
Type scale · ratio 1.250
Anchored at 1.5rem. The classes are the system’s vocabulary; bare Tailwind steps are not used anywhere in the theme.
type-d3 / page title30 → 37.5pxDeliver your first message
type-d1 / every h224pxLease and acknowledge
type-read / guides prose16 → 18pxA lease is a claim on a message.
type-scan / reference prose16pxLimits apply per queue unless the row says otherwise.
type-small / interface14pxPer-queue and per-token ceilings.
type-label / mono slugs12pxvisibility_timeout
Radius and line
rounded-themed6pxrounded-sm4pxrounded-full999px
Spacing rhythm
3 · 4 · 6 · 8 · 10 · 12 · 16
Hairline
--border is decorative, around 1.4:1. Anything it outlines also changes fill and carries a label, so nothing depends on the line.
border + fill + label
uisrc/components/ui/*.tsx
Nineteen primitives. Container, docs-layout, outline-rail, wire-figure, lang-tabs, log-filter and request-builder are shown in context below; the search dialog and the mode toggle are mounted once, in the header, so ⌘K and the theme class each have one owner.
A working consumer needs one queue, one publish, one lease and one ack.
1Create a queueName it, set the visibility timeout, and Halyard provisions it in the caller's region.
2PublishSend a JSON body with an idempotency key. A duplicate key returns the original message.
3LeaseAsk for up to 25 messages. They stay invisible to other consumers while the lease holds.
4AcknowledgeAck deletes the message. Nack returns it early instead of waiting for the timeout.
first-consumer.shbash
exportHALYARD_KEY="hk_live_2f9c…"exportAPI="https://api.halyard.dev/v1"# 1. createcurl"$API/queues" \
-d '{"name":"orders","visibility_timeout":"30s"}'# 2. publishcurl"$API/queues/orders/messages" \
-H "Idempotency-Key: 8f14e45f" \
-d '{"body":{"order_id":"ord_10427"}}'# 3. lease up to 25 messages, wait 20s for one to arrivecurl"$API/queues/orders/leases" -d '{"max":25,"wait":"20s"}'# 4. acknowledge the lease you were handedcurl"$API/queues/orders/leases/lse_01HQ8Z/ack" -X POST
guides-shellsrc/components/guides-shell.tsx
The three-pane frame, the reading edge, the article, and the Wire diagram figure.
Guides menu
Guides
Deliver your first message
This walkthrough takes an empty account to a consumer that acknowledges work, in about ten minutes.
Updated 13 August 20268 minute readAPI v2.4
Before you start
You need an API key with the queues:admin and queues:write scopes. Keys are scoped to one environment, and the sandbox key is free: it holds 50,000 messages and drops them after seven days.
Every example below is a plain curl. Halyard publishes client libraries for TypeScript, Python and Go, and none of this walkthrough needs one.
Publish a message
A queue is created by name. The visibility timeout you set here is the default for every lease taken against it, and a consumer can shorten it per request.
Publishing stores the body verbatim, up to 256 KB. Halyard answers with 202 once the write is durable in three availability zones, so a successful response means the message survives a node loss.
Idempotency keys are remembered for 24 hours. A repeat inside that window returns the original message id instead of enqueueing a second copy, which makes a publish safe to retry after a timeout.
Lease and acknowledge
A lease is a claim. While it holds, the message is invisible to every other consumer, so two workers cannot pick up the same order. Ask for up to 25 messages at once and set wait to hold the connection open until one arrives.
Acknowledging deletes the message and closes the lease. Acks are idempotent, so a retried ack after a network failure returns 204 rather than an error.
curl"$API/queues/orders/leases/lse_01HQ8Z/ack" \
-X POST -H "Authorization: Bearer $HALYARD_KEY"# 204 No Content
Work that outlives the timeout should extend the lease rather than race it. One PATCH per half-timeout is the pattern the SDKs use, and it is documented at extend a lease.
How a message moves
Five states, one loop. Everything Halyard guarantees comes from this path, so it is worth reading once before you tune anything.
trigger
POST /messages
The producer sends a body and an idempotency key. Halyard answers 202 once the write is durable.
step
Durable log
The message is written to three availability zones and ordered inside its ordering key.
step
Lease held
A consumer takes the message for visibility_timeout seconds. No one else can see it in that window.
check
Acked before the timeout?
yes
end · success
Deleted
The message leaves the queue and the lease closes. Acks are idempotent.
no
step
Redelivered
delivery_count increments and the backoff doubles, up to five minutes. The message becomes visible again.
returns to Lease held · the next consumer leases it
end · warning
Dead letters
After max_deliveries attempts the message moves to the dead-letter queue with its full history.
The path of a single Halyard message from publish to acknowledgement, including the redelivery loop and the dead-letter exit.
Handle failure
A consumer that crashes without acking is not an error condition. The lease expires, the message becomes visible again, and delivery_count increments. Nack when you already know the work failed: it returns the message immediately instead of paying for the whole timeout.
curl"$API/queues/orders/leases/lse_01HQ8Z/nack" \
-X POST -H "Authorization: Bearer $HALYARD_KEY" \
-d '{"retry_after": "60s", "reason": "payment_provider_timeout"}'# 204 No Content
Backoff doubles from one second and stops at five minutes. After max_deliveries attempts the message moves to the queue’s dead-letter queue with its full delivery history, where you can inspect it and replay it. See dead letters for both endpoints.
Careful
A dead-letter queue with no alarm on it is a silent data loss policy. Alert on halyard.dead_letters.depth above zero, not on a threshold.
Go to production
Four settings separate a working consumer from a production one. The loop below is the shape the TypeScript SDK ships, written out so nothing is hidden.
consumer.tsts
import { Halyard } from"@halyard/node";
consthalyard = newHalyard({ key: process.env.HALYARD_KEY });
forawait (constleaseofhalyard.queue("orders").leases({
max: 25, // batch: one round trip per 25 orderswait: "20s", // long poll: no busy loop, no idle costregion: "eu-west" // read from the region that wrote
})) {
for (constmessageoflease.messages) {
awaitfulfil(message.body);
}
awaitlease.ack();
}
Ordering keys hold order inside a key and allow parallelism across keys, so one slow customer never blocks another. Retention runs to 14 days on paid plans, and a queue can be paused without losing what is already in it.
Nine endpoint blocks, parameter tables and response samples in the same frame, plus the one request builder. It assembles a curl command and never sends it.
API reference menu
Reference
Halyard HTTP API
Nine endpoints, JSON in and JSON out, over one base URL.
https://api.halyard.dev/v1API v2.4.1Updated 13 August 2026
Authentication
Every request carries a bearer token. Tokens are scoped to one environment and one set of permissions, and Halyard never accepts a key in a query string.
Authorization headerbash
curl"$API/queues" \
-H "Authorization: Bearer hk_live_2f9c…"# 401 without it# {"error":"unauthorized","message":"Provide a bearer token."}
Scopes
queues:adminscope
Create and delete queues. Nothing else needs it.
queues:writescope
Publish, lease, extend, ack, nack and replay. This is what a consumer runs with.
queues:readscope
List queues and read dead letters. Never moves a message.
Create a queue
POST/queuesqueues:admin201
Queues are created by name and are ready to accept messages when the call returns.
Body parameters
namestringrequired
Lowercase letters, digits and hyphens, up to 64 characters. Unique inside the environment.
visibility_timeoutduration · default 30s
How long a lease hides a message. Accepts 1s to 12h; a consumer can shorten it per lease.
max_deliveriesinteger · default 5
Attempts before a message moves to the dead-letter queue. Set 1 to disable retries.
regionstring · default the caller’s region
One of eu-west, us-east, us-west, ap-south. Fixed after creation.
Errors carry a stable code, a human message, and nothing else. Retrying helps on 429 and 503; it does not on the rest.
Error codes
invalid_request400
The body failed validation. The response names the field. Retrying will not help.
unauthorized401
Missing or expired key. Rotate it in the dashboard, then retry.
queue_not_found404
No queue by that name in this environment. Check the environment before the name.
lease_expired409
The lease timed out before the ack arrived. The message is already back in the queue; do not republish it.
rate_limited429
Over the ceiling. Retry-After carries the seconds to wait.
region_unavailable503
A region is failing over. Retry with backoff; writes are accepted again within seconds.
Rate limits
Limits apply per queue unless the row says otherwise. Ask support before you need a raise, not during the incident.
Ceilings
Publishper queue
1,000 messages a second sustained, 5,000 in a burst of up to ten seconds.
Leaseper queue
500 calls a second. Batching with max raises throughput without raising this number.
Acknowledgeper queue
5,000 calls a second, matched to the publish burst so an ack never becomes the bottleneck. Extend and nack share this ceiling.
Managementper token
60 calls a minute across queue creation and deletion.
Headersevery response
X-RateLimit-Remaining and X-RateLimit-Reset are always present, including on a 200. Reset is a Unix epoch in seconds.
sdk-shellsrc/components/sdk-shell.tsx
One consumer, written three times, behind one tab strip: the selection is shared by every block on the page and a hash selects it.
Client libraries menu
SDKs
Official client libraries
Thin wrappers over the same HTTP API: batching, lease extension, backoff, and nothing else.
Updated 13 August 2026API v2.4.13 languages
What a client does
Every client is the same program in three languages: it leases a batch, hands you the bodies, and acks when your handler returns. Four things are handled below the call, and everything else is the HTTP API you already have.
Handled for you
Retries429 and 503
Backoff doubles from one second and stops at five minutes. Every other code surfaces to your handler immediately, because retrying will not help.
Lease extensionhalf-timeout
One extend call per half of the visibility timeout while a handler is still running. It stops on ack, nack or a thrown error.
Batchingmax 25
One lease call per batch, long polling up to 20s, so an idle consumer holds a connection instead of spinning.
Idempotency24 hours
A publish retried after a timeout reuses its Idempotency-Key, so the message is stored once and you get the original id back.
Note
No client is required. The quickstart runs on plain curl, and a language without an official library talks to the same nine endpoints.
The same consumer, three times
One queue, batches of 25, a 20 second long poll and an ack when the handler returns. Pick a language and the page follows it.
@halyard/node 2.4.1Node 20 and Bun 1.1
The lease iterator is an async generator, so the loop ends when you break out of it and the open lease is nacked on the way out rather than left to time out.
The lease is a context manager as well as an iterable. Leaving the block acks it, and an exception inside the block nacks it with the exception type as the reason.
Leases arrive on a channel that closes when the context is cancelled, so a consumer shuts down on the first signal without dropping the batch it is holding.
The spec is generated from the same source as this reference, so it carries all nine endpoints, every parameter and every error code. It ships with each release; generate a client for anything the three libraries do not cover.
Seven releases, newest first, filtered by tag: tagged changes stop at the reading edge, the endpoints a release added cross it as request lines.
Changelog menu
Changelog
Halyard release notes
Every change to the API, with the version you pin against and the date it shipped.
Updated 13 August 2026API v2.4.11 breaking change since v1.9
Note
The base URL carries the HTTP surface and has been /v1 since launch; the release number below is the service running behind it. A change that removes a shape gets an overlap window and appears here as breaking.
7 of 7 releases · 18 changes
v2.4.1
13 August 2026
current
fixed
The rate-limit headers were omitted from 204 responses. Acknowledge and queue delete now carry X-RateLimit-Remaining and X-RateLimit-Reset like every other call.
fixed
Extending an expired lease answered 200 with a stale expiry. It answers 409 lease_expired, and the message is already back in the queue.
changed
The error body dropped a request id field that was never populated. An error carries a stable code and a human message, and nothing else.
v2.4.0
30 July 2026
added
Extend a lease. One call per half-timeout keeps long work in hand instead of racing the visibility timeout, and the extension is added to the current expiry rather than to now.
added
Return work early with nack, and an optional retry_after that overrides the doubling backoff once.
changed
The acknowledge ceiling rose from 1,000 to 5,000 calls a second, matched to the publish burst so an ack never becomes the bottleneck.
Idempotency keys were forgotten after six hours instead of the documented 24, so a publish retried the next morning enqueued a second copy.
fixed
A queue deleted with a lease open left that lease answering 200. In-flight consumers now get 404 on the next ack, which is what the reference always said.
v2.3.0
21 May 2026
added
Dead letters, with the delivery history that got a message there, and replay to move it back with delivery_count reset and the ordering key preserved.
changed
max_deliveries defaults to 5, up from 3. Queues created before this release kept their own value.
fixed
A message that exhausted its deliveries during a region failover could be redelivered once more. It dead-letters on the fifth attempt.
ordering_key on publish. Messages sharing a key are delivered in publish order, and different keys run in parallel, so one slow customer never blocks another.
added
ap-south joins eu-west, us-east and us-west. A queue’s region is fixed after creation.
changed
Long polling holds a lease call open for up to 20 seconds, up from 5. Idle consumers cost nothing extra.
v2.0.0
14 October 2025
breaking
Leases replaced receipts. A lease call returns one lease id for the whole batch, and ack and nack take that lease instead of a per-message receipt. Both shapes were accepted for 90 days; receipts were switched off on this date.
changed
visibility_timeout is a queue setting a consumer overrides per lease, instead of a value carried on every message.
the ack that changedbash
# v1.9: the ack named the message, one call per messagecurl"$API/queues/orders/messages/msg_01HQ8Z3F/ack" -X POST# v2.0: the ack names the lease, and closes the whole batchcurl"$API/queues/orders/leases/lse_01HQ8Z/ack" -X POST
v1.9.0
3 June 2025
deprecated
The v1 line is closed to features. It takes security fixes until 1 June 2027, and the documentation stays online after that.
added
X-RateLimit-Remaining and X-RateLimit-Reset, backported from the v2 line so a consumer can read its headroom on either.
support-desksrc/components/support-desk.tsx
The help page, built from the same fixtures as the reference so its sample report cannot contradict it.
Support menu
Support
How to get help
What support answers, how fast it replies, and what to put in the first message.
Updated 13 August 2026First reply in one business dayAPI v2.4.1
What support answers
Support is the engineer who owns the endpoint. It answers the behaviour a reference cannot: why one queue’s leases expire early, why a message dead-lettered, whether a ceiling can move before a launch.
Read the error first. Every code Halyard returns says whether retrying helps, and the error table settles most of what arrives here before it is sent.
Tip
Search the docs before you write. Press⌘Kon any page to search every heading in the guides and the reference.
What to include
A report support can act on names one queue, one message and one moment. Without those three the first reply is a request for them, and the business day is spent asking.
Include these
queuename and environmentrequired
A name is unique inside one environment, not across them, so say whether this is sandbox or live. The region narrows it further.
message or leaseidrequired
Either prefix works. Support can read the full delivery history behind msg_ and lse_ ids; you cannot.
whenRFC 3339, UTCrequired
To the second. Request logs are kept for 30 days, and a timestamp is what makes them searchable.
got and expectedstatus codesrequired
Paste the error body verbatim. It names the field that failed, which is usually the whole answer.
idempotency keyheader
If the call carried one. It is how the original attempt is found behind a retry.
Six lines is a complete report. This one describes an ack that arrived after its lease had expired, which is the most common thing support sees.
A 503 region_unavailable is a failover, not an outage. Retry with backoff: writes are accepted again within seconds, and a single one does not need a ticket.
Two things are worth waking someone for. A dead-letter queue that is filling, which is why the alarm belongs on halyard.dead_letters.depth above zero rather than on a threshold. And a publish still rate limited after the epoch in X-RateLimit-Reset has passed, which is the one 429 that is not your traffic.
The delivery path carries a 99.9% monthly target per region, and the availability terms say what the target covers and what a miss is worth. Support opens a claim from the same report an incident used, so send the report either way.
Careful
Do not republish a message after a 409. The lease expired, the message is already back in the queue, and republishing puts a second copy of the same order in front of a consumer.
Asking for a raise
Ceilings are per queue unless the limits table says per token. Ask before the launch rather than during it: a raise takes one business day, and an incident does not wait for one.
Send the queue, the sustained rate you expect, the shape of the burst and the region. Publish and acknowledge are separate numbers, so a consumer that keeps up at 1,000 a second still needs its own headroom to drain a backlog.
legal-desksrc/components/legal-desk.tsx
Policy, never attestation: the agreement, the use limits, the retention table and the availability target. Nothing a third party would have to certify.
Legal menu
Legal
Terms and policies
What you agree to when you call the API, and what Halyard commits to in return.
Updated 13 August 2026In force since 1 June 202530 days notice
The agreement
Creating a key is the acceptance. There is no separate signature, and the version in force is the one on this page on the day the call is made.
Your messages stay yours. Halyard stores them to deliver them, reads them only to deliver them, and never trains anything on them. You can delete a queue and its contents at any time, and the deletion is immediate rather than scheduled.
A term on this page changes with 30 days notice, sent to the account address. An API shape is a different promise and keeps its own schedule: a removed shape gets the 90 day overlap the v2.0.0 release note records, and every breaking change appears in the changelog before it ships.
Either side can end the account with 30 days notice. Halyard can end it immediately for the uses below, in which case queued messages stay readable for 14 days so you can drain them.
Acceptable use
A queue is a delivery mechanism, not storage. Publishing messages you never intend to lease, to hold data cheaply, is the one use of the API that is out of bounds by design rather than by volume.
The rest is what you would expect. Do not put someone else’s data through a queue without the right to, do not use Halyard to send unsolicited mail, and do not attempt to read a queue that is not yours. Scopes make the last one hard; the scope table says what each one opens.
Load testing is welcome and needs no permission below the published ceilings. Above them, ask support for a raise first: an unannounced burst reads as an incident and gets handled as one.
Careful
A key found in a public repository is revoked without notice. It is the one action Halyard takes on your account before telling you, because the alternative is your queue draining to somebody else.
What is stored
Five things, and nothing else. A queue’s region is fixed when it is created, and everything in the table below stays in that region for its whole life.
Retention
Message bodiesuntil acked
Held in the queue's own region across three availability zones, and deleted when the lease that carries them is acknowledged.
Dead letters14 days
A message that exhausts max_deliveries keeps its full delivery history for 14 days, then it is deleted whether or not it was replayed.
Request logs30 days
Method, path, status, timestamp and idempotency key. No bodies. This is what support reads when you send a timestamp.
Idempotency keys24 hours
The key and the id it returned, so a retry inside the window answers with the original message instead of a second copy.
API keysuntil revoked
Stored hashed. A key is shown once, at creation, and cannot be read back afterwards by you or by Halyard.
The four regions are eu-west, us-east, us-west and ap-south. Choosing one is how residency is handled: there is no global replication to opt out of, because there is none to begin with.
Availability
The delivery path carries a 99.9% monthly target per region. Everything the target covers, and everything it pays, is in the table.
Monthly targets
Publish and lease99.9% monthly
Measured per region on requests that reach Halyard. A 503 during a failover counts against it; a 429 against your own ceiling does not.
Management calls99.5% monthly
Queue creation and deletion. They are rarer, they are slower, and they are not on the delivery path.
Credit10% of the month
One credit per region per month, applied to the next invoice. It is the whole remedy for a missed target.
Claim window30 days
Send the region, the window and a request id. Support opens the claim from the same report the incident used.
A single 503 region_unavailable is a failover rather than an outage, and it is measured but not usually claimable on its own. When it is an incident says what to watch and when to write.
support-stripsrc/components/support-strip.tsx
Still stuck?
Support answers API questions in one business day, with the engineer who owns the endpoint.