Skip to main content
POST
Subscribe an endpoint to a topic

Authorizations

Authorization
string
header
required

BASALTIC-HMAC-SHA256 request signing for service-account (access-key) auth. An AWS-SigV4-style scheme: an HMAC-SHA256 over a canonical request, keyed by a signing key derived from your secret access key. The basaltic CLI does this for you; the steps below are for a non-CLI client.

Headers you send

  • Authorization (this scheme) — format below.
  • X-Date: request time in UTC, compact ISO-8601 basic: YYYYMMDDTHHMMSSZ (e.g. 20240115T103000Z). Must be within 5 minutes of server time.
  • X-Nonce: random per-request value (e.g. 16+ bytes of hex). Required — it is what makes each signature unique so the server can reject replays. X-Date alone is not enough (one-second resolution; UNSIGNED-PAYLOAD never enters the canonical request).
  • X-Content-Sha256 (optional): lowercase-hex SHA-256 of the request body, to bind the exact bytes to the signature — the server re-hashes and rejects a mismatch. Omit it, and put the literal UNSIGNED-PAYLOAD in the body-hash slot, for an empty body or a streaming upload that shouldn't be buffered.

Authorization header format

  • <date>: YYYYMMDD (the day part of X-Date).
  • <region>: target region code (e.g. sa-saopaulo-1), or global for the region-agnostic (IAM) endpoints.
  • <service>: basaltic.
  • SignedHeaders: the covered header names, lowercased and sorted, joined by ; — at minimum host;x-date;x-nonce (all three required).
  • Signature: lowercase hex from step 4.
  • Note the , (comma + space) between the three Authorization parameters.

Step 1 — canonical request

Join these six lines with \n (LF):

  • canonical-path: the URL-escaped request path (e.g. /v1/compute/instances).
  • canonical-query: each query key and value URL-encoded, sorted by key then value, joined by &; empty string when there is no query.
  • canonical-headers: for each signed header, name:value (name lowercased, value trimmed), sorted by name, each followed by a trailing \n.
  • signed-headers: the same names, lowercased, sorted, joined by ;.
  • body-hash: lowercase-hex SHA-256 of the body, or the literal UNSIGNED-PAYLOAD.

Step 2 — string to sign

Join with \n:

The second line is the credential date at midnight — NOT the X-Date value — and the third is the credential scope without the access-key id.

Step 3 — derive the signing key (HMAC chain over your secret)

Step 4 — signature

Example (POST with a JSON body)

Request validity

  • Valid for 5 minutes from the X-Date timestamp.
  • The signature must cover at least host, x-date, and x-nonce.
  • Mutating requests (POST/PUT/PATCH/DELETE) that present a signed nonce are anti-replay guarded for the drift window; omitting X-Nonce is rejected.

Rate limits

There is no global request budget: a limit applies only where an operation documents a 429 response, and each such endpoint counts its own fixed window. Everything else is bounded by quota, not by request rate.

Per-endpoint request limits

A few endpoints are limited in front of the handler, counted per caller — the authenticated principal when the request carries credentials, the client IP otherwise. On this API surface that is the public region catalogue, GET /v1/regions: it guards no secret, and the budget is there because the catalogue is unauthenticated and database-backed.

An operation that is limited says so by documenting a 429. These responses — 429 and 2xx — carry the budget, so a client can pace itself instead of discovering the ceiling by hitting it:

  • X-RateLimit-Limit — requests allowed per window on this endpoint. Authoritative: read it rather than hard-coding a number.
  • X-RateLimit-Remaining — requests left in the window (0 on a 429).
  • X-RateLimit-Reset — seconds until the window resets. A duration, not a timestamp, so it needs no clock agreement.
  • Retry-After — on a 429 only; seconds to wait. Never zero.

Over the limit the response is 429 with error code RATE_LIMITED. Honor Retry-After — retrying sooner is refused and extends the window, since the refused attempt is itself counted.

Service-level limits

Some resources meter the work itself rather than the request, and are documented on the operation. They return 429 with their own error code and no rate-limit headers, because the budget is not a per-endpoint window: sending email is capped per (account, identity) per second (EMAIL_RATE_LIMIT), and charge attempts are capped per invoice (INVOICE_PAYMENT_ATTEMPTS_EXHAUSTED).

Headers

Idempotency-Key
string

Optional client-generated key that makes a create replay-safe. Retrying a request with the same key returns the original outcome verbatim instead of creating a duplicate resource. Reusing a key with a different request body is rejected (422); a request whose key is still being processed returns 409. Records are honored for 24 hours. Use a UUID or similarly unique token.

Maximum string length: 255

Body

application/json
topic_id
string<uuid>
required
Example:

"a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"

protocol
enum<string>
required
Available options:
email,
https,
http,
queue
Example:

"https"

endpoint
string
required

An email address; an http/https URL whose scheme matches the protocol and resolves to a publicly-routable address; or, for queue, the CRN of a queue in the same account.

A queue subscription skips the confirmation handshake and is created already confirmed. The handshake exists to prove that whoever controls the endpoint consents to receive, which is necessary for an address or a URL you may not own. A queue is your own resource, so consent is established instead by requiring queue:SendMessage on it at subscribe time — an authorization decision rather than a demonstration of reachability.

Maximum string length: 2048
Example:

"https://hooks.example.com/basaltic"

raw_message_delivery
boolean
default:false

http/https only.

Example:

false

filter_policy
object

Restricts which messages reach a subscription, matched against message attributes. Every named attribute must match at least one of its alternatives (AND across names, OR within a name). An omitted or empty policy matches everything.

An alternative is a literal string, or one of: {"prefix": "..."}, {"suffix": "..."}, {"anything-but": [...]}, {"exists": true|false}, {"numeric": [">=", 0, "<", 100]}.

Only exists can match an attribute that is absent; every other form requires a value to compare.

Example:
delivery_policy
object

Retry behaviour. Omitted fields fall back to the topic's policy, then to platform defaults (6 attempts, 20s to 20m exponential backoff with jitter, 15s per-request timeout). A subscription's policy is layered on its topic's field by field, so overriding only max_attempts keeps the topic's backoff curve.

redrive_policy
object

Names a queue in the subscriber's own account that receives each terminally failed delivery — the full original notification plus a failure block (delivery id, endpoint, attempts, last error, status code) — as a delivery_failure message. Without one, terminal failures are visible only via the delivery records and the delivery-statistics endpoints.

The caller must hold queue:SendMessage on the target when the policy is set: writes happen later with no principal present, so consent is established up front, exactly as for a protocol=queue endpoint. For a queue subscription the target must differ from the endpoint queue.

tags
object
Example:

Response

Subscription created (or the existing one returned).

subscription
object
required
signing_secret
string

The HMAC key for verifying webhook deliveries. Returned only on the first create of an http/https subscription — absent for email, and absent when an existing subscription is returned.

Example:

"kJ8vQ2xR5tY7uI0oP3aS6dF9gH1jK4lZ7xC0vB3nM6Q"