Queue Types
Single consumer delivery with visibility key enforcement and primary key deduplication.
Independent message copies delivered to multiple consumers, each tracking their own position.
State snapshot store with primary key lookups, delta queries, and optional sort key ordering.
Numeric field aggregation into clock-aligned time buckets with OHLC statistics.
Enrichment queue for single consumers with automatic key-value lookup on publish.
Enrichment queue for multiple consumers with automatic key-value lookup on publish.
Whole-file document repository — upload XML/CSV/JSON files kept intact, list, and download in any format.
Plain-text document repository — upload and retrieve text/plain files stored and served verbatim, with no format conversion.
One-to-One
one-to-oneThe One-to-One queue delivers each message to exactly one consumer. A visibility key ensures that only one message per key group can be in-flight at any time, preventing duplicate processing. Messages are deduplicated by primary key hash on publish. Consumers must acknowledge (or nack) each message before receiving the next one for that visibility key. If a lease expires, the message is automatically requeued by the reaper.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| primary_key | string | string[] | "order_id" | Field(s) used for SHA-256 deduplication hash. Duplicate publishes with the same primary key hash are rejected. |
| visibility_key | string | string[] | "customer_id" | Field(s) controlling in-flight exclusivity. Only one message per visibility key value can be leased at a time. |
| consume_ttl_seconds | integer (0–300) | 60 | Lease duration in seconds. If not acknowledged within this window, the message is automatically requeued. |
| max_message_age_seconds | integer (1–1209600) | 86400 | Maximum age of a message in seconds. Expired messages are moved to the dead letter queue by the reaper. |
| max_queue_depth | integer (0–100000) | 10000 | Maximum number of messages allowed in the queue (0 = unlimited). Behaviour when exceeded is controlled by overflow_policy. |
| overflow_policy | "reject" | "drop_oldest_append" | "reject" | What to do when max_queue_depth is reached. Reject returns 409; drop_oldest removes the oldest message and appends the new one. |
| auto_ack | boolean | false | When true, messages are automatically acknowledged on consume (no explicit ack required). |
| allow_subscriptions | boolean | false | When true, other tenants can subscribe to this queue for cross-tenant fan-out. |
| masking_rules | object[] | null | null | Optional data masking or tokenization rules applied to payload fields before encryption on publish. |
| schema_validation | "none" | "partial" | "full" | "none" | Payload validation mode. none = off; partial = every defined field required, extra fields allowed; full = exact match, no extra fields. A failing payload is rejected with 400. Frozen after creation. |
| schema | object | null | null | JSON Schema (an object schema with a non-empty properties map) validated against each published payload. Required when schema_validation is partial or full. Frozen after creation. |
Publish Flow
Consume Flow
Dead Letter Flow
Key Behaviors
- Visibility key enforcement — only one message per visibility key value can be in-flight (leased) at a time. This prevents a single consumer from monopolising the queue and ensures ordered processing per key group.
- Primary key deduplication — the primary key fields are hashed with SHA-256 on publish. If a message with the same hash already exists in the queue, the publish is rejected with a 409 Conflict.
- Overflow policies — when max_queue_depth is set and the queue is full, "reject" returns a 409 error to the publisher, while "drop_oldest_append" silently removes the oldest message to make room.
- Lease-based consumption — each consume creates a time-limited lease. If the consumer crashes or fails to ack/nack, the reaper automatically requeues the message after consume_ttl_seconds.
- Auto-ack mode — when enabled, messages are immediately deleted on consume with no explicit acknowledgement required. Nacks are rejected in this mode.
One-to-Many
one-to-manyThe One-to-Many queue delivers independent copies of each message to multiple consumers. Each consumer (identified by client_id) tracks their own position in the queue and must acknowledge a message before receiving the next one. There is no visibility key — consumption is tracked per consumer, not per message field. This is ideal for fan-out patterns where multiple services need to process the same data independently.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| primary_key | string | string[] | "message_id" | Field(s) used for SHA-256 deduplication hash on publish. |
| consume_ttl_seconds | integer (0–300) | 60 | Lease duration in seconds per consumer. Expired leases are requeued for that consumer. |
| max_message_age_seconds | integer (1–1209600) | 86400 | Maximum age of a message. Expired messages are moved to the dead letter queue. |
| max_queue_depth | integer (0–100000) | 10000 | Maximum number of messages in the queue (0 = unlimited). |
| overflow_policy | "reject" | "drop_oldest_append" | "reject" | Behaviour when max_queue_depth is reached. |
| auto_ack | boolean | false | When true, messages are auto-acknowledged on consume. |
| allow_subscriptions | boolean | false | Allow cross-tenant subscriptions to this queue. |
| masking_rules | object[] | null | null | Optional data masking or tokenization rules. |
| schema_validation | "none" | "partial" | "full" | "none" | Payload validation mode. none = off; partial = every defined field required, extra fields allowed; full = exact match, no extra fields. A failing payload is rejected with 400. Frozen after creation. |
| schema | object | null | null | JSON Schema (an object schema with a non-empty properties map) validated against each published payload. Required when schema_validation is partial or full. Frozen after creation. |
Publish Flow
Consume Flow
Dead Letter Flow
Key Behaviors
- Independent consumer tracking — each consumer (client_id) maintains their own position. Consumer A can be on message 5 while Consumer B is still on message 2.
- No visibility key — unlike One-to-One, there is no field-based routing. Every consumer sees every message in queue order.
- Per-consumer leasing — each consumer gets their own lease. If Consumer A's lease expires, only Consumer A's copy is requeued.
- Primary key deduplication — same as One-to-One; duplicate primary key hashes are rejected on publish.
- Fan-out pattern — publish once, consume independently by many services. Perfect for event distribution.
Key-Value
key-valueThe Key-Value queue is a state snapshot and lookup store. Items are keyed by their primary key and can be retrieved individually, in bulk, or as a delta since the consumer's last request. There is no consume/ack/nack cycle and no dead letter queue — this is a persistent store that consumers read from at any time. It also serves as the lookup source for Stream-to-One and Stream-to-Many enrichment queues.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| primary_key | string | string[] | "item_id" | Field(s) used to uniquely identify each item. |
| on_duplicate | "reject" | "replace" | "reject" | What to do when publishing an item whose primary key already exists: reject returns 409; replace upserts (overwrites) the stored item. A queue setting, not a per-publish option. |
| sort_key | string | string[] | null | null | Optional field(s) to control the order items are returned in GET /items responses. |
| max_message_age_seconds | integer (1–1209600) | 86400 | Maximum age for items in seconds (1–2592000). Expired items are removed by the reaper. Capped at 14 days; retention cannot be disabled. |
| max_queue_depth | integer (0–100000) | 10000 | Maximum number of items allowed in the store (0 = unlimited). |
| overflow_policy | "reject" | "drop_oldest_append" | "reject" | Behaviour when max_queue_depth is reached. |
| allow_subscriptions | boolean | false | Allow cross-tenant subscriptions to this store. |
| masking_rules | object[] | null | null | Optional data masking or tokenization rules. |
| schema_validation | "none" | "partial" | "full" | "none" | Payload validation mode. none = off; partial = every defined field required, extra fields allowed; full = exact match, no extra fields. A failing payload is rejected with 400. Frozen after creation. |
| schema | object | null | null | JSON Schema (an object schema with a non-empty properties map) validated against each published item. Required when schema_validation is partial or full. Frozen after creation. |
Publish Flow
Consume Flows
Delete Flow
Key Behaviors
- State snapshots — consumers can read the full state at any time with GET /items. No consume/ack cycle means no message loss from crashes.
- Delta queries — POST /items/updates returns only items modified since the provided timestamp, enabling efficient polling without re-fetching everything.
- Sort key ordering — when a sort_key is configured, GET /items returns items sorted by that field for consistent ordering.
- On-duplicate handling — "reject" (default) returns 409 if the primary key already exists; "replace" overwrites the existing item with the new payload.
- No DLQ — since there is no consume/ack cycle, there is no dead letter queue. Expired items (via max_message_age_seconds) are simply removed.
- Enrichment source — Key-Value queues serve as the lookup source for Stream-to-One and Stream-to-Many queues.
Time-Series
time-seriesThe Time-Series queue aggregates numeric data points into clock-aligned time buckets. Each bucket computes OHLC (open, high, low, close) statistics along with sum, count, and average for every configured aggregation field. Buckets are aligned to clock boundaries (e.g., a 5-minute interval starting at the top of the hour) for consistent reporting. Gap filling ensures continuous time ranges even when no data arrives.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| aggregation_fields | string[] | (required) | List of numeric field names from the payload to aggregate (e.g., ["price", "volume"]). |
| primary_key | string | string[] | null | null | Optional field(s) to partition data into separate series (e.g., "symbol" for multi-stock tracking). |
| aggregation_interval_minutes | 1 | 5 | 10 | 15 | 30 | 60 | 1440 | 5 | Width of each time bucket in minutes (1440 = daily). Must be one of the allowed values. |
| max_buckets | integer (1–100000) | 1000 | Maximum number of buckets retained per series. Oldest buckets are dropped when exceeded. |
| max_message_age_seconds | integer (1–1209600) | 0 (disabled) | Optional time-based bucket retention: the reaper evicts buckets older than this (up to 14 days). Omit to disable — buckets then stay bounded only by max_buckets. |
| timezone | string (IANA) | (required) | Required. A valid IANA timezone (e.g. "UTC", "America/New_York") that bucket boundaries align to; an invalid or missing value is rejected with a 400 at create. |
| decimal_places | integer (0–10) | null | 2 | Rounding precision (decimal places) for aggregated values. Set to null to keep full precision. |
| allow_subscriptions | boolean | false | Allow cross-tenant subscriptions to this time-series. |
| schema_validation | "none" | "partial" | "full" | "none" | Payload validation mode applied to each published event. none = off; partial = every defined field required, extra fields allowed; full = exact match, no extra fields. Frozen after creation. |
| schema | object | null | null | JSON Schema (an object schema with a non-empty properties map) validated against each published event. Required when schema_validation is partial or full. Frozen after creation. |
Publish Flow
close (latest), sum, count, avg DB-->>API: Bucket updated API->>Sub: Fan-out to subs API-->>C: 200 OK {bucket}
Query Flows
Key Behaviors
- Clock-aligned buckets — bucket boundaries snap to clock intervals (e.g., 10:00, 10:05, 10:10 for 5-minute intervals), not relative to the first data point.
- OHLC aggregation — each field in every bucket tracks open (first value), high (maximum), low (minimum), close (most recent), sum, count, and average.
- Gap filling — when fill_gaps is true, empty buckets are included in range queries with zero counts, ensuring a continuous time axis for charting.
- Multi-series support — when primary_key is set, data is partitioned into separate series. Pass the
primary_keyquery parameter to retrieve a specific series, andGET /buckets/keysto list the active series. - No consume/ack cycle — like Key-Value, Time-Series is a read-only store. There is no dead letter queue.
- Automatic eviction — when max_buckets is exceeded, the oldest buckets are dropped to keep storage bounded.
- Clear buckets — the queue owner can reset the series with
DELETE /queues/{id}/buckets(tenant-admin auth), removing every stored bucket for the queue.
Stream-to-One
stream-to-oneThe Stream-to-One queue combines the single-consumer delivery model of One-to-One with automatic data enrichment. On publish, the API performs a foreign key lookup against a linked Key-Value queue and merges the lookup data into the message payload before encryption and storage. If the lookup fails, the message is either rejected (with a DLQ entry) or deferred for later enrichment, depending on the missing_lookup policy. Consumption follows the same visibility-key-enforced, lease-based pattern as One-to-One.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| primary_key | string | string[] | "message_id" | Field(s) for deduplication hash within this queue. |
| visibility_key | string | string[] | (required) | Field(s) for in-flight exclusivity, same as One-to-One. |
| foreign_key | string | string[] | (required) | Field(s) in the incoming payload whose value is looked up against the linked Key-Value queue's primary key. The field names may differ. |
| lookup_queue_id | string | (required) | Id of the Key-Value queue to join with on publish. |
| missing_lookup | "reject" | "defer" | "reject" | What to do when the foreign key is not found in the lookup queue. "reject" sends to DLQ; "defer" stores as pending. |
| consume_ttl_seconds | integer (0–300) | 60 | Lease duration in seconds. |
| max_message_age_seconds | integer (1–1209600) | 86400 | Maximum message age before dead-lettering. |
| max_queue_depth | integer (0–100000) | 10000 | Maximum number of messages in the queue (0 = unlimited). |
| overflow_policy | "reject" | "drop_oldest_append" | "reject" | Behaviour when max_queue_depth is reached. |
| auto_ack | boolean | false | Auto-acknowledge on consume. |
| allow_subscriptions | boolean | false | Allow cross-tenant subscriptions. |
| masking_rules | object[] | null | null | Optional data masking or tokenization rules. |
| schema_validation | "none" | "partial" | "full" | "none" | Payload validation mode. none = off; partial = every defined field required, extra fields allowed; full = exact match, no extra fields. A failing payload is rejected with 400. Frozen after creation. |
| schema | object | null | null | JSON Schema (an object schema with a non-empty properties map) validated against each published payload. Required when schema_validation is partial or full. Frozen after creation. |
Publish + Enrichment Flow
Consume Flow
Key Behaviors
- Automatic enrichment — the API transparently joins incoming data with lookup data before the consumer ever sees the message. No client-side joins needed.
- Missing lookup: reject — if the foreign key is not found in the Key-Value queue, the message is sent to the dead letter queue with reason "key_not_found" and a 422 is returned.
- Missing lookup: defer — the message is stored with status "pending_enrichment". When the matching key-value item is later published, deferred messages are automatically enriched and moved to the active queue.
- Visibility key enforcement — same as One-to-One, only one message per visibility key can be in-flight at a time.
- Lookup queue validation — at creation the lookup_queue must reference an existing Key-Value queue owned by the same tenant (rejected with a 400 otherwise). The foreign key's value is matched against that queue's primary key at publish time; the field names need not be identical.
Stream-to-Many
stream-to-manyThe Stream-to-Many queue is the multi-consumer variant of Stream-to-One. It performs the same automatic Key-Value lookup enrichment on publish, but delivers independent copies to multiple consumers — just like One-to-Many. There is no visibility key; each consumer independently tracks their position and acknowledges messages at their own pace.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| primary_key | string | string[] | "message_id" | Field(s) for deduplication hash within this queue. |
| foreign_key | string | string[] | (required) | Field(s) used as the lookup key against the linked Key-Value queue. |
| lookup_queue_id | string | (required) | Id of the Key-Value queue to join with on publish. |
| missing_lookup | "reject" | "defer" | "reject" | Policy when the foreign key is not found: reject to DLQ, or defer until the key appears. |
| consume_ttl_seconds | integer (0–300) | 60 | Lease duration per consumer. |
| max_message_age_seconds | integer (1–1209600) | 86400 | Maximum message age before dead-lettering. |
| max_queue_depth | integer (0–100000) | 10000 | Maximum number of messages in the queue (0 = unlimited). |
| overflow_policy | "reject" | "drop_oldest_append" | "reject" | Behaviour when max_queue_depth is reached. |
| auto_ack | boolean | false | Auto-acknowledge on consume. |
| allow_subscriptions | boolean | false | Allow cross-tenant subscriptions. |
| masking_rules | object[] | null | null | Optional data masking or tokenization rules. |
| schema_validation | "none" | "partial" | "full" | "none" | Payload validation mode. none = off; partial = every defined field required, extra fields allowed; full = exact match, no extra fields. A failing payload is rejected with 400. Frozen after creation. |
| schema | object | null | null | JSON Schema (an object schema with a non-empty properties map) validated against each published payload. Required when schema_validation is partial or full. Frozen after creation. |
Publish + Enrichment Flow
Consume Flow
Key Behaviors
- Same enrichment as Stream-to-One — identical foreign key lookup and merge behaviour on publish, including reject and defer modes for missing lookups.
- Multi-consumer delivery — like One-to-Many, each consumer independently tracks position and acknowledges at their own pace.
- No visibility key — unlike Stream-to-One, there is no field-based in-flight exclusivity. All consumers receive all messages.
- Independent leasing — each consumer's lease is independent. A nack or lease expiry for one consumer does not affect others.
- Deferred enrichment — when missing_lookup is "defer" and the key-value item is later published, all deferred messages matching that key are enriched and promoted to the active queue for all consumers.
Document
documentThe Document queue is a repository for whole files. Unlike the message queue types, an uploaded XML, CSV, or JSON document is stored intact as a single item — it is never split into per-record messages. Clients list the stored documents, download an individual document in the format they ask for, and delete it. There is no consume/ack/nack cycle and no dead letter queue: a download is non-destructive, and a document stays until it is explicitly deleted or expires.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| document_ttl_seconds | integer (1–2592000) | 2592000 (30 days) | Retention in seconds; documents older than the TTL are hard-deleted by the reaper. Capped at 30 days and cannot be disabled (defaults to the 30-day maximum). Editable after creation. |
| max_queue_depth | integer (0–100000) | 0 (unlimited) | Optional cap on the number of stored documents; an upload beyond it is rejected with 429. 0 = unlimited. |
| allow_subscriptions | boolean | false | Allow cross-tenant document subscriptions from this queue (document → document fan-out). |
Upload
POST /queues/{id}/documents with the raw file as the body and Content-Type set to
the file's format (application/json, text/csv, or application/xml).
Two headers are required: X-Document-Name and X-Document-Description.
The document must be convertible to JSON or the upload is rejected. Maximum size is 1 MB.
Duplicate filenames are allowed — each upload is an independent document with its own id.
List & Download
GET /queues/{id}/documents returns a paginated list (filename, description,
size, upload time) with optional filename-prefix and uploaded-at filters. GET
/queues/{id}/documents/{doc_id} downloads one document; the Accept header selects the
format (JSON / CSV / XML) and the broker converts on the fly — the original format is returned
byte-exact. The name and description come back as X-Document-Name /
X-Document-Description response headers.
Delete
DELETE /queues/{id}/documents/{doc_id} removes a document. Only consumers may
delete — upload requires the publish permission; list, download, and delete require the
consume permission.
Cross-Tenant Fan-Out
A document queue can be a subscription source (allow_subscriptions). Each uploaded document is
fanned out to subscriber tenants' document queues, re-encrypted under each subscriber's own key. Delivered
copies are independent — a source-side delete or TTL expiry does not cascade to
subscribers, and re-delivery is idempotent.
Key Behaviors
- Whole-file storage — a document is one item, never split into messages.
- Format conversion on download — stored once, served as JSON, CSV, or XML per the Accept header (the source must be JSON-convertible at upload).
- Non-destructive reads — downloading never removes a document; only an explicit delete or TTL does.
- Duplicate names allowed — documents are addressed by id and distinguished by upload time.
Text Document
text-documentThe Text Document queue is a repository for plain-text files. It is the text/plain sibling of the Document queue: an uploaded document is stored intact as a single item and returned exactly as it was uploaded. Unlike the Document queue, text is treated as an opaque blob — it is never parsed, canonicalised to JSON, or converted between formats. Everything else (listing, download, delete, retention, cross-tenant fan-out, permissions) works exactly like the Document queue.
Settings
| Setting | Type | Default | Description |
|---|---|---|---|
| document_ttl_seconds | integer (1–2592000) | 2592000 (30 days) | Retention in seconds; documents older than the TTL are hard-deleted by the reaper. Capped at 30 days and cannot be disabled (defaults to the 30-day maximum). Editable after creation. |
| max_queue_depth | integer (0–100000) | 0 (unlimited) | Optional cap on the number of stored documents; an upload beyond it is rejected with 429. 0 = unlimited. |
| allow_subscriptions | boolean | false | Allow cross-tenant text-document subscriptions from this queue (text-document → text-document fan-out). |
Upload
POST /queues/{id}/documents with the raw file as the body and Content-Type:
text/plain. Two headers are required: X-Document-Name and
X-Document-Description. The body is stored verbatim — there is no
JSON/CSV/XML validation, so any UTF-8 text is accepted. A non-text/plain Content-Type is
rejected with 415. Maximum size is 1 MB; duplicate filenames are
allowed (each upload is an independent document with its own id).
List & Download
GET /queues/{id}/documents returns the same paginated list as the Document
queue. GET /queues/{id}/documents/{doc_id} downloads one document and always returns it
verbatim as text/plain — the Accept header is ignored and
no conversion is attempted, because the stored text is not structured.
Delete
DELETE /queues/{id}/documents/{doc_id} removes a document. Only consumers may
delete — upload requires the publish permission; list, download, and delete require the
consume permission.
Cross-Tenant Fan-Out
A text-document queue can be a subscription source (allow_subscriptions). Each uploaded
document is fanned out to subscriber tenants' text-document queues, re-encrypted under each subscriber's
own key. Delivered copies are independent — a source-side delete or TTL expiry does
not cascade to subscribers, and re-delivery is idempotent.
Key Behaviors
- Verbatim storage — the exact bytes uploaded are stored and returned; no parsing or canonicalisation.
- No format conversion — download always returns text/plain; the Accept header is ignored.
- text/plain only — a structured Content-Type (JSON/CSV/XML) is rejected with 415; use the Document queue for those.
- Otherwise identical to Document — same TTL, depth cap, fan-out, and permission model.
Cross-Cutting Features
Cross-Tenant Subscriptions
Any queue type can opt in to cross-tenant subscriptions by setting allow_subscriptions: true. When a subscriber tenant creates a subscription, every message published to the source queue is automatically fan-out copied to the subscriber's queue. The subscriber's queue must match the source queue's type. Subscriptions are managed by the subscribing tenant and can be removed at any time.
Overflow Policies
Depth limits apply to the message queue types. When a queue's max_queue_depth is set (non-zero), the overflow policy determines what happens when the queue is full:
- reject (default) — the publish request is rejected with a 409 Conflict. The publisher must retry or handle the backpressure.
- drop_oldest_append — the oldest message in the queue is silently removed to make room for the new message. Useful for "latest N" patterns where older data is less valuable.
Document queues have no overflow policy, but they do support an optional
max_queue_depth document-count cap (an upload beyond it is rejected with 429); otherwise
they are bounded by the per-document 1 MB size cap and optional
document_ttl_seconds retention.
Retention Ceiling
Retention is bounded and cannot be disabled. Messages (and their dead letters) are
capped at 14 days; documents are capped at 30 days.
An explicit max_message_age_seconds (1–1,209,600) or document_ttl_seconds
(1–2,592,000) is required — a 0 "keep forever" value is rejected. The message
limit is measured from publish and applies across a message's whole lifetime: a message
moved to the dead-letter queue is still hard-deleted once it is 14 days past publish, so time spent
queued plus time spent dead-lettered never exceeds the ceiling. (Time-series buckets are aggregates
rather than messages and are bounded instead by max_buckets.)
Message Size Limits
Each message payload is limited to 10 KB — an over-limit publish is rejected with a 413. This applies to every message queue type and to each record of a batch or file publish, so a batch is bounded by the per-message limit rather than a fixed message count (there is no hard cap on the number of records in a request). Messages are meant to be small events; large payloads belong in a Document queue, which stores each file whole under a separate 1 MB cap.
Content Types
All queue types support publishing and consuming in JSON, CSV,
and XML formats. Set the Content-Type header on publish and the
Accept header on consume to control the format. The broker automatically converts
between formats — publish in CSV, consume in JSON, or any other combination. Internal storage
is always JSON; conversion happens transparently at the API boundary.
Batch Publishing
Publish many records in one request — a JSON array, a multi-row CSV, or a multi-element XML
document. The X-Batch-Mode header controls the semantics: best-effort
(default) publishes every record that passes validation and returns a per-record results/errors
report, while all-or-nothing validates every record first and publishes nothing if
any record fails (for example a missing primary key).
File Upload
Upload a whole CSV, XML, or JSON file to POST /publish/file (up to 1 MB).
A file is treated as one document and is always all-or-nothing: the entire file must
parse and every record must be valid (a JSON object carrying the queue's primary key, within the
10 KB per-message limit) — otherwise the upload is rejected and nothing is published.
Payload Schema Validation
Any queue type can enforce a JSON Schema on every published payload. The schema and its
mode (schema + schema_validation) are set at queue creation and frozen
afterwards. Validation governs the top level of the payload — every field named in the schema's
properties is required in both modes, and the mode controls whether unlisted fields are allowed:
- none (default) — no validation; any payload is accepted.
- partial — all defined fields are required, but the payload may carry additional fields the schema doesn't list. Good for evolving payloads where only the core fields are guaranteed.
- full — exact match: all defined fields required and no extra fields permitted. A payload that fails validation is rejected with a 400.