Upgrade to v0.13
This guide covers breaking changes and migration steps when upgrading from v0.12 to v0.13.
Breaking Changes Overview
| Change | Impact | Action Required |
|---|---|---|
| Delivery renamed to Attempt | All delivery-related endpoints, response types, and config | Update API routes, response handling, and config vars |
| Route restructuring | Tenant-scoped event, schema, and topic endpoints removed | Update API client paths |
| New retry mechanism | Retry endpoint moved and changed format | Update retry calls |
| ID prefix delimiter removed | Custom ID prefixes | Include delimiter in prefix value (e.g., evt_ instead of evt) |
| Refined API schema | Pagination, query filters, and error responses | Update response parsing, query parameters, and error handling |
Empty custom_headers rejected | Webhook destination creation/update | Remove empty custom_headers or omit the field |
| SDK v0.13.1 changes | SDK security names, global tenant_id, method names | Update SDK constructor and method calls |
| Database schema changes | PostgreSQL log store | Automatic — migrations run on startup |
Delivery Renamed to Attempt
The "delivery" concept in the API has been renamed to "attempt." An attempt represents a single delivery attempt of an event to a destination — the same concept, just a clearer name.
Attempt Schema
json
id: Unique attempt identifier (default prefix configurable viaIDGEN_ATTEMPT_PREFIX)status:successorfailedcode: String response status code or error codeattempt_number: 0 for first attempt, 1+ for retriesmanual: Whether this was a manually triggered retryevent_id: The ID of the associated eventdestination_id: The destination ID this attempt was sent toresponse_data: Response details (only included wheninclude=response_datais specified)event: Expanded event object (only present wheninclude=eventorinclude=event.datais specified)
The include Parameter
The attempt endpoints support an include query parameter to expand related data inline:
| Value | Effect |
|---|---|
include=event | Includes event object with id, topic, time, eligible_for_retry, metadata |
include=event.data | Same as event plus the full event data payload |
include=response_data | Includes response_data with status code, body, and headers |
Multiple values can be combined: ?include=event.data&include=response_data
Configuration
| v0.12 | v0.13 |
|---|---|
IDGEN_DELIVERY_PREFIX | Removed — use IDGEN_ATTEMPT_PREFIX |
IDGEN_DELIVERY_EVENT_PREFIX | Removed |
If you had customized IDGEN_DELIVERY_PREFIX, update it to IDGEN_ATTEMPT_PREFIX.
Route Restructuring
v0.13 restructures the API routes. Events and attempts are now accessed through top-level endpoints or scoped under destinations, rather than deeply nested under tenants. Destination type schemas and topics are no longer tenant-scoped.
Removed Routes
| v0.12 Route | v0.13 Replacement |
|---|---|
GET /tenants/:tenant_id/events | GET /events?tenant_id=:tenant_id |
GET /tenants/:tenant_id/events/:event_id | GET /events/:event_id |
GET /tenants/:tenant_id/events/:event_id/deliveries | GET /attempts?event_id=:event_id or GET /tenants/:tenant_id/destinations/:destination_id/attempts?event_id=:event_id |
GET /tenants/:tenant_id/destinations/:destination_id/events | GET /tenants/:tenant_id/destinations/:destination_id/attempts |
GET /tenants/:tenant_id/destinations/:destination_id/events/:event_id | GET /tenants/:tenant_id/destinations/:destination_id/attempts/:attempt_id |
POST /tenants/:tenant_id/destinations/:destination_id/events/:event_id/retry | POST /retry with { "event_id": "...", "destination_id": "..." } |
GET /tenants/:tenant_id/destination-types | GET /destination-types |
GET /tenants/:tenant_id/destination-types/:type | GET /destination-types/:type |
GET /tenants/:tenant_id/topics | GET /topics |
New Routes
| Route | Description |
|---|---|
GET /events | List events (admin cross-tenant, or filtered by tenant_id) |
GET /events/:event_id | Get a specific event by ID |
GET /attempts | List attempts (admin cross-tenant, with filters) |
GET /attempts/:attempt_id | Get a specific attempt by ID |
GET /tenants/:tenant_id/destinations/:destination_id/attempts | List attempts for a destination |
GET /tenants/:tenant_id/destinations/:destination_id/attempts/:attempt_id | Get a specific attempt for a destination |
POST /retry | Retry event delivery |
Action: Update all API client paths to the new routes.
New Retry Mechanism
The retry endpoint has been moved from a deeply nested path to a standalone top-level endpoint with a request body:
v0.12:
v0.13:
json
When authenticated with a Tenant JWT, only events belonging to that tenant can be retried. When authenticated with Admin API Key, events from any tenant can be retried.
Action: Update retry calls to use POST /retry with event_id and destination_id in the request body.
ID Prefix Delimiter Removed
ID generation no longer adds a default _ delimiter between the prefix and the generated ID. The prefix value is now used as-is.
If you use custom ID prefixes, include the delimiter in the prefix value:
| v0.12 | v0.13 |
|---|---|
IDGEN_EVENT_PREFIX=evt (produces evt_xxx) | IDGEN_EVENT_PREFIX=evt_ (produces evt_xxx) |
IDGEN_DESTINATION_PREFIX=des (produces des_xxx) | IDGEN_DESTINATION_PREFIX=des_ (produces des_xxx) |
This applies to all IDGEN_*_PREFIX config vars: IDGEN_EVENT_PREFIX, IDGEN_DESTINATION_PREFIX, and IDGEN_ATTEMPT_PREFIX.
Action: Append _ (or your desired delimiter) to all custom ID prefix values.
Refined API Schema
v0.13 refines the API pagination, query filters, sorting, and error responses.
Pagination Response Format
All paginated list endpoints now return a new response envelope.
v0.12:
json
v0.13:
json
Key differences:
datarenamed tomodelsnext/prevcursors moved into apaginationobject alongsideorder_by,dir, andlimitcountremoved from event/attempt list responses (still present on tenant list)- Empty cursors are now
nullinstead of""
Action: Update all code that parses paginated responses:
response.data→response.modelsresponse.next→response.pagination.nextresponse.prev→response.pagination.prev
Query Filter Format
Time filter parameters have been updated to use a structured format:
Event and attempt list endpoints:
| v0.12 | v0.13 |
|---|---|
?start=2024-01-01T00:00:00Z | ?time[gte]=2024-01-01T00:00:00Z |
?end=2024-01-31T23:59:59Z | ?time[lte]=2024-01-31T23:59:59Z |
Tenant list endpoint:
| v0.12 | v0.13 |
|---|---|
| (not available) | ?created_at[gte]=2024-01-01T00:00:00Z |
| (not available) | ?created_at[lte]=2024-01-31T23:59:59Z |
The time[gte]/time[lte] and created_at[gte]/created_at[lte] filters support both YYYY-MM-DD and full RFC3339 timestamps.
Sorting Parameters
The order query parameter on GET /tenants has been split into two parameters:
| v0.12 | v0.13 |
|---|---|
?order=desc | ?order_by=created_at&dir=desc |
The new order_by and dir parameters are also available on event and attempt list endpoints:
| Parameter | Values | Default |
|---|---|---|
order_by | time (events/attempts), created_at (tenants) | Varies by endpoint |
dir | asc, desc | desc |
Error Response Format
Error responses now include a status field and use a consistent structure:
v0.12:
json
v0.13:
json
Key differences:
- New
statusfield mirrors the HTTP status code - Validation errors in
datachanged from a{ field: tag }object to an array of human-readable messages
SDK Migration
The official SDKs (TypeScript, Go, Python) are generated from the OpenAPI spec. When you upgrade to SDK versions that target v0.13, the following breaking changes apply. This section shows the migration from the pre–v0.13 SDK API to the v0.13 API.
List response shape
All list endpoints return { models, pagination } instead of { data, next, prev }.
Before (v0.12-style SDK):
ts
After (v0.13 SDK):
ts
Use response.models for the array of items and response.pagination for next, prev, order_by, dir, and limit.
Events API
| Before (v0.12-style) | After (v0.13) |
|---|---|
sdk.events.listByDestination({ tenantId, destinationId }) | sdk.events.list({ tenantId, destinationId }) — or list without destinationId for all tenant events |
sdk.events.getByDestination({ tenantId, destinationId, eventId }) | sdk.events.get(eventId) — pass event ID as a string |
| Tenant-scoped list/get by destination | Use sdk.events.list({ tenantId }) and filter client-side, or use attempts API for destination-scoped data |
Note: GET /events?destination_id=... is documented but currently returns 500 in some environments; prefer listing without destination_id until that is fixed. See GitHub issue #688 for details.
Attempts (formerly deliveries)
| Before (v0.12-style) | After (v0.13) |
|---|---|
| Delivery-focused endpoints / types | sdk.attempts.list(), sdk.attempts.get(), sdk.attempts.retry() |
| Destination-scoped deliveries | sdk.destinations.listAttempts(), sdk.destinations.getAttempt() |
| Retry via event/destination path | sdk.attempts.retry({ eventId, destinationId }) |
Schemas and topics
| Before (v0.12-style) | After (v0.13) |
|---|---|
| Tenant-scoped destination types / schemas | sdk.schemas.listDestinationTypes(), sdk.schemas.get() (unscoped) |
| Tenant-scoped topics | sdk.topics.list() (unscoped) |
Summary checklist for SDK users
- Replace any use of
response.datawithresponse.modelsandresponse.next/response.prevwithresponse.pagination?.next/response.pagination?.prev. - Switch events access to
sdk.events.list(request)andsdk.events.get(eventId)(event ID as string); remove use of event-by-destination list/get if present. - Switch delivery/retry usage to
sdk.attempts.*andsdk.destinations.listAttempts()/getAttempt(). - Use
sdk.attempts.retry({ eventId, destinationId })for retries. - Use
sdk.schemas.listDestinationTypes()andsdk.topics.list()without tenant scope.
TypeScript SDK v0.13.0 and later reflect these changes. Go and Python SDKs will align with the same API once regenerated from the v0.13 OpenAPI spec.
SDK v0.13.1 Changes
SDK v0.13.1 flattens security into a single top-level apiKey constructor parameter, removes the global tenant_id parameter, and cleans up method names.
Flattened security
The two security schemes (adminApiKey / tenantJwt) have been collapsed into a single apiKey parameter. Pass either an Admin API Key or a Tenant JWT — the SDK sends it as a Bearer token in both cases.
TypeScript:
ts
For tenant JWT authentication, pass the JWT as apiKey:
ts
Go:
go
Python:
py
Global tenant_id parameter removed
The tenantId constructor option has been removed. Pass tenantId directly to each method that requires it.
TypeScript:
ts
Positional parameters for tenants and destinations
In v0.13.1, tenant- and destination-scoped methods take positional arguments (e.g. tenantId first), not a single request object. Update calls as shown in the table below.
Request body (params) change: The three methods that send a request body — tenants.upsert, destinations.create, and destinations.update — no longer take that body inside a params property. Pass the body as the second (or third) argument directly.
| Method | Before (object form) | After (v0.13.1 positional) |
|---|---|---|
tenants.upsert | upsert({ tenantId }) or upsert({ tenantId, params }) | upsert(tenantId, params?) |
tenants.delete | delete({ tenantId }) | delete(tenantId) |
tenants.get | get({ tenantId }) | get(tenantId) |
destinations.list | list({ tenantId, type? }) | list(tenantId, type?, topics?) |
destinations.create | create({ tenantId, params }) | create(tenantId, params) |
destinations.get | get({ tenantId, destinationId }) | get(tenantId, destinationId) |
destinations.update | update({ tenantId, destinationId, params }) | update(tenantId, destinationId, params) |
destinations.delete | delete({ tenantId, destinationId }) | delete(tenantId, destinationId) |
Example — before (v0.13.0): one object with tenantId and (for create/update/upsert) a params property for the body:
ts
Example — after (v0.13.1): positional arguments; the body is the second or third argument (no params wrapper):
ts
events.list() still takes a single request object (e.g. { tenantId?, topic?, limit? }). events.get() takes the event ID as a string: events.get(eventId).
Method name cleanup
Redundant Jwt suffixes have been removed from method names:
| Before (v0.13.0) | After (v0.13.1) |
|---|---|
sdk.schemas.getDestinationTypeJwt() | sdk.schemas.getDestinationType() |
sdk.schemas.listDestinationTypesJwt() | sdk.schemas.listDestinationTypes() |
Summary checklist for SDK v0.13.1 users
- Replace
security: { adminApiKey: "..." }orsecurity: { tenantJwt: "..." }withapiKey: "..."as a top-level constructor parameter. - Remove
tenantIdfrom the SDK constructor and pass it as the first (positional) argument to each tenant/destination method (e.g.destinations.list(tenantId),destinations.get(tenantId, destinationId)). - Change tenant and destination method calls from object form to positional:
tenants.upsert(tenantId, params?),tenants.delete(tenantId),destinations.create(tenantId, params),destinations.get(tenantId, destinationId),destinations.update(tenantId, destinationId, params),destinations.delete(tenantId, destinationId). Pass the request body (formerly theparamsproperty) as that positional argument — do not wrap it in{ params: ... }. - Use
events.get(eventId)with the event ID as a string, notevents.get({ eventId }). - Rename
getDestinationTypeJwttogetDestinationTypeandlistDestinationTypesJwttolistDestinationTypes.
Empty custom_headers Rejected
Webhook and standard webhook destinations no longer accept an empty custom_headers object. Previously, passing "custom_headers": {} was silently accepted. In v0.13, this returns a validation error.
If you set custom_headers, it must contain at least one entry:
json
If you don't need custom headers, omit the custom_headers field entirely instead of passing an empty object.
Database Schema Changes
v0.13 includes a PostgreSQL schema migration that renames the deliveries table to attempts and denormalizes event data into it for improved query performance. This migration runs automatically when Outpost starts.
Before upgrading, back up your PostgreSQL database. The migration performs destructive operations including dropping the old event_delivery_index table and rebuilding indexes.
Upgrade Checklist
-
Before upgrading:
- Update API clients to use
/attemptsroutes instead of/deliveriesand destination-scoped/eventsroutes - Update retry calls to use
POST /retrywithevent_idanddestination_idin request body - Update destination-types and topics calls to use unscoped routes (
/destination-types,/topics) - Update event access from
GET /tenants/:id/events/:idtoGET /events/:id - Rename
IDGEN_DELIVERY_PREFIXtoIDGEN_ATTEMPT_PREFIXand removeIDGEN_DELIVERY_EVENT_PREFIX - Append delimiter to all custom
IDGEN_*_PREFIXconfig values (e.g.,evt→evt_) - Update response parsing for the new pagination envelope (
models/pagination) - Update
GET /tenantssorting fromordertoorder_by+dir - Update time filter params from
start/endtotime[gte]/time[lte] - Update error response handling if parsing
datafield - Back up PostgreSQL database
- Update API clients to use
-
Upgrade:
- Update Outpost to v0.13 and restart — database migrations run automatically on startup