# Webbu API — Full Documentation > Webhook buffering and batched delivery API. Webbu receives webhooks, buffers them by a grouping key, and delivers batched payloads to configured HTTP destinations with retry and DLQ support. OpenAPI version: 3.0.3 API version: 1.0.0 ## Servers - **Production**: `https://webbu.dev/api` - **Local emulator**: `http://localhost:5001/webhook-buffer/us-central1/api` ## Authentication All authenticated endpoints require the `X-API-Key` header with a Webbu API key. - **Type**: API Key - **Header**: `X-API-Key` - **Format**: `wbk_` followed by 48 hex characters Example: ``` curl -H "X-API-Key: wbk_your_api_key" https://webbu.dev/api/v1/admin/projects ``` ## Endpoints ### Ingest Webhook ingestion endpoints #### POST /v1/ingest/{projectId}/{bufferId} **Ingest a webhook payload** Operation ID: `ingestWebhook` Receives a webhook payload, extracts the group key, and buffers it for batched delivery. Returns 202 Accepted immediately. The group key is read from the JSON body at the buffer's `groupKey.path` (`jsonpath` mode), falling back to the `groupKey.fallbackHeader` request header. If no key is found the request fails with 400. An API key is optional; if `X-API-Key` is sent it must be valid and have access to the buffer. Each request uses 1 credit per started KB of the JSON-serialized payload (429 when the balance is insufficient). **Authentication**: Optional (public endpoint) **Parameters:** - `projectId` (path): string **(required)** — The project ID - `bufferId` (path): string **(required)** — The buffer ID - `X-Idempotency-Key` (header): string — Message ID used for deduplication when the JSON body has no top-level `messageId` field. If neither is present, a random UUID is generated (no deduplication). **Request Body:** **Responses:** - **202**: Webhook accepted and buffered - `status`: string - `messageId`: string — The message ID used for deduplication - `itemId`: string — The unique item ID assigned to this payload - **400**: Invalid request (buffer disabled, project mismatch, or missing group key) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **401**: Invalid or revoked API key, or missing/invalid source signature (when source verification is enabled) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **403**: API key does not have access to this buffer - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Buffer or customer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **429**: Insufficient credits - `error`: string - `credits`: number — Current credit balance - `required`: number — Credits required for this request - `message`: string - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### Projects Project management #### GET /v1/admin/projects **List projects** Operation ID: `listProjects` Returns all projects for a customer. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `customerId` (query): string — The customer ID to list projects for **Responses:** - **200**: List of projects - array of Project - **400**: Missing customerId - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### POST /v1/admin/projects **Create a project** Operation ID: `createProject` Creates a new project for the authenticated customer. Requires owner or admin role. The caller chooses the project ID (the dashboard uses `p__`). **Authentication**: Required (`X-API-Key` header) **Request Body:** - `projectId`: string **(required)** — Unique project ID chosen by the caller - `customerId`: string — Optional. Defaults to the authenticated customer; any other value is rejected with 403. - `name`: string **(required)** **Responses:** - **201**: Project created - `projectId`: string - `customerId`: string - `name`: string - `enabled`: boolean - `createdAt`: number - `updatedAt`: number - **400**: Missing required fields or customer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Project already exists - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/projects/{projectId} **Get a project** Operation ID: `getProject` Returns a single project by ID. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `projectId` (path): string **(required)** **Responses:** - **200**: Project details - `projectId`: string - `customerId`: string - `name`: string - `enabled`: boolean - `createdAt`: number - `updatedAt`: number - **404**: Project not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### PATCH /v1/admin/projects/{projectId} **Update a project** Operation ID: `updateProject` Updates a project's name or enabled status. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `projectId` (path): string **(required)** **Request Body:** - `name`: string - `enabled`: boolean **Responses:** - **200**: Updated project - `projectId`: string - `customerId`: string - `name`: string - `enabled`: boolean - `createdAt`: number - `updatedAt`: number - **404**: Project not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### DELETE /v1/admin/projects/{projectId} **Delete a project** Operation ID: `deleteProject` Deletes a project. Fails if the project still has buffers. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `projectId` (path): string **(required)** **Responses:** - **204**: Project deleted - **400**: Cannot delete project with existing buffers - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Project not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### Buffers Buffer configuration #### GET /v1/admin/buffers **List buffers** Operation ID: `listBuffers` Returns all buffers for a customer, optionally filtered by project. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `customerId` (query): string — The customer ID - `projectId` (query): string — Filter by project ID **Responses:** - **200**: List of buffers - array of Buffer - **400**: Missing customerId - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### POST /v1/admin/buffers **Create a buffer** Operation ID: `createBuffer` Creates a new buffer with batching rules, delivery configuration, and overflow settings. The buffer is validated against the BufferSchema before creation. **Authentication**: Required (`X-API-Key` header) **Request Body:** - `bufferId`: string **(required)** — Unique buffer ID chosen by the caller (the dashboard uses `b__`) - `customerId`: string — Optional. Defaults to the authenticated customer; any other value is rejected with 403. - `projectId`: string **(required)** - `name`: string **(required)** - `enabled`: boolean - `groupKey`: object **(required)** — How the group key is extracted. With `jsonpath`, `path` is read from the JSON body (dot notation, e.g. `$.data.customer`); `fallbackHeader` is used when the path is missing (or always, with `header` mode). Ingest fails with 400 if no key is found. - `mode`: string **(required)** (enum: jsonpath, header) - `path`: string - `fallbackHeader`: string - `batch`: object — Flush conditions. A group is delivered when any of them is met. - `maxItems`: integer - `maxBytes`: integer - `idleTimeoutMs`: integer - `maxWaitFromFirstMs`: integer - `overflow`: object — Per-segment limits; past them, new items go to a new segment of the same group key. - `maxGroupItems`: integer - `maxGroupBytes`: integer - `delivery`: object **(required)** - `url`: string **(required)** - `method`: string (enum: POST, PUT) - `headers`: object — Extra headers sent with every delivery - `idempotencyHeader`: string — Header carrying a per-batch idempotency key (`grp::v`) - `throughput`: object - `maxAttempts`: integer — Delivery attempts (including the first) before the batch goes to the DLQ - `backoff`: object - `initialMs`: integer - `maxMs`: integer - `multiplier`: number - `dlqPauseThreshold`: integer — The buffer is disabled after this many consecutive batches go to the DLQ - `sourceVerification`: object — Optional webhook source signature verification config (HMAC) - `enabled`: boolean — Whether to verify incoming webhook signatures - `signingSecret`: string — Secret key used to compute HMAC signature - `signatureHeader`: string — HTTP header that carries the signature, as the lowercase hex HMAC-SHA256 of the JSON body (no prefix) - `algorithm`: string (enum: hmac-sha256, hmac-sha1) — Stored with the buffer; signatures are currently always verified with HMAC-SHA256 **Responses:** - **201**: Buffer created - `bufferId`: string - `customerId`: string - `projectId`: string - `name`: string - `enabled`: boolean - `groupKey`: object — Group key extraction configuration - `batch`: object - `maxItems`: integer - `maxBytes`: integer - `idleTimeoutMs`: integer - `maxWaitFromFirstMs`: integer - `overflow`: object - `delivery`: object - `url`: string - `method`: string - `createdAt`: number - `updatedAt`: number - **400**: Missing fields, invalid config, or project not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Buffer already exists - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/buffers/{bufferId} **Get a buffer** Operation ID: `getBuffer` Returns a single buffer by ID with its full configuration. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `bufferId` (path): string **(required)** **Responses:** - **200**: Buffer details - `bufferId`: string - `customerId`: string - `projectId`: string - `name`: string - `enabled`: boolean - `groupKey`: object — Group key extraction configuration - `batch`: object - `maxItems`: integer - `maxBytes`: integer - `idleTimeoutMs`: integer - `maxWaitFromFirstMs`: integer - `overflow`: object - `delivery`: object - `url`: string - `method`: string - `createdAt`: number - `updatedAt`: number - **404**: Buffer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### PATCH /v1/admin/buffers/{bufferId} **Update a buffer** Operation ID: `updateBuffer` Updates a buffer's configuration. Only specific fields can be updated: name, enabled, groupKey, batch, overflow, delivery, sourceVerification, throughput, analytics, dlqPauseThreshold. Re-enabling a disabled buffer resets its DLQ count. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `bufferId` (path): string **(required)** **Request Body:** - `name`: string - `enabled`: boolean - `groupKey`: object - `batch`: object - `overflow`: object - `delivery`: object - `sourceVerification`: object — Optional webhook source signature verification config (HMAC) - `enabled`: boolean — Whether to verify incoming webhook signatures - `signingSecret`: string — Secret key used to compute HMAC signature - `signatureHeader`: string — HTTP header that carries the signature, as the lowercase hex HMAC-SHA256 of the JSON body (no prefix) - `algorithm`: string (enum: hmac-sha256, hmac-sha1) — Stored with the buffer; signatures are currently always verified with HMAC-SHA256 - `throughput`: object - `analytics`: object - `dlqPauseThreshold`: integer **Responses:** - **200**: Updated buffer - `bufferId`: string - `customerId`: string - `projectId`: string - `name`: string - `enabled`: boolean - `groupKey`: object — Group key extraction configuration - `batch`: object - `maxItems`: integer - `maxBytes`: integer - `idleTimeoutMs`: integer - `maxWaitFromFirstMs`: integer - `overflow`: object - `delivery`: object - `url`: string - `method`: string - `createdAt`: number - `updatedAt`: number - **404**: Buffer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### DELETE /v1/admin/buffers/{bufferId} **Delete a buffer** Operation ID: `deleteBuffer` Permanently deletes a buffer. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `bufferId` (path): string **(required)** **Responses:** - **204**: Buffer deleted - **404**: Buffer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### POST /v1/admin/buffers/{bufferId}/clone **Clone a buffer** Operation ID: `cloneBuffer` Creates a copy of an existing buffer with a new ID. The cloned buffer inherits all configuration but starts with fresh runtime state. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `bufferId` (path): string **(required)** — The buffer ID to clone **Request Body:** - `name`: string — Name for the cloned buffer (defaults to "Original Name (Copy)") **Responses:** - **201**: Cloned buffer - `bufferId`: string - `customerId`: string - `projectId`: string - `name`: string - `enabled`: boolean - `groupKey`: object — Group key extraction configuration - `batch`: object - `maxItems`: integer - `maxBytes`: integer - `idleTimeoutMs`: integer - `maxWaitFromFirstMs`: integer - `overflow`: object - `delivery`: object - `url`: string - `method`: string - `createdAt`: number - `updatedAt`: number - **400**: Failed to clone buffer configuration - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Buffer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### POST /v1/admin/buffers/{bufferId}/test **Test buffer delivery** Operation ID: `testBuffer` Sends a test delivery to the buffer's configured destination URL. Useful for verifying that the destination is reachable and configured correctly. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `bufferId` (path): string **(required)** — The buffer ID to test **Request Body:** - `payload`: object **(required)** — Test payload to deliver **Responses:** - **200**: Test result - `success`: boolean - `statusCode`: integer - `durationMs`: integer - `responseBody`: string - `error`: string - **400**: Missing payload - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Buffer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### Analytics Metrics and analytics #### GET /v1/analytics/summary **Get dashboard summary stats** Operation ID: `getDashboardSummary` Returns today's metrics, trend deltas compared to yesterday, and resource counts (projects and buffers). **Authentication**: Required (`X-API-Key` header) **Parameters:** - `customerId` (query): string — The customer ID to get stats for **Responses:** - **200**: Dashboard summary stats - `today`: object - `ingestCount`: integer - `deliveryCount`: integer - `deliverySuccess`: integer - `deliveryFail`: integer - `dlqCount`: integer - `avgBatchSize`: number - `compressionRatio`: number - `trend`: object - `ingestDelta`: number — Percentage change from yesterday - `deliveryDelta`: number - `successRateDelta`: number - `counts`: object - `projects`: integer - `buffers`: integer - **400**: Missing customerId - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/analytics/timeseries **Get analytics timeseries data** Operation ID: `getAnalyticsTimeseries` Returns time series metrics, latency histograms, and aggregated totals. Can be filtered by project and buffer. Defaults to the last 7 days. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `customerId` (query): string — The customer ID - `projectId` (query): string — Filter by project ID - `bufferId` (query): string — Filter by buffer ID - `days` (query): integer (default: 7) — Number of days to include **Responses:** - **200**: Analytics timeseries data - `timeseries`: array - `histograms`: object - `timeToFlush`: object - `deliveryDuration`: object - `totals`: object - `ingestCount`: integer - `deliveryCount`: integer - `deliverySuccess`: integer - `deliveryFail`: integer - `retryCount`: integer - `itemsDelivered`: integer - `dlqCount`: integer - `compressionRatio`: number - `successRate`: number - **400**: Missing customerId - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### DLQ Dead letter queue management #### GET /v1/dlq **List dead letter queue items** Operation ID: `listDlqItems` Returns DLQ items for the customer. Optionally filter by buffer ID. Results are limited to a maximum of 500 items. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `customerId` (query): string — The customer ID - `bufferId` (query): string — Filter by buffer ID - `limit` (query): integer (default: 100) — Maximum number of items to return **Responses:** - **200**: List of DLQ items - array of DlqItem - **400**: Missing customerId - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Buffer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/dlq/{dlqId} **Get DLQ item details** Operation ID: `getDlqItem` Returns the full details of a single dead letter queue item. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `dlqId` (path): string **(required)** — The DLQ item ID - `customerId` (query): string — The customer ID (for ownership validation) **Responses:** - **200**: DLQ item details - `dlqId`: string - `customerId`: string - `projectId`: string - `bufferId`: string - `groupId`: string - `groupKey`: string - `reason`: string (enum: max_retries_exceeded, non_retryable_error) - `lastError`: string — Error of the last delivery attempt (automatic or manual retry) - `lastStatusCode`: integer — HTTP status of the last delivery attempt (0 for network errors) - `attempt`: integer — Zero-based index of the last automatic attempt (automatic attempts made = attempt + 1) - `itemCount`: integer — Number of events in the failed batch - `payloadSummary`: object - `messageIds`: array - `firstPayloadPreview`: string — First 500 characters of the first event's payload (JSON) - `payloadStored`: boolean — Whether the full batch is stored and can be retried. Missing on entries created before batches were stored; those return `payload_unavailable` on retry. - `batch`: object - `groupHash`: string - `segment`: integer - `firstItemAt`: number - `lastActivityAt`: number - `groupVersion`: integer - `payloadBytes`: integer — Total size of the stored event payloads - `retryCount`: integer — Number of failed manual retries - `lastRetryAt`: number - `createdAt`: number - **400**: Missing customerId - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **403**: Access denied - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: DLQ item not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### DELETE /v1/dlq/{dlqId} **Delete a DLQ item** Operation ID: `deleteDlqItem` Permanently deletes a dead letter queue item and its stored batch. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `dlqId` (path): string **(required)** — The DLQ item ID to delete - `customerId` (query): string — The customer ID (for ownership validation) **Responses:** - **204**: DLQ item deleted - **400**: Missing customerId - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **403**: Access denied - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: DLQ item not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/dlq/{dlqId}/batch **Get the stored batch of a DLQ item** Operation ID: `getDlqBatch` Returns the exact delivery body (`{webbu, window, items[]}`) that failed and that `POST /v1/dlq/{dlqId}/retry` sends again. Entries created before batches were stored have no batch and return `409` with code `payload_unavailable`. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `dlqId` (path): string **(required)** — The DLQ item ID - `customerId` (query): string — The customer ID (for ownership validation) **Responses:** - **200**: The stored delivery body - `webbu`: object - `customerId`: string - `projectId`: string - `bufferId`: string - `groupKey`: string - `groupHash`: string - `segment`: integer - `window`: object - `firstItemAt`: number - `lastActivityAt`: number - `itemsCount`: integer - `items`: array - **403**: Access denied - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: DLQ item not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: The batch of this entry was not stored (`code` is `payload_unavailable`) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### POST /v1/dlq/{dlqId}/retry **Retry a DLQ item** Operation ID: `retryDlqItem` Redelivers the stored batch of a dead letter queue item — the same events, in the same order, with the same body and idempotency key (`grp:{groupId}:v{version}`) — to the buffer's **current** delivery configuration (URL, method, headers, signing). This is a single, synchronous delivery attempt (destination timeout: 10 seconds). - Delivered (`200`): the DLQ item and its stored batch are deleted. - Destination failed (`502`, code `delivery_failed`): the item stays in the DLQ with `retryCount`, `lastError` and `lastStatusCode` updated. You can retry it again. - Another retry of the same item is in flight (`409`, code `retry_in_progress`). - The item was created before batches were stored (`409`, code `payload_unavailable`). **Authentication**: Required (`X-API-Key` header) **Parameters:** - `dlqId` (path): string **(required)** — The DLQ item ID to retry **Request Body:** - `customerId`: string — The customer ID (optional; must match the authenticated customer) **Responses:** - **200**: Batch redelivered; the DLQ item was removed - `success`: boolean - `status`: string (enum: delivered) - `message`: string - `dlqId`: string - `itemCount`: integer - `statusCode`: integer — HTTP status returned by the destination - `durationMs`: number - **403**: Access denied - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: DLQ item or buffer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Not retried: `retry_in_progress` (another retry of this item is running) or `payload_unavailable` (the item's events were not stored) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **502**: The destination did not accept the batch; the item stays in the DLQ - `error`: string - `code`: string (enum: delivery_failed) - `dlqId`: string - `itemCount`: integer - `statusCode`: integer — HTTP status returned by the destination (0 for network errors) - `durationMs`: number - `lastError`: string - `errorClass`: string - `retryCount`: integer — Failed manual retries of this item, including this one --- ### API Keys API key management #### GET /v1/admin/api-keys **List API keys** Operation ID: `listApiKeys` Lists all API keys for the authenticated customer. Never returns the key hash or secret. **Authentication**: Required (`X-API-Key` header) **Responses:** - **200**: List of API keys - array of object - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### POST /v1/admin/api-keys **Create a new API key** Operation ID: `createApiKey` Creates a new API key for the authenticated customer. The plaintext secret is returned only once in the response — store it securely. Maximum of 10 active keys per customer. **Authentication**: Required (`X-API-Key` header) **Request Body:** - `name`: string **(required)** — Human-readable name for the key - `role`: string (enum: viewer, member, admin, owner) — Role for the key (defaults to caller's role, cannot exceed it) - `projectIds`: array — Restrict key to specific projects **Responses:** - **201**: API key created (secret returned only once) - `keyId`: string - `name`: string - `secret`: string — Plaintext API key (wbk_ prefix). Not retrievable again. - `keyPrefix`: string - `role`: string - `projectIds`: array - `createdAt`: string - **400**: Invalid input - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **403**: Cannot create key with higher role than own - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Maximum active keys limit reached - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### DELETE /v1/admin/api-keys/{keyId} **Revoke an API key** Operation ID: `revokeApiKey` Soft-deletes an API key by setting a revokedAt timestamp. The key can no longer be used for authentication. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `keyId` (path): string **(required)** — The API key ID to revoke **Responses:** - **204**: API key revoked - **404**: API key not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: API key is already revoked - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### Credits Credit and billing #### GET /v1/admin/credit-packs **List credit packs** Operation ID: `listCreditPacks` Returns the one-time credit packs with prices derived from the subscription plans: each pack uses the per-credit rate of the largest plan whose monthly credits are less than or equal to the pack size, using the plans' prices in the requested currency. `available` is false until the pack's Hubla offer is configured. If the plans have no prices in the requested currency yet, the packs are priced in the first currency of `currencies` and `currency` says which one. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `currency` (query): string (default: BRL) — Currency to price the packs in **Responses:** - **200**: Credit packs - `currency`: string — Currency of every price in `packs` - `currencies`: array — Currencies the packs can be priced in - `packs`: array - **400**: Unsupported currency (code `unsupported_currency`) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/credit-packs/{packId}/checkout-url **Get credit pack checkout URL** Operation ID: `getCreditPackCheckoutUrl` Returns the Hubla checkout URL for a credit pack, linked to the authenticated customer. Credit packs can only be bought with an active subscription; otherwise the request fails with 403 and code `subscription_required`. Purchased credits never expire. If the amount paid differs from the pack price (e.g. a coupon), the credits granted are proportional to the amount paid. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `packId` (path): string **(required)** **Responses:** - **200**: Checkout URL generated - `checkoutUrl`: string - `packId`: string - `credits`: integer - **403**: An active subscription is required to buy credit packs (code `subscription_required`) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Pack not found or inactive - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Pack is not available for purchase yet (no Hubla offer configured) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **503**: Payment provider not configured - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/credits **Get credit balance** Operation ID: `getCredits` Returns the current credit balance, limit, and usage for the authenticated customer. **Authentication**: Required (`X-API-Key` header) **Responses:** - **200**: Credit balance information - `customerId`: string - `credits`: number — Current credit balance - `creditLimit`: number — Monthly credit limit - `creditsUsedThisMonth`: number — Credits consumed this billing cycle - `lastResetAt`: number — Timestamp of last monthly reset - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### POST /v1/admin/credits/add **Add credits (local emulator only)** Operation ID: `addCredits` Development helper that grants free credits to the authenticated customer (maximum 100,000 per request). Only available when the API runs in the local emulator; everywhere else it responds 404. Buy credits with a credit pack instead (see /v1/admin/credit-packs). **Authentication**: Required (`X-API-Key` header) **Request Body:** - `amount`: number **(required)** — Number of credits to add **Responses:** - **200**: Credits added - `customerId`: string - `creditsAdded`: number - `newBalance`: number - **400**: Invalid amount - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Not available outside the local emulator - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/credits/transactions **Get credit transaction history** Operation ID: `getCreditTransactions` Returns a list of credit transactions (purchases, consumptions, refunds) for the authenticated customer. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `limit` (query): integer (default: 50) — Maximum number of transactions to return **Responses:** - **200**: Credit transaction list - `transactions`: array - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### Payments Subscription and payment management #### GET /v1/admin/plans **List available subscription plans** Operation ID: `listPlans` Returns all available subscription plans. **Authentication**: Required (`X-API-Key` header) **Responses:** - **200**: List of plans - array of Plan - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/subscription **Get current subscription** Operation ID: `getSubscription` Returns the authenticated customer's current subscription and associated plan details. **Authentication**: Required (`X-API-Key` header) **Responses:** - **200**: Subscription and plan details (null if no subscription) - `subscription`: Subscription - `plan`: Plan - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### DELETE /v1/admin/subscription **Cancel subscription (graceful)** Operation ID: `cancelSubscription` Schedules cancellation for end of current billing period. The subscription remains active (status: CANCELLING) until currentPeriodEnd. **Authentication**: Required (`X-API-Key` header) **Request Body:** - `reason`: string — Reason for cancellation **Responses:** - **200**: Cancellation scheduled - `message`: string - `endsAt`: number — Timestamp when subscription ends - `status`: string - **404**: No active subscription - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Already cancelling or cancelled - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **503**: Payment provider not configured - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### DELETE /v1/admin/subscription/immediate **Cancel subscription immediately** Operation ID: `cancelSubscriptionImmediate` Terminates the subscription immediately (admin/owner only). Bypasses graceful end-of-period cancellation. **Authentication**: Required (`X-API-Key` header) **Request Body:** - `reason`: string — Reason for immediate cancellation **Responses:** - **204**: Subscription cancelled immediately - **404**: No active subscription - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Already cancelled - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **503**: Payment provider not configured - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/invoices **List invoices** Operation ID: `listInvoices` Returns a list of invoices for the authenticated customer. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `limit` (query): integer (default: 50) — Maximum number of invoices to return **Responses:** - **200**: List of invoices - array of Invoice - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/invoices/{invoiceId} **Get invoice details** Operation ID: `getInvoice` Returns the details of a specific invoice. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `invoiceId` (path): string **(required)** — The invoice ID **Responses:** - **200**: Invoice details - `invoiceId`: string - `customerId`: string - `subscriptionId`: string - `status`: string - `amount`: number - `paidAt`: number - `createdAt`: number - **403**: Forbidden (invoice belongs to another customer) - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Invoice not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/subscription/checkout-url **Get checkout URL** Operation ID: `getCheckoutUrl` Returns a checkout URL for the specified plan, linked to the authenticated customer. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `planId` (query): string **(required)** — The plan ID to get a checkout URL for **Responses:** - **200**: Checkout URL generated - `checkoutUrl`: string - `planId`: string - `planName`: string - **400**: Missing planId or plan inactive - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **404**: Plan not found or missing checkout URL - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **503**: Payment provider not configured - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### Customers Customer management #### POST /v1/admin/customers **Create a customer** Operation ID: `createCustomer` Creates a new customer record. Requires owner role. **Authentication**: Required (`X-API-Key` header) **Request Body:** - `customerId`: string **(required)** - `name`: string **(required)** **Responses:** - **201**: Customer created - `customerId`: string - `name`: string - `enabled`: boolean - `credits`: number - `creditLimit`: number - `creditsUsedThisMonth`: number - `createdAt`: number - **400**: Missing required fields - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **409**: Customer already exists - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- #### GET /v1/admin/customers/{customerId} **Get a customer** Operation ID: `getCustomer` Returns a single customer by ID. **Authentication**: Required (`X-API-Key` header) **Parameters:** - `customerId` (path): string **(required)** **Responses:** - **200**: Customer details - `customerId`: string - `name`: string - `enabled`: boolean - `credits`: number - `creditLimit`: number - `creditsUsedThisMonth`: number - `createdAt`: number - **404**: Customer not found - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) - **500**: Internal server error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) --- ### Health Health check #### GET /health **Health check** Operation ID: `healthCheck` Returns the service health status. **Authentication**: Required (`X-API-Key` header) **Responses:** - **200**: Service is healthy - `status`: string --- ## Schemas ### Error - `error`: string **(required)** — Error message - `code`: string — Machine-readable error code, when the error has one (e.g. `payload_unavailable`) ### Customer - `customerId`: string - `name`: string - `enabled`: boolean - `credits`: number - `creditLimit`: number - `creditsUsedThisMonth`: number - `createdAt`: number ### Project - `projectId`: string - `customerId`: string - `name`: string - `enabled`: boolean - `createdAt`: number - `updatedAt`: number ### Buffer - `bufferId`: string - `customerId`: string - `projectId`: string - `name`: string - `enabled`: boolean - `groupKey`: object — Group key extraction configuration - `batch`: object - `maxItems`: integer - `maxBytes`: integer - `idleTimeoutMs`: integer - `maxWaitFromFirstMs`: integer - `overflow`: object - `delivery`: object - `url`: string - `method`: string - `createdAt`: number - `updatedAt`: number ### DlqItem - `dlqId`: string - `customerId`: string - `projectId`: string - `bufferId`: string - `groupId`: string - `groupKey`: string - `reason`: string (enum: max_retries_exceeded, non_retryable_error) - `lastError`: string — Error of the last delivery attempt (automatic or manual retry) - `lastStatusCode`: integer — HTTP status of the last delivery attempt (0 for network errors) - `attempt`: integer — Zero-based index of the last automatic attempt (automatic attempts made = attempt + 1) - `itemCount`: integer — Number of events in the failed batch - `payloadSummary`: object - `messageIds`: array - `firstPayloadPreview`: string — First 500 characters of the first event's payload (JSON) - `payloadStored`: boolean — Whether the full batch is stored and can be retried. Missing on entries created before batches were stored; those return `payload_unavailable` on retry. - `batch`: object - `groupHash`: string - `segment`: integer - `firstItemAt`: number - `lastActivityAt`: number - `groupVersion`: integer - `payloadBytes`: integer — Total size of the stored event payloads - `retryCount`: integer — Number of failed manual retries - `lastRetryAt`: number - `createdAt`: number ### DeliveryBatch Body delivered to a buffer's destination - `webbu`: object - `customerId`: string - `projectId`: string - `bufferId`: string - `groupKey`: string - `groupHash`: string - `segment`: integer - `window`: object - `firstItemAt`: number - `lastActivityAt`: number - `itemsCount`: integer - `items`: array ### DlqRetryResult - `success`: boolean - `status`: string (enum: delivered) - `message`: string - `dlqId`: string - `itemCount`: integer - `statusCode`: integer — HTTP status returned by the destination - `durationMs`: number ### DlqRetryFailure - `error`: string - `code`: string (enum: delivery_failed) - `dlqId`: string - `itemCount`: integer - `statusCode`: integer — HTTP status returned by the destination (0 for network errors) - `durationMs`: number - `lastError`: string - `errorClass`: string - `retryCount`: integer — Failed manual retries of this item, including this one ### Plan - `planId`: string - `name`: string - `priceInCents`: integer — Legacy single-currency monthly price (in `currency`, BRL). Use `prices`. - `currency`: string — Currency of `priceInCents` - `prices`: object — Monthly price per currency, in cents (e.g. BRL 4900, USD 990) - `BRL`: integer - `USD`: integer - `creditsPerCycle`: number - `isActive`: boolean ### Subscription - `subscriptionId`: string - `customerId`: string - `planId`: string - `status`: string (enum: PENDING, ACTIVE, CANCELLING, CANCELLED, PAST_DUE) - `currentPeriodStart`: number - `currentPeriodEnd`: number - `autoRenew`: boolean ### Invoice - `invoiceId`: string - `customerId`: string - `subscriptionId`: string - `status`: string - `amount`: number - `paidAt`: number - `createdAt`: number