Hanzo AI

MQ

Queue and stream admin for your org: create them, watch them drain, ack what you pulled.

Also for this capability: API · CLI · MCP · SDKs

Queue and stream admin for your org: create them, watch them drain, ack what you pulled.

Base URLhttps://api.hanzo.ai
Operations15
AuthAuthorization: Bearer $HANZO_API_KEY

Specification

HIP-1061 · MQ — Queues and Streams — Draft · read the specification →

/v1/mq is the queue side of the platform bus: an org creates durable streams, inspects and purges them, reads stored messages by sequence, manages pull consumers and pulls the next batch. It rides the same embedded broker the tenant publish door rides (HIP-1060) and shares none of its operations.

This HIP specifies the split, the tenancy encoding, and the delivery semantics a client has to know because they are not the broker's defaults. The implementation is hanzoai/cloud apps/mq.

Motivation

One broker can front two products or one confused one. When the authored intent for this surface was written it carried publish, subscribe, request/reply, keyed storage and object storage alongside the queue operations — five capabilities on one address, three of which the platform already serves elsewhere. Serving all of it would have meant the same broker call behind two doors with two shapes, and a client choosing between them by accident.

The split is therefore the first normative statement here, not a footnote.

Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

The split

The subject side — publishing, request/reply, keyed state — is the pubsub capability's surface (HIP-1060). The queue side — stream lifecycle, stored-message access, consumer lifecycle, pull delivery — is this one. No operation appears on both, and an operation added to either that already exists on the other is a defect.

Two further families stay off this surface for the same reason: keyed storage is already a product of its own, and object storage is /v1/s3. The broker's own mechanisms for both are an implementation detail of this deployment, not a second door.

The served set and the refused set are both closed lists in apps/mq/typed_wire_test.go, checked against the live router in both directions: an operation leaving the served list fails, and an operation that starts being served while still named as refused fails. A refusal row MUST be deleted the day its fact stops being true.

Tenancy

The broker also carries platform-internal streams, so isolation is enforced by this surface and not by the broker:

  • Stream names are namespaced per org on the wire and presented bare to the caller (apps/mq/mq.go:194).
  • Stream subjects are confined to the org's subject root (apps/mq/mq.go:206); callers state subjects relative to it, the root is added inbound and stripped outbound.

The org encoding MUST be injective into the broker's name alphabet, so two distinct orgs can never share a namespace. TestNamespaceEncodingIsInjective holds that property.

Consequences: two orgs cannot bind overlapping subjects, no tenant stream can capture a platform subject, and a caller's handle can only ever resolve to a stream inside its own namespace. The org comes from the validated principal (HIP-0026) and MUST NOT be nameable in a request.

Delivery semantics that are not the broker's defaults

  • A pull acknowledges on delivery. This surface has no acknowledge operation, so an explicit consumer whose messages were never acknowledged would redeliver forever. The operation says so in its own prose and TestConsumerPullAcksOnDelivery pins it. A client that needs acknowledge-after-processing MUST use the broker port, where the acknowledgement is expressible.
  • An empty waiting pull answers 408; a no-wait pull answers 200 with an empty page. The distinction is the caller's, chosen per request.
  • A pull's wait is bounded by a server cap, so a caller cannot park requests on this surface indefinitely.
  • Purge reports the difference between the stream's message count before and after, because the client library discards the broker's own purged count.

Availability is honest

Mount never requires a live broker: the connection retries, so the surface mounts and can describe itself with nothing running. Until the plane is reachable, every operation answers 503 and the health operation reports degraded. It MUST NOT report healthy on a connection it does not have.

What it owns, charges and emits

The capability owns no store: the durable log it administers is the broker's one file store, owned by the pubsub capability that runs the node (HIP-1060). This surface reaches it as a client over the one bus knob (apps/mq/mq.go:93) and forks nothing of its own; the client library is nats.go (Apache 2.0).

The addresses are the operations at /v1/mq (plugin/mq/openapi.json): health and info; stream list, create, get, update, delete and purge; stored-message read and delete by sequence; consumer list, create, get and delete; and the pull. All typed, none declared.

It is free, in those words: the plugin declares Price: cloud.Free (plugin/mq/main.go:23), and no meter runs behind any route.

It publishes no events on the platform bus, so a customer's webhooks (HIP-1310) receive nothing from it. It emits nothing to observability beyond the request span every route gets; the degraded state is answered on the health operation, not exported.

The stage is ga — the manifest row declares none, and absent is ga (HIP-0139 §8).

Rationale

The alternative to a closed refusal ledger is a comment saying "not yet". Measured on this corpus, that is how a servable operation stays unserved forever: nobody can tell whether the sentence is still true, so nobody acts on it. Making both directions a test means the refusal expires by itself the day the wire moves.

Security Considerations

The name and subject encoding is the whole tenant boundary. It is injective by construction rather than by convention, because a collision is a cross-tenant read of a durable log. The org is never an input field, so there is no request shape that asks the surface to trust the caller about who it is.

Every functional test opens a real embedded broker node on a random port with a temporary store and drives the HTTP surface over it — stream lifecycle, pull and acknowledge, a second org seeing nothing, platform streams invisible, and the degraded answer when the plane is down.

Four surfaces

SurfaceReaches this capability asCoverage
RESTmq at its own prefix15 operations
CLIhanzo mq …15 of 15
SDKMqApi in every published client15 methods
MCPtool mq on https://api.hanzo.ai/v1/mcp15 operations, 3 under the document's own id — ask describe for the rest

Quickstart

export HANZO_API_KEY=sk-...   # console.hanzo.ai → API keys

Then the first call — a read that needs nothing but the key. GET /v1/mq/info, operation get_mq_info:

hanzo mq info

Answers 200 with object — ok.

Endpoints

EndpointWhat it does
GET /v1/mq/healthReports whether the message plane behind this surface answers.
GET /v1/mq/infoReturns the broker's identity and the org's stream count.
DELETE /v1/mq/stream/{name}/message/{seq}Erases one message by sequence; the sequence gap remains.
GET /v1/mq/stream/{name}/messageReads stored messages without a consumer: by sequence, by newest on a subject, or walking a subject forward from a sequence.
POST /v1/mq/stream/{name}/purgeRemoves messages from a stream, leaving its consumers in place.
GET /v1/mq/stream/{name}Returns one stream's configuration and live state.
PUT /v1/mq/stream/{name}Reconfigures an existing stream; the path names the stream, and the immutable fields (storage, retention) must restate what they are.
DELETE /v1/mq/stream/{name}Removes a stream with all its messages and consumers.
POST /v1/mq/stream/{stream}/consumer/{name}/nextPulls the consumer's next batch.
GET /v1/mq/stream/{stream}/consumer/{name}Returns one consumer's configuration and delivery state.
DELETE /v1/mq/stream/{stream}/consumer/{name}Removes a consumer and its delivery state; unacknowledged messages stay in the stream.
GET /v1/mq/stream/{stream}/consumerReturns a stream's consumers, name-ordered, with delivery state.
POST /v1/mq/stream/{stream}/consumerCreates a durable pull consumer on a stream and returns it.
GET /v1/mq/streamReturns the org's streams, name-ordered, with their live state.
POST /v1/mq/streamCreates a durable stream in the org's namespace and returns it.

All Hanzo APIs · Interactive reference

How is this guide?