# Outpost Docs > Concatenated markdown of every page, including the generated API reference. > Prefer `/llms.txt` to fetch pages selectively. # Getting Started This guide walks through the minimum path from zero to a delivered SMS: a tenant, a project, an API key, and a send. Tenant and project management endpoints require an **admin** API key. If you are integrating a downstream service, an Outpost administrator will hand you a tenant-scoped key and you can skip to step 4. Prefer TypeScript? Generate types once, then use the same typed client for every step. For the full setup, see [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). ```bash npm install openapi-fetch npm install -D openapi-typescript typescript npx openapi-typescript "$OUTPOST_URL/v1/openapi.json" -o ./src/outpost-api.ts ``` ```typescript import createClient from 'openapi-fetch'; import type { paths } from './outpost-api'; export const api = createClient({ baseUrl: process.env.OUTPOST_URL!, headers: { 'x-api-key': process.env.OUTPOST_API_KEY! }, }); ``` Use an admin key for steps 1-3, then switch `OUTPOST_API_KEY` (or create a second client) to the project key you create in step 3. **Create a tenant.** A tenant represents your organization and scopes all of your data. ```typescript const { data, error } = await api.POST('/v1/tenants', { body: { name: 'Acme Corp' }, }); if (error) throw error; const tenantId = data.tenantId; ``` ```bash curl -X POST "$OUTPOST_URL/v1/tenants" \ -H "x-api-key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corp" }' ``` **Create a project under the tenant.** Projects hold API key and webhook configuration. A tenant needs at least one project before it can send. ```typescript const { data, error } = await api.POST('/v1/tenants/{tenantId}/projects', { params: { path: { tenantId } }, body: { name: 'Notifications Service' }, }); if (error) throw error; const projectId = data.projectId; ``` ```bash curl -X POST "$OUTPOST_URL/v1/tenants/$TENANT_ID/projects" \ -H "x-api-key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Notifications Service" }' ``` **Create an API key for the project.** The plaintext key is returned **exactly once** as `plainApiKey` - store it securely. Outpost only keeps a SHA-256 hash. ```typescript const { data, error } = await api.POST('/v1/projects/{projectId}/api-keys', { params: { path: { projectId } }, body: { name: 'production' }, }); if (error) throw error; // Shown once. Persist this; you cannot retrieve it again. const tenantKey = data.plainApiKey; ``` ```bash curl -X POST "$OUTPOST_URL/v1/projects/$PROJECT_ID/api-keys" \ -H "x-api-key: $ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production" }' ``` **Send a message.** Sends are asynchronous - the endpoint validates each recipient, queues accepted items, and returns `202` with per-item outcomes. ```typescript const sendApi = createClient({ baseUrl: process.env.OUTPOST_URL!, headers: { 'x-api-key': tenantKey }, }); const { data, error } = await sendApi.POST('/v1/messages/sms', { body: { items: [{ to: '+15551234567', body: 'Hello from Outpost!' }], communicationId: 'welcome-blast-1', }, }); if (error) { console.error('Send failed:', error); } else { console.log('Accepted:', data.accepted, 'Rejected:', data.rejected); } ``` ```bash curl -X POST "$OUTPOST_URL/v1/messages/sms" \ -H "x-api-key: $TENANT_KEY" \ -H "Content-Type: application/json" \ -d '{ "items": [{ "to": "+15551234567", "body": "Hello from Outpost!" }], "communicationId": "welcome-blast-1" }' ``` The optional `communicationId` groups the batch and enables deduplication - the same phone can never receive two messages with the same `communicationId`. **Track the outcome.** Delivery happens asynchronously via the carrier network. You can poll the message, browse its [events](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/events/content.md), or - the recommended approach - configure a [webhook](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) on your project so Outpost pushes `DELIVERED`, `FAILED`, and opt-out notifications to you. ## What happens to your message [#what-happens-to-your-message] After the API accepts an item, the message flows through validation, a send queue, AWS End User Messaging, and delivery receipt processing. The full lifecycle - statuses, retries, and compliance footers - is documented in [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md). ## Where to go next [#where-to-go-next] * [How Outpost Fits Together](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/architecture/content.md) - the system map * [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md) - lifecycle, statuses, retries * [Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) - push notifications to your systems * [API Reference](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/content.md) - every endpoint, schema, and error, generated from the OpenAPI document * [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md) - end-to-end type safety from the same spec --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/getting-started) --- # Overview Outpost is an enterprise SMS messaging platform: sending, delivery tracking, retries, phone validation, opt-out management, webhooks, and pre-aggregated analytics with multi-tenant isolation. The platform in plain language: what it does and who it is for. Send your first SMS: a tenant, a project, an API key, and a send. See every component of the system and how they relate to each other. The core entity: a single SMS tracked from creation through delivery, failure, or block. Get message and phone events pushed to your own systems. Admin console how-tos: manage tenants, send messages, and inspect phones. Every endpoint, schema, and error, generated from the OpenAPI document. --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs) --- # What is Outpost? Outpost is an enterprise SMS messaging platform built on AWS End User Messaging (Pinpoint SMS). It handles the full lifecycle of outbound and inbound SMS - sending, delivery tracking, retries, phone number validation, opt-out management, and cost tracking - with multi-tenant isolation and pre-aggregated analytics. Outpost is **messaging infrastructure, not a user-facing product**. It is consumed by downstream services that have their own users, campaigns, and communication concepts. Those services interact with Outpost through a REST API authenticated by API keys. ## What Outpost does for you [#what-outpost-does-for-you] * **Sends SMS** with per-message lifecycle tracking and automatic retries * **Processes delivery receipts** from carrier networks in real time via AWS SNS/SQS * **Validates phone numbers** (carrier, type, SMS capability) before sending * **Manages opt-outs** - STOP/START keywords, an API, and a public opt-out page * **Handles two-way SMS** for inbound replies and keyword processing * **Isolates tenants** with tenant-scoped API keys, stats, and data access * **Pre-aggregates statistics** across dimensions (tenant, project, campaign, phone, country) and time windows (hour, day, month, all-time) * **Keeps an immutable audit trail** of every message lifecycle change and phone state change * **Delivers webhooks** so your systems learn about deliveries and opt-outs as they happen * **Groups messages** into communications and campaigns, with deduplication at the communication level ## Who uses which part? [#who-uses-which-part] | You are... | You mostly care about | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | A downstream service sending SMS | [Getting Started](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/getting-started/content.md), [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md), [Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) | | An operator/admin using the dashboard | [Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md), [Stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md), [Phones](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md) | | A developer working on Outpost itself | [How Outpost Fits Together](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/architecture/content.md), [Workers](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md) | ## Next steps [#next-steps] * New to the system? Start with [How Outpost Fits Together](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/architecture/content.md) to see every component and how they relate. * Ready to send a message? Follow [Getting Started](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/getting-started/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/what-is-outpost) --- # Overview {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api) --- # API Keys & Authentication Every request to Outpost (except the public opt-out page) is authenticated with an API key sent in the `x-api-key` header. **In the dashboard:** keys are issued and revoked from a project's API Access section, see [Managing Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/tenants-and-projects/content.md#api-access). ## How they relate [#how-they-relate] API keys are owned by [projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md), and the tenant/project a key belongs to is stamped onto every message it sends - which is how messages, stats, and webhooks all end up correctly scoped. ## Two kinds of keys [#two-kinds-of-keys] | | Admin key | Tenant key | | ---------------------------------------------- | ----------------------------- | ------------------------------------ | | Scope | Entire system | One tenant + one project | | Created via | By hand (deliberately no API) | `POST /projects/:projectId/api-keys` | | Can specify `tenantId`/`projectId` in requests | Yes | No - returns `403` | | Typical user | Outpost dashboard, operators | Downstream services | ## Key facts [#key-facts] **The plaintext key is shown exactly once.** Creating a key returns `plainApiKey` a single time. Outpost stores only a SHA-256 hash - if the plaintext is lost, create a new key. Keys look like `outpost-`, so they're recognizable in config files without revealing their scope. **Validation is a single fast lookup.** For a tenant key to be valid: the key must exist, be active, not be expired, and both its tenant and project must be active. Tenant/project status is denormalized onto the key record (`tenantIsActive`, `projectIsActive`), so disabling a tenant takes effect immediately without adding database reads to the authentication hot path. **Failures are distinct.** An invalid/expired/deactivated key returns `401`; a valid key used on an endpoint its role doesn't allow returns `403`. **Keys can expire.** An optional `expiresAt` can be set at creation. Expired keys remain in the system but stop working. **Limits:** a project can have at most 10 API keys. ## The one unauthenticated surface [#the-one-unauthenticated-surface] The public messaging status page (`GET`/`POST` `/o/:token`) is intentionally unauthenticated so message recipients can manage their opt-out state from a link. It is rate-limited and tokens are unguessable - see [Phones & Opt-Out](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md#the-public-messaging-status-page). ## API reference [#api-reference] * [`GET /v1/projects/:projectId/api-keys`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/listProjectApiKeys/content.md) - list a project's keys (hashes only) * [`POST /v1/projects/:projectId/api-keys`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/createProjectApiKey/content.md) - create a key (plaintext returned once) * [`DELETE /v1/projects/:projectId/api-keys/:keyId`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/deleteProjectApiKey/content.md) - revoke a key Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/api-keys) --- # How Outpost Fits Together Outpost is built from a small set of components. This page is the system map: what each component is, and - more importantly - how they relate. ## The two hierarchies [#the-two-hierarchies] Outpost organizes data along two independent hierarchies that meet at the message. **The ownership hierarchy** answers "who is sending?" Tenants own projects; projects own API keys and webhook configs. Every authenticated request is scoped by this chain. **The grouping hierarchy** answers "what is being sent?" Campaigns group communications; communications group messages; each message records its individual send attempts. The **[Phone](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md)** sits outside both hierarchies. Phones are global - the same `+15551234567` is a single record shared by every tenant that messages it, because opt-out is a regulatory commitment to the phone's owner, not to any one tenant. ## The full entity map [#the-full-entity-map] How to read this: * A **[Message](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md)** is the center of the system. It belongs to a tenant/project (ownership), optionally to a communication/campaign (grouping), and always targets a phone. * **[Events](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/events/content.md)** are the immutable audit trail. Message events track the message lifecycle; phone events track phone state changes (opt-outs, validation failures). * **[Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md)** are how events leave Outpost. A project's webhook configs subscribe to event types; matching events produce delivery attempts. * **[Stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md)** are derived counters, updated asynchronously when messages settle or phones change state. They are a read model - messages, phones, and events remain the source of truth. ## How a message flows through the system [#how-a-message-flows-through-the-system] The runtime picture: components that do work, connected by queues. 1. The **API** validates each recipient (format, duplicates, validity, opt-out), creates the message, and queues it. 2. The **send worker** runs preflight checks and submits to AWS End User Messaging. 3. AWS sends back **delivery receipts**; the **DLR worker** updates the message, settles it or requeues it for retry. 4. The **two-way worker** handles inbound replies - STOP/START/HELP keywords drive opt-out state on the phone. 5. Every DynamoDB write flows through **streams** to the **stream worker**, which updates stats and fans out webhook deliveries. Workers and queues are covered in detail in [Workers & Background Processing](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md). ## Component index [#component-index] | Component | One-line summary | | ------------------------------------------------------------------------- | ---------------------------------------------------------- | | [Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md) | Ownership and configuration: who sends, with what settings | | [API Keys](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/api-keys/content.md) | Authentication; admin keys vs tenant-scoped keys | | [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md) | One SMS with full lifecycle tracking; the core entity | | [Phones & Opt-Out](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md) | Global phonebook, validation, regulatory opt-out | | [Communications & Campaigns](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/communications-and-campaigns/content.md) | Optional grouping layers; deduplication and rollups | | [Events](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/events/content.md) | Immutable audit trail for messages and phones | | [Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) | Push event notifications to downstream systems | | [Stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md) | Pre-aggregated counters powering dashboards | | [Workers](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md) | The async machinery: send, DLR, two-way, stream, webhook | | [Cost Model](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/cost-model/content.md) | How per-attempt carrier costs roll up | --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/architecture) --- # Communications & Campaigns Communications and campaigns are **optional** organizational layers above individual messages. You never need them to send, but they provide grouping, deduplication, and aggregated stats. **In the dashboard:** [Sending & Tracking Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/sending-and-tracking/content.md) shows how to browse campaigns and drill into a communication's recipients. ## How they relate [#how-they-relate] * A **communication** is a logical batch treated as one unit - a single broadcast to a list of recipients. It is the **deduplication key**: the same phone can never receive two messages with the same `communicationId`. Duplicates are rejected at the API before anything is queued. * A **campaign** groups one or more communications over time - a product launch running several sends over weeks. It has **no deduplication semantics of its own**; it exists purely for aggregation and analytics. Stats (message count, cost, delivery counts) roll up from messages to communications to campaigns - see [Stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md). ## Auto-materialization [#auto-materialization] Downstream services already have their own campaign and communication concepts with their own IDs. Outpost doesn't make them pre-register anything: callers pass `communicationId` and `campaignId` directly on the send request, and Outpost **creates the records automatically on first use**. This is called auto-materialization. ```json POST /v1/messages/sms { "items": [{ "to": "+15551234567", "body": "Hello" }], "communicationId": "broadcast-2026-q1", "campaignId": "q1-promo" } ``` The first send referencing these IDs creates the records; later sends load them. Creation is race-safe - concurrent first sends resolve to exactly one record. ### Tenant locking [#tenant-locking] An auto-materialized communication or campaign is stamped with the sending tenant's ID. From then on, **no other tenant can use that ID** - a send from a different tenant referencing it is rejected with a `409` conflict. This prevents one tenant's grouping (and its stats) from leaking into another's. Admin keys, which act across tenants, are exempt. ## Browsing and stats [#browsing-and-stats] Both entities are readable via the API and dashboard: * `GET /v1/communications` / `GET /v1/campaigns` - list with search and filters, optionally with stats (`withStats=true`) * `GET /v1/communications/:id` / `GET /v1/campaigns/:id` - details with aggregate stats (delivery rate, cost, counts) ## Design notes [#design-notes] Auto-materialization is a pragmatic bridge for existing integrations. The long-term direction is for communications and campaigns to become first-class citizens - created explicitly with richer configuration - without breaking implicit creation for current callers. ## API reference [#api-reference] * [`GET /v1/communications`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/communications/listCommunications/content.md) - list communications * [`GET /v1/communications/:communicationId`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/communications/getCommunicationDetails/content.md) - details with aggregate stats * [`GET /v1/communications/:communicationId/messages`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/messages/listCommunicationMessages/content.md) - a communication's messages * [`GET /v1/campaigns`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/campaigns/listCampaigns/content.md) - list campaigns * [`GET /v1/campaigns/:campaignId`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/campaigns/getCampaignDetails/content.md) - details with aggregate stats Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/communications-and-campaigns) --- # Cost Model Every SMS send has a real cost - a provider price plus a carrier fee, reported by AWS on the delivery receipt. Outpost tracks these costs at the finest grain and rolls them up so you can answer "what did this campaign cost?" or "what has this tenant spent this month?" ## How it relates [#how-it-relates] * **Attempt** - cost data arrives per attempt on the final delivery receipt. A message that retried twice has cost on each attempt that reached the carrier. * **Message** - `totalCost` is the sum across all attempts. Retries make a message cost more; this is visible and intentional. * **Everything above** - when a message settles, its cost is added to the `SMS_COST` stat for every relevant dimension via the [stream worker](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md#stream-worker). ## Things worth knowing [#things-worth-knowing] **Retries cost money.** Each attempt that reaches the carrier is billed. Outpost's generous retry policy (up to 3 attempts by default) trades cost for deliverability. **Footers can add segments.** Compliance footers and sender identity prefixes count toward SMS segment limits, so the assembled message may span more billable parts than the raw body implies. See [Messages - compliance footers](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md#compliance-footers-and-sender-identity). **Precision is preserved internally, rounded for display.** AWS reports sub-cent costs. Outpost stores and aggregates at full provider precision so rollups never compound rounding errors; amounts are rounded to cents only when building API responses. Attempt-level provider price/fee fields are the exception - they serialize at raw precision so you can distinguish a true $0 from a sub-cent fee. Dashboards show `< $0.01` for non-zero amounts that would round to zero. **Stats cost is in dollars.** The `SMS_COST` stat metric is denominated in dollars (not cents). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/cost-model) --- # Events Events are immutable audit log entries. They capture what happened, when, and why - for every message lifecycle change and every phone state change - enabling debugging, analytics, and compliance reporting. **In the dashboard:** a phone number's page shows its full event trail, see [Phones & the Number Pool](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/phones-and-numbers/content.md#message-and-event-history). ## How they relate [#how-they-relate] Events come in two categories: * **MESSAGE events** attach to a message and track its lifecycle from queuing through settlement. * **PHONE events** attach to a phone. Crucially, they exist so that rejections are auditable *even when no message was ever created* - a duplicate, invalid number, or opted-out rejection happens before message creation, and the phone record is where that history lives. Events are also the source for [webhook](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) fan-out: delivery attempts are recorded on the event itself, making each event the complete audit trail of both what happened and who was notified. ## What fires when [#what-fires-when] During a send, events land in two places depending on how far the item got: | Stage | Outcome | Event | Attached to | | ---------------- | ------------------------------------------------- | --------------------------------------------------------------------------------- | ----------- | | API validation | Duplicate for a communication | `DUPLICATE` | Phone | | API validation | Number invalid (landline, ...) | `INVALID_NUMBER` | Phone | | API validation | Phone opted out | `NO_SEND_OPT_OUT` | Phone | | API accept | Message queued | `QUEUED` | Message | | Send worker | AWS accepted | `PROVIDER_ACCEPTED` | Message | | Send worker | AWS rejected, retries left | `PROVIDER_FAILED` | Message | | Send worker | AWS rejected, not retryable or attempts exhausted | `FAILED` plus `PROVIDER_FAILED` or `MAX_ATTEMPTS_REACHED` | Message | | Send worker | Preflight failure (permanent) | `FAILED` plus one of `PHONE_NOT_FOUND`, `NO_SEND_OPT_OUT`, `MAX_ATTEMPTS_REACHED` | Message | | Send worker | Duplicate send dropped (attempt in flight) | `ACTIVE_ATTEMPTS` (not terminal, no settle) | Message | | Delivery receipt | Non-failure receipt, final or interim | `DELIVERED`, `BLOCKED`, `CARRIER_ROUTING`, `SENT` | Message | | Delivery receipt | Final failure, retries left | `ATTEMPT_FAILED` (message requeues, not terminal) | Message | | Delivery receipt | Final failure, attempts exhausted | `FAILED` plus `MAX_ATTEMPTS_REACHED` (settles) | Message | | Opt-out / opt-in | Any channel | `OPTED_OUT` / `OPTED_IN` | Phone | `FAILED` is always terminal and fires exactly once per message, alongside the specific reason event that explains why. `ATTEMPT_FAILED` means a single delivery attempt failed but the message still has attempts remaining - it is not terminal. Each event stores its category, type, the raw provider payload where applicable, and the time it occurred. ## Key properties [#key-properties] **Immutable.** Events are never updated or deleted (webhook delivery status, recorded on the event, is the one addition made after creation). **Non-blocking.** Event creation never fails the operation that triggered it - a hiccup writing an audit record won't stop a message from sending. **Queryable per entity.** `GET /messages/:messageId/events` and `GET /phones/:phone/events` return the history for one message or one phone. ## API reference [#api-reference] * [`GET /v1/messages/:messageId/events`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/events/getMessageEvents/content.md) - one message's event history * [`GET /v1/phones/:phone/events`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/events/getPhoneEvents/content.md) - one phone's event history Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/events) --- # Messages A message is the core entity in Outpost: a single SMS with full lifecycle tracking. Every message records its recipient, body, status, attempt history, cost breakdown, and settlement state. **In the dashboard:** [Sending & Tracking Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/sending-and-tracking/content.md) shows how to send and trace messages visually. ## How it relates [#how-it-relates] Messages sit at the bottom of the [grouping hierarchy](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/architecture/content.md#the-two-hierarchies) and are the join point of the whole system: they carry the ownership stamp (tenant/project), the grouping IDs (communication/campaign), and the recipient ([phone](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md)). ## The lifecycle [#the-lifecycle] A message crosses three system boundaries: the **API** (validation, creation, queuing), the **send worker** (submission to AWS), and the **DLR worker** (delivery receipts from the carrier network). ### Statuses [#statuses] A message is **settled** when it reaches a terminal outcome: `DELIVERED`, `FAILED`, or `BLOCKED`. `FAILED` is terminal-only: it is written exactly once, the moment a message settles as failed. A failed-but-retryable delivery attempt never sets the message's status to `FAILED`; the status stays `SENT`/`CARRIER_ROUTING` until the retry flips it back to `QUEUED`. Settlement is final - it triggers cost recording, [stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md) aggregation, and [webhook](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) delivery, and a settled message can never be re-queued. `BLOCKED` (carrier spam filters, opt-out) is never retried. ## Sending: what the API does with your request [#sending-what-the-api-does-with-your-request] `POST /messages/sms` accepts 1–100 items per request. Each item runs through a validation pipeline: 1. **Phone formatting** - the number is normalized to E.164 and a [phone record](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md) is created if new. 2. **Deduplication** - if the request has a `communicationId` and this phone already received a message for it, the item is rejected. See [Communications & Campaigns](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/communications-and-campaigns/content.md). 3. **Phone validation** - landlines and invalid numbers are rejected. 4. **Opt-out enforcement** - opted-out phones are rejected. 5. **Create & queue** - the message is created and queued for the send worker. The endpoint returns `202` with per-item outcomes: `accepted`, `rejected` (business-rule violations, each with a machine-readable `reason` like `OPTED_OUT` or `DUPLICATE`), and `errors`. One bad recipient never fails the rest of the batch. Items can carry an opaque `metadata` bag (up to 20 keys) that Outpost never interprets but echoes back on webhook deliveries - use it to correlate deliveries with your own records. ## Retries [#retries] Outpost retries generously to maximize delivery, up to 3 attempts per message by default: * **Transient provider errors** (throttling, timeouts) retry via the queue automatically - the message's status stays `QUEUED` while this happens. * **Carrier failures** reported in a delivery receipt re-queue the message if attempts remain. This fires an `ATTEMPT_FAILED` event, not `FAILED` - the message isn't done yet. * **Stale attempts** - if no delivery receipt arrives within 72 hours, the attempt is force-failed so the message can retry or settle instead of hanging forever. * **Manual retry** - `POST /messages/:messageId/retry` (also in the dashboard) creates a brand-new message with the same content. When attempts are exhausted (or a failure isn't retryable at all), the message settles: status becomes `FAILED`, and a `FAILED` event fires alongside the specific reason event (e.g. `MAX_ATTEMPTS_REACHED`). Every attempt is recorded in the message's `attemptHistory` with its own status, provider IDs, carrier info, and cost - see [Cost Model](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/cost-model/content.md). ## Compliance: footers and sender identity [#compliance-footers-and-sender-identity] Outpost automatically appends opt-out footers to satisfy TCPA/CTIA guidelines: * **LINK footer** (`Opt out: `) - added to every message sent to a country not covered by dedicated numbers, where STOP replies cannot be received. Not disableable - it's the only working opt-out path for those recipients. * **REPLY footer** (`Reply STOP to unsubscribe.`) - a periodic reminder (28-day cadence per phone) for covered countries, controlled by the sending [project's compliance settings](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md#projects). A project may also enable a **sender identity prefix** (`{identity}: {body}`) applied to every outbound message. The tenant-supplied `body` is never modified; the exact assembled text delivered to the recipient is stored separately as `sentBody` - essential in a compliance dispute. Note that footers and prefixes count toward SMS segment limits, so they can increase cost. ## Inbound messages [#inbound-messages] Messages have a direction: `OUTBOUND` or `INBOUND`. Inbound messages (replies, STOP/START/HELP keywords) are created by the [two-way worker](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md#two-way-worker) with status `DELIVERED`. ## API reference [#api-reference] * [`POST /v1/messages/sms`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/messages/sendSms/content.md) - send a batch of messages * [`POST /v1/messages/:messageId/retry`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/messages/retryMessage/content.md) - manually retry as a new message * [`GET /v1/messages/:messageId/events`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/events/getMessageEvents/content.md) - lifecycle history for one message * [`GET /v1/communications/:communicationId/messages`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/messages/listCommunicationMessages/content.md) - messages in a communication * [`GET /v1/phones/:phone/messages`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/messages/listMessagesByPhone/content.md) - messages sent to one phone Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/messages) --- # Phones & Opt-Out Outpost maintains a global **phonebook**: every phone number the system has ever seen is stored as a phone record, keyed by its E.164 form (`+15551234567`). The phone is the primary "identity actor" - messages, events, and several stats dimensions hang off it. **In the dashboard:** [Phones & the Number Pool](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/phones-and-numbers/content.md) shows how to inspect any phone's validation, opt-out state, and history. ## How it relates [#how-it-relates] **Phones are global, not tenant-scoped.** The same number is a single record shared by every tenant that messages it. This is deliberate: opt-out is a regulatory commitment to the phone's *owner*, not to a particular tenant. If a person texts STOP, no tenant - current or future - may message them again. Outpost tracks *which* tenants and projects have messaged each phone, but no tenant owns the record. ## Validation [#validation] Numbers are validated in three layers, cheapest first: 1. **Schema check** at the API edge - is it even shaped like E.164? 2. **Static parse** - is it a dialable number for its country? 3. **AWS validation** - a call to AWS End User Messaging determines carrier, type (mobile / landline / VOIP), and SMS capability. Results are cached on the record, so a valid phone never costs a second validation call. Landlines and unsupported types are marked `isValid: false` and will **never be sent to** - but the record is kept, so rejections are auditable and repeat sends don't re-pay for validation. Phones are never deleted. ## Opt-out and opt-in [#opt-out-and-opt-in] Phones are opted **in** by default. Opting out blocks all messages to that number regardless of tenant, project, or communication. | Channel | Opt out | Opt back in | | ---------------------------- | --------------------------------------------------------- | ------------------------------- | | Two-way SMS keyword | `STOP`, `UNSUBSCRIBE`, `CANCEL`, `END`, `QUIT`, `STOPALL` | `START`, `UNSTOP`, `YES` | | API (admin) | `POST /phones/:phone/opt-out` | `DELETE /phones/:phone/opt-out` | | Public messaging status page | form on `/o/:token` | same page | SMS keyword opt-in/opt-out only works for recipients in countries covered by the phone pool's dedicated numbers. For everyone else (shared routes), replies are silently lost - which is why Outpost appends an opt-out **link** footer to those messages. See [Messages - compliance footers](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md#compliance-footers-and-sender-identity). ### Enforcement is double-checked [#enforcement-is-double-checked] Opt-out is enforced twice: at the API (the item is rejected before a message record is created) and again in the send worker just before the AWS call. The second check catches the race where a phone opts out after the API accepted a message but before it was sent. ### The public messaging status page [#the-public-messaging-status-page] Every phone record carries an unguessable random token. `GET /o/:token` renders a minimal page showing the phone's current status with a button to change it; the state change only happens on `POST`, so link-prefetching bots can't opt anyone out. These are the only unauthenticated routes in the API, and they are rate-limited. ## Phone-level stats [#phone-level-stats] Each phone record accumulates its own counters - total messages, delivered/failed/blocked counts, and total cost - alongside the system-wide [stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md) dimensions (phone counts and opt-in/opt-out counts by country, tenant, and project). ## API reference [#api-reference] * [`GET /v1/phones`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/phones/listPhones/content.md) - list and search phone records * [`GET /v1/phones/:phone`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/phones/getPhoneDetails/content.md) - one phone's record and counters * [`POST /v1/phones/:phone/opt-out`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/opt-out/optOut/content.md) - opt a phone out * [`DELETE /v1/phones/:phone/opt-out`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/opt-out/optIn/content.md) - opt a phone back in * [`GET /v1/phones/:phone/events`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/events/getPhoneEvents/content.md) - a phone's event history Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/phones) --- # Stats & Analytics Stats are pre-aggregated counters that answer questions like "how many messages did this tenant deliver today?" or "what has this campaign cost?" instantly, without scanning message or phone records. Stats are a **derived read model** - messages, phones, and events remain the source of truth. If stats and raw data ever disagree, raw data wins. **In the dashboard:** every chart and stat card, from the [home page](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/content.md) down to individual campaigns and phones, is powered by these counters. ## How they relate [#how-they-relate] Stats are updated asynchronously by the [stream worker](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md#stream-worker): when a message settles or a phone changes state, the change fans out into counter increments across every relevant dimension. ## Dimensions and windows [#dimensions-and-windows] Every counter is bucketed by a **dimension** (whose number is this?) and a **window** (over what period?): | | | | ---------- | ------------------------------------------------------------------------------ | | Dimensions | `GLOBAL`, `TENANT`, `PROJECT`, `CAMPAIGN`, `COMMUNICATION`, `PHONE`, `COUNTRY` | | Windows | `HOUR`, `DAY`, `MONTH`, `ALL_TIME` | So one delivered message increments `SMS_DELIVERED_COUNT` for the global, tenant, project, campaign (if any), communication (if any), and phone dimensions - each across all four time windows. ## Metrics [#metrics] **Message metrics** (recorded when a message settles): `SMS_COUNT`, `SMS_COST`, `SMS_PARTS`, `SMS_DELIVERED_COUNT`, `SMS_FAILED_COUNT`, `SMS_BLOCKED_COUNT` - across all windows and message dimensions. **Phone metrics** (all-time only, for global/country/tenant/project): `PHONE_COUNT`, `VALID_PHONE_COUNT`, `SMS_OPT_IN_COUNT`, `SMS_OPT_OUT_COUNT`. A message only counts after it **settles** (`DELIVERED`, `FAILED`, or `BLOCKED`) - in-flight messages don't appear in stats, and a message that racks up multiple failed attempts in its history before settling is still counted exactly once. ## Accuracy guarantees [#accuracy-guarantees] Recording is *at-least-once with a bounded, rare over-count*: no increment is ever lost, and two layers of idempotency protection keep queue redeliveries and concurrent workers from double-counting. Dimensions can briefly lag each other by a few in-flight operations. For accounting-grade numbers, the raw message records are authoritative. ## Reading stats [#reading-stats] * **Curated overview endpoints** power the dashboard: per-tenant, per-project, per-communication, and per-campaign overviews with totals and zero-filled chart series, plus message-volume, phones-by-country, and top-communications/campaigns endpoints. * **`POST /v1/stats/query`** (admin) is the free-form endpoint: fetch one value, a time series for one dimension value, an all-time total, or one metric across many dimension values (e.g. top tenants by cost). ## API reference [#api-reference] * [`POST /v1/stats/query`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/stats/queryStats/content.md) - free-form stats queries (admin) * [`GET /v1/stats/global/overview`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/stats/getGlobalStatsOverview/content.md) - system-wide overview * [`GET /v1/stats/tenants/:tenantId/overview`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/stats/getTenantStatsOverview/content.md) - per-tenant overview * [`GET /v1/stats/projects/:projectId/overview`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/stats/getProjectStatsOverview/content.md) - per-project overview Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/stats) --- # Tenants & Projects Tenants and projects form the **ownership hierarchy**: they answer "who is sending, and with what configuration?" **In the dashboard:** [Managing Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/tenants-and-projects/content.md) walks through creating and configuring them. ## How they relate [#how-they-relate] * A **tenant** is an organization using Outpost. All other data - projects, messages, campaigns, stats - is scoped under a tenant. * A **project** is a configuration container under a tenant. It holds [API keys](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/api-keys/content.md), [webhook configs](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md), and SMS compliance preferences. A tenant needs at least one project to generate an API key and start sending. * Both are **stats dimensions** - you can ask "how much has this tenant/project spent this month?" See [Stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md). ## Tenants [#tenants] A tenant is intentionally minimal: an ID, a name, and a status (`active` / `disabled` / `deleted`). **Status controls access immediately.** A tenant's status directly controls whether its API keys work. Disabling a tenant flips a denormalized `tenantIsActive` flag on every one of its keys, so requests are rejected with `401` on the very next call - without slowing down authentication. **Deletion is soft.** Deleting a tenant marks it `deleted` and deactivates its keys; the record and all its data stay in the database for auditing. Deleted tenants disappear from all reads. Restoring one requires developer intervention - there is no API for it. ## Projects [#projects] Projects are Outpost's **configuration boundary**. Unlike communications and campaigns (which downstream services own and Outpost [auto-materializes](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/communications-and-campaigns/content.md#auto-materialization)), projects are always created explicitly because they require deliberate configuration choices. A project owns: * **API keys** - up to 10 per project. See [API Keys](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/api-keys/content.md). * **Webhook configs** - up to 10 per project. See [Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md). * **Compliance settings** - whether sends include periodic "Reply STOP" reminder footers (`optOutRemindersEnabled`, default on) and an optional sender identity prefix prepended to every outbound message (`identityEnabled` + `identity`, max 20 characters). How these drive message assembly is covered in [Messages - compliance footers](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md#compliance-footers-and-sender-identity). **Limits are enforced atomically.** A tenant can have at most 20 projects, enforced with an atomic conditional counter rather than a count query, so concurrent creates cannot slip past the cap. Soft-deleting a project frees a slot. **Status cascades like tenants.** Disabling or deleting a project deactivates its API keys via the same denormalized-flag mechanism. ## Design notes [#design-notes] * **Why soft-delete?** Tenants and projects are roots of large resource trees. Hard deletion would require cascading deletes across messages, stats, and events - expensive and risky. Soft-delete preserves audit data while revoking access instantly. * **Future direction for tenants:** tenant management is expected to move upstream to the LXS Directory Service, at which point Outpost will auto-materialize tenant records on first reference. The minimal data model anticipates this. ## API reference [#api-reference] * [`POST /v1/tenants`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/tenants/createTenant/content.md) - create a tenant * [`GET /v1/tenants`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/tenants/listTenants/content.md) - list tenants * [`POST /v1/tenants/:tenantId/projects`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/createProjectForTenant/content.md) - create a project under a tenant * [`GET /v1/projects`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/listProjects/content.md) - list projects * [`PATCH /v1/projects/:projectId`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/updateProject/content.md) - update a project (status, compliance settings) Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/tenants-and-projects) --- # Webhooks Webhooks forward Outpost [events](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/events/content.md) to downstream systems through HTTP callbacks. Instead of polling for message outcomes, your service registers an endpoint and Outpost pushes notifications - message delivered, message failed, phone opted out - as they happen. **In the dashboard:** webhook configurations are managed per project, see [Managing Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/tenants-and-projects/content.md#webhook-configuration). ## How they relate [#how-they-relate] Webhook configs live on [projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md) - the same boundary that owns API keys and produced the traffic. Each config declares a destination URL (HTTPS only), optional auth (Bearer or Basic), custom headers, and an explicit list of subscribed event types. ## What you can subscribe to [#what-you-can-subscribe-to] * **Message events** - the full lifecycle: `QUEUED`, `PROVIDER_ACCEPTED`, `DELIVERED`, `ATTEMPT_FAILED`, `FAILED`, `BLOCKED`, and the rest. `ATTEMPT_FAILED` fires when a delivery attempt fails but the message still has attempts remaining; `FAILED` fires exactly once, when the message settles as failed for good. * **Phone events** - only `OPTED_OUT` and `OPTED_IN` are webhook-eligible. Because phones are [global](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md), an opt-out is delivered to every project that has messaged that phone (each project's configs are evaluated independently). Subscriptions are explicit: a config with no subscriptions receives nothing. ## The delivery request [#the-delivery-request] Deliveries are `POST` requests with a versioned JSON payload containing the event fields plus fresh context - a `message` object (status, `isSettled`, cost, attempt count, your `metadata` bag) for message events, or a `phone` object (opt-out state, validation state, carrier) for phone events. `isSettled` lets you tell a terminal `FAILED` apart from an in-progress retry without re-deriving it from `status` alone. Every delivery carries identifying headers, including: | Header | Use | | --------------------------- | ---------------------------------------- | | `x-outpost-event` | The event type - route on this | | `x-outpost-delivery-id` | Unique per attempt - deduplicate on this | | `x-outpost-project-id` | Which project's config produced this | | `x-outpost-payload-version` | Payload contract version (currently `1`) | These header names are reserved and cannot be spoofed by custom header config. Set `metadata` on your send request (`items[].metadata`) - Outpost never interprets it, but echoes it back inside the `message` context on every webhook delivery for that message. ## Delivery semantics [#delivery-semantics] * A 2xx response marks the delivery `SUCCEEDED`; anything else marks it `FAILED` with the status code recorded. * Every attempt is persisted on the event record, so the dashboard can show exactly what was delivered where. * Failed deliveries are **not currently retried automatically** - design your endpoint to be available, and treat webhook data as a notification layer over the authoritative API. * A config can be **paused** without deleting it (`endpoint.isActive: false`); paused configs are skipped at fan-out time. ## Managing configs [#managing-configs] Webhook configs are managed as project subresources: `GET`/`POST /v1/projects/:projectId/webhook-configs` and `GET`/`PUT`/`PATCH`/`DELETE` on `/v1/projects/:projectId/webhook-configs/:webhookConfigId`. The outbound payload shape is also documented in the OpenAPI document's `webhooks` section. ## API reference [#api-reference] * [`GET /v1/projects/:projectId/webhook-configs`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/getProjectWebhookConfigs/content.md) - list a project's configs * [`POST /v1/projects/:projectId/webhook-configs`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/addProjectWebhookConfig/content.md) - add a config * [`PATCH /v1/projects/:projectId/webhook-configs/:webhookConfigId`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/patchProjectWebhookConfigById/content.md) - update (e.g. pause) a config * [`DELETE /v1/projects/:projectId/webhook-configs/:webhookConfigId`](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/projects/deleteProjectWebhookConfigById/content.md) - remove a config * [Outbound webhook payload](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/webhooks/outpost-event/content.md) - the exact shape Outpost POSTs to your endpoint Calling from TypeScript? See [Generating a Typed Client](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/developers/typed-client/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/webhooks) --- # Workers & Background Processing Almost everything interesting in Outpost happens asynchronously. The API responds in milliseconds; five workers, connected by queues, do the actual work. ## How they relate [#how-they-relate] Each worker is a pure function processing queue messages. In production they run as Lambda functions triggered by SQS; for local development, long-running **daemons** poll the same queues and invoke the same handlers, so behavior matches production. ## The five workers [#the-five-workers] ### Send worker [#send-worker] Takes queued messages and submits them to AWS End User Messaging. Before every send it runs preflight checks - does the phone exist, is it opted out, are attempts left, is another attempt still in flight? - and settles the message as `FAILED` if a permanent check fails. The in-flight check is the exception: a duplicate send is dropped without settling, since the pending delivery receipt decides the outcome. Provider errors are classified as retryable (queue-level retry) or fatal. See [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md#retries). ### DLR worker [#dlr-worker] Processes **delivery receipts** (DLRs) - the carrier network's verdict on each send, arriving via SNS/SQS up to 72 hours later. It records an [event](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/events/content.md) for every receipt and updates the attempt with cost and carrier data on final receipts, then decides the message's fate: settle it (as `DELIVERED`, `BLOCKED`, or `FAILED` once attempts are exhausted or the message has become permanently unsendable, such as an opt-out arriving mid-flight), or requeue for retry. A failed-but-retryable receipt fires `ATTEMPT_FAILED` and requeues without ever setting the message's status to `FAILED`; only the final, no-more-retries outcome writes `FAILED` and fires the terminal `FAILED` event. ### Two-way worker [#two-way-worker] Processes **inbound SMS**. Every inbound message is recorded (direction `INBOUND`). Keyword replies drive opt-out state: * **STOP** (and `UNSUBSCRIBE`, `CANCEL`, `END`, `QUIT`, `STOPALL`) - opts the [phone](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md) out, optionally sending a confirmation first. * **START** (`UNSTOP`, `YES`) - opts the phone back in with a confirmation. * **HELP** (`INFO`) - sends usage instructions. Confirmation messages bypass opt-out checks (they must be sendable while the phone is still opted out) and never receive compliance footers. ### Stream worker [#stream-worker] Listens to DynamoDB streams - every write to any entity flows through it. It routes changes to the owning entity's logic, which turns them into [stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md) increments and [webhook](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) fan-out. Idempotency layers ensure redelivered stream records never double-count. ### Webhook worker [#webhook-worker] Executes one webhook delivery attempt at a time: loads the event, builds the payload with fresh message/phone context, POSTs to the configured endpoint, and records the outcome on the event. ## Infrastructure notes [#infrastructure-notes] * **Phone pools are shared infrastructure** - provisioned outside application environments (number registration is slow) and supplied via Vault. Pool country coverage drives the compliance LINK footer decision - see [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md#compliance-footers-and-sender-identity). * **Delivery receipts are environment-owned** - each environment has its own configuration set and DLR queue, so receipts route back to the environment that sent the message. * **Two-way topics are pool-level** - environments sharing a pool all receive the same inbound STOP, so only one environment should send confirmations. --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/concepts/workers) --- # Dashboard Overview The Outpost dashboard is a web console for **Outpost administrators**. Where the API gives each tenant a scoped view of their own data, the dashboard shows the whole platform: every tenant, project, campaign, phone number, and dollar spent, in one place. It runs at `dashboard..outpost.24g.dev` (for example `dashboard.prod.outpost.24g.dev`) and signs you in with your 24G single sign-on account. The dashboard home page with system-wide stat cards and activity charts ## What each section covers [#what-each-section-covers] The left navigation mirrors the platform's core concepts: | Section | What you do there | Concept behind it | | ------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------- | | **Dashboard** | System-wide stats, recent activity, and a Quick Send form | [Stats & Analytics](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md) | | **Campaigns** | Browse campaign folders and their aggregate performance | [Communications & Campaigns](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/communications-and-campaigns/content.md) | | **Communications** | Review individual broadcast sends and their recipients | [Communications & Campaigns](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/communications-and-campaigns/content.md) | | **Phone Numbers** | Inspect any recipient phone: validation, opt-out state, history | [Phones & Opt-Out](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md) | | **Number Pool** | View the AWS number pool Outpost sends from | [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md) | | **Tenants** | Create and manage tenants, projects, API keys, and webhooks | [Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md) | ## The home page [#the-home-page] The home page is a live pulse on outbound activity: * **Stat cards**: all-time totals for messages, delivered, failed, blocked, cost, and known phone numbers. * **Outpost Activity**: message volume and cost charts for a selected window, switchable between By Day and By Month. * **Send Activity**: the most recently active communications and campaigns across the system, each linking to its detail page. * **Audience Health**: opt-out rate across all phones and a summary of the number pool. ## How-to guides [#how-to-guides] * [Managing Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/tenants-and-projects/content.md): create tenants and projects, issue API keys, configure compliance and webhooks. * [Sending & Tracking Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/sending-and-tracking/content.md): use Quick Send and follow a broadcast down to each recipient. * [Phones & the Number Pool](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/phones-and-numbers/content.md): inspect a phone's full history and the sending-number inventory. --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/dashboard) --- # Phones & the Number Pool The dashboard gives you two phone-related views: **Phone Numbers** (the recipients Outpost has messaged, and their opt-out state) and **Number Pool** (the AWS-provisioned numbers Outpost sends *from*). The underlying model is covered in [Phones & Opt-Out](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md). ## Phone Numbers [#phone-numbers] The landing page summarizes the phone inventory by messaging permission state (total, active, opted out) with the system-wide opt-out rate, above a table of every known phone. You can search by number and filter by status, country, or tenant. Each row shows which tenant(s) the phone belongs to and its current opt-in/opt-out badge. ## Inside a phone number [#inside-a-phone-number] A phone number detail page with validation, carrier, and location A phone's page answers "can we message this number, and what happened when we did": * **Opt-in / opt-out badge** and last activity, next to the number itself. * **Validation**: whether the number passed validation and when it was last checked. * **Carrier and type**: for example Verizon Wireless / MOBILE, or a VOIP line, which affects deliverability. * **Location**: country and region. * **Message Volume**: daily delivered/failed/blocked outcomes for this number. ### Message and event history [#message-and-event-history] Message History and Event History tables on a phone page Below the charts are two tables: * **Message History**: every message sent to this number, with direction, body preview, status, and the sending number. * **Event History**: the raw event trail (`QUEUED`, `SENT`, `PROVIDER_ACCEPTED`, `DELIVERED`, `FAILED`, and so on) with each event's payload details. A webhook icon on a row means that event was pushed to a subscriber; see [Events](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/events/content.md) and [Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md). This is the best first stop when someone asks "why didn't my text arrive": the event trail shows exactly how far the message got. ## Number Pool [#number-pool] The Number Pool page showing pool configuration The Number Pool page is a read-only view of the AWS End User Messaging pool Outpost sends from: the pool ID and ARN, message type, whether two-way messaging and shared routes are enabled, self-managed opt-outs, and deletion protection. Below the configuration is the list of origination identities (the actual sending numbers) by country and capability. This page is diagnostic rather than configurable: the pool is managed in AWS infrastructure, not from the dashboard. --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/dashboard/phones-and-numbers) --- # Sending & Tracking Messages The dashboard lets you send messages directly and, more importantly, trace what happened to every send: from a campaign, to a communication (one broadcast), to the individual message for each recipient. The grouping model is explained in [Communications & Campaigns](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/communications-and-campaigns/content.md). ## Quick Send [#quick-send] The home page ends with a **Quick Send** form, an admin shortcut for one-off or test sends without touching the API: The Quick Send form on the dashboard home page 1. Pick the **tenant** and **project** the send should be scoped to. 2. Optionally attach it to an existing **campaign** and **communication** for grouping. 3. Add up to **100 recipients** (enter, tab, comma, or paste to add) and a message up to **1600 characters**. ## Campaigns [#campaigns] The Campaigns page ranks campaigns by message count and cost, with a searchable table showing each campaign's tenant and its aggregate recipients, delivered, failed, and blocked counts. Campaigns are folders: click one to see the communications inside it. The Campaigns page with top-performer charts and campaign table ### Inside a campaign [#inside-a-campaign] A campaign's page shows the accumulative activity stats at the top of all communications associated with it and a communication table at the bottom The Campaigns page with top-performer charts and campaign table ## Communications [#communications] The Communications page is the same view one level down: every broadcast send across all tenants, filterable by tenant and searchable by ID. The Communications page listing broadcast sends ### Inside a communication [#inside-a-communication] A communication's page shows the outcome of that one broadcast: total recipients, delivered/failed/blocked counts with rates, total cost, and hour-by-hour delivery activity charts. At the bottom, the **recipients table** lists every phone number in the blast with its status, billable message parts, cost, and send time: Clicking a recipient opens that phone number's page, where you can see the full [message and event history](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/dashboard/phones-and-numbers/content.md) including webhook deliveries. Costs shown here roll up from per-part message pricing; see [Cost Model](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/cost-model/content.md). --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/dashboard/sending-and-tracking) --- # Managing Tenants & Projects Long term Tenant management will be handled by Drive Core V2 platform and Outpost will just associate configuration with that Tenant. But at the time of the initial rewrite, said functionality wasn't ready. Ideally the Drive Core v2 Directory Service would also be in charge of authentication. Everything an administrator does to onboard and configure a customer happens under **Tenants** in the dashboard. The hierarchy matches the platform model: a tenant contains projects, and each project has its own API keys, compliance settings, and webhook configurations. See [Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md) for the concept itself. ## The Tenants page [#the-tenants-page] The Tenants page with per-tenant charts and the tenant table The landing page shows how messages, cost, and phones are distributed across tenants, plus a searchable table of every tenant with its status. From here you can: * **Create a tenant** with the button in the top right. * **Filter** by name or by Active/Disabled status. * **Open a tenant** by clicking its row. ## Inside a tenant [#inside-a-tenant] A tenant's page shows its all-time totals (messages, cost, phones, opt-outs), activity charts, and recent sends. The **Disable Tenant** button suspends the tenant, which blocks its API keys from sending. The Tenants page with per-tenant charts and the tenant table At the bottom you'll see **Send Activity** for recent Communications and Campaigns for the given tenant along with the **Projects** table, with each project's status and how many webhook configurations it has: The Projects section of a tenant page Tenants are limited to 20 projects. **Create Project** adds one; clicking a row opens it. ## Inside a project [#inside-a-project] A project page repeats the stats and activity views scoped to that project, API Access, Compliance, and Webhook Configuration sections of a project then gets to the three things you actually configure: API Access, Compliance, and Webhook Configuration sections of a project ### API Access [#api-access] Each project can have up to 10 API keys. The table shows each key's name, a masked value, when it was last used, and its expiry. **Create Key** shows the plaintext key **once**, at creation time; after that only the masked form is visible, so copy it immediately. The trash icon revokes a key. Keys created here are tenant-scoped. See [API Keys & Authentication](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/api-keys/content.md) for how scoping and roles work. ### Compliance [#compliance] Two per-project compliance toggles, both explained in depth in [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md): * **Periodic opt-out reminders**: appends a "Reply STOP to unsubscribe" footer roughly every 4 weeks per recipient, following TCPA/CTIA guidance. The reminder clock is shared across all projects messaging a number, so another project's send may satisfy the reminder. Recommended to keep enabled. * **Sender identity prefix**: prepends a short brand identifier to every message, for example "Acme: Your code is 123456". Messages sent to countries outside the phone pool's coverage always include an opt-out link; that cannot be disabled, because STOP replies are not received over shared routes. ### Webhook Configuration [#webhook-configuration] Each project can have up to 10 webhook configurations. Each one shows its destination URL, HTTP method, auth mode, timeout, and the event types it subscribes to (for example `DELIVERED`, `FAILED`, `BLOCKED`, `OPTED_OUT`, `OPTED_IN`). The row controls let you pause, edit, or delete a configuration. API Access, Compliance, and Webhook Configuration sections of a project See [Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md) for delivery semantics, retries, and payload shape. --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/dashboard/tenants-and-projects) --- # Generating a Typed Client The Outpost API is described by an OpenAPI 3.1 document, generated from the same route schemas the server enforces at runtime. That means you never have to hand-write request or response types: generate them from the spec and they are correct by construction. The API serves its own spec at `GET /v1/openapi.json` (no authentication required), so you can generate against any environment you can reach. ## 1. Generate types with openapi-typescript [#1-generate-types-with-openapi-typescript] [openapi-typescript](https://openapi-ts.dev/) converts the spec into a single `.ts` file of types, with zero runtime code. ```bash npm install -D openapi-typescript typescript npx openapi-typescript "$OUTPOST_URL/v1/openapi.json" -o ./src/outpost-api.ts ``` Re-run the command whenever the API changes; the diff of the generated file shows you exactly what changed in the contract. ## 2. Call the API with openapi-fetch [#2-call-the-api-with-openapi-fetch] [openapi-fetch](https://openapi-ts.dev/openapi-fetch/) is a tiny (\~6 kB) fetch wrapper that consumes those types. Paths, methods, path/query parameters, request bodies, and response shapes are all checked at compile time. ```bash npm install openapi-fetch ``` ```typescript import createClient from 'openapi-fetch'; import type { paths } from './outpost-api'; const api = createClient({ baseUrl: process.env.OUTPOST_URL, headers: { 'x-api-key': process.env.OUTPOST_API_KEY }, }); // Fully typed: TypeScript knows this endpoint, its body shape, and its 202 response. const { data, error } = await api.POST('/v1/messages/sms', { body: { items: [{ to: '+15551234567', body: 'Hello from Outpost!' }], communicationId: 'welcome-blast-1', }, }); if (error) { // `error` is the typed error response body. console.error('Send failed:', error); } else { // `data` is the typed 202 payload with per-item outcomes. console.log('Accepted:', data.accepted, 'Rejected:', data.rejected); } ``` Every request returns `{ data, error }` rather than throwing, and both sides are typed from the spec. A typo in a path, a missing required field, or reading a property that does not exist on the response is a compile error, not a production bug. Authentication is a project-scoped API key in the `x-api-key` header. See [API Keys & Authentication](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/api-keys/content.md) for roles and scoping. ## Inside the Outpost monorepo [#inside-the-outpost-monorepo] The dashboard follows the same generate-from-spec path as any other TypeScript consumer. Types are produced from the committed document at `packages/api/openapi.json` (`npm run generate:api` in `packages/dashboard`, hooked to `predev` / `prebuild`). After changing an API route schema, regenerate the committed document first: ```bash npm run generate:openapi -w @outpost/api ``` ## Other languages [#other-languages] The spec is a standard OpenAPI 3.1 document, so any OpenAPI generator works, for example [openapi-generator](https://openapi-generator.tech/) for Python, Go, Java, and others: ```bash openapi-generator generate -i "$OUTPOST_URL/v1/openapi.json" -g python -o ./outpost-client ``` ## Browsing the contract [#browsing-the-contract] The full endpoint-by-endpoint contract, with schemas and error responses, lives in the [API Reference](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/api/content.md) section of these docs, generated from the same OpenAPI document. --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/developers/typed-client) --- # Glossary | Term | Definition | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Message** | A single SMS with full lifecycle tracking - body, recipient, status, attempt history, cost. The smallest unit in the grouping hierarchy. See [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md). | | **Attempt** | One try at sending a message through AWS. A message can have multiple attempts (retries), each with its own status and cost. | | **Communication** | A logical batch of messages treated as one unit. The deduplication key: the same phone cannot receive two messages with the same `communicationId`. See [Communications & Campaigns](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/communications-and-campaigns/content.md). | | **Campaign** | A group of one or more communications over time. Purely aggregative - no deduplication semantics of its own. | | **Phone** | A global phone number record (E.164) with validation status, opt-out status, carrier info, and aggregate stats. Shared by all tenants. See [Phones & Opt-Out](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/phones/content.md). | | **Tenant** | An organization using Outpost. The top-level ownership unit - all data and stats are scoped under a tenant. See [Tenants & Projects](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/tenants-and-projects/content.md). | | **Project** | A configuration container under a tenant: API keys, webhook configs, and compliance settings. Also a stats dimension. | | **API key** | Credential for the REST API, sent in `x-api-key`. Admin keys have full access; tenant keys are scoped to one tenant and project. See [API Keys](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/api-keys/content.md). | | **Event** | An immutable audit log entry tracking a message lifecycle change or phone state change. See [Events](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/events/content.md). | | **Stat** | A pre-aggregated counter (by dimension and time window) powering fast analytics. See [Stats](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/stats/content.md). | | **Webhook** | An HTTP callback configured on a project that forwards subscribed events to an external endpoint. See [Webhooks](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/webhooks/content.md). | | **Settled** | A message's terminal state - `DELIVERED`, `FAILED`, or `BLOCKED`. `FAILED` is written exactly once, at the moment a message settles as failed; it never appears as an intermediate, retryable state. Settlement triggers cost recording, stats, and webhooks, and is irreversible. | | **Auto-materialization** | Outpost creating a communication, campaign, or phone record automatically the first time it is referenced, instead of requiring pre-registration. | | **DLR** | Delivery receipt - the carrier network's asynchronous verdict on a send, processed by the [DLR worker](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md#dlr-worker). | | **Two-way SMS** | Inbound replies from recipients, including STOP/START/HELP keyword handling. See [Workers - two-way worker](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/workers/content.md#two-way-worker). | | **Opt-out footer** | Compliance text appended to outbound messages - either a "Reply STOP" reminder or an opt-out link. See [Messages](https://docs.pro-1139.outpost.24g.dev/llms.mdx/docs/concepts/messages/content.md#compliance-footers-and-sender-identity). | | **Shared route** | An AWS origination identity used when the destination country has no dedicated number in the pool. Shared routes cannot receive replies, so opt-out link footers are mandatory there. | | **E.164** | The international phone number format (`+15551234567`) used as the phone record's natural key. | --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/reference/glossary) --- # Complete the SSO login flow {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/auth/authCallback) --- # Start the SSO login flow {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/auth/authLogin) --- # Clear the SSO session cookie {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/auth/authLogout) --- # Return the identity of the current SSO session {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/auth/authMe) --- # Get details for a specific campaign {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/campaigns/getCampaignDetails) --- # Get a paginated list of campaigns {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/campaigns/listCampaigns) --- # Get details for a specific communication {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/communications/getCommunicationDetails) --- # Get a paginated list of communications {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/communications/listCommunications) --- # Get a single event by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/events/getEvent) --- # Get all events for a specific message {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/events/getMessageEvents) --- # Get all events for a specific phone number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/events/getPhoneEvents) --- # Get a single message by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/messages/getMessage) --- # Get messages for a specific communication {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/messages/listCommunicationMessages) --- # Get messages for a specific phone number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/messages/listMessagesByPhone) --- # Retry a failed message {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/messages/retryMessage) --- # Enqueue SMS messages {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/messages/sendSms) --- # Opt a phone number back in to receiving messages {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/opt-out/optIn) --- # Opt a phone number out of receiving messages {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/opt-out/optOut) --- # Get the full ISO 3166-1 alpha-2 country list for phone filtering {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/phones/getPhoneCountries) --- # Get details for a specific phone number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/phones/getPhoneDetails) --- # Get a paginated list of phone numbers {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/phones/listPhones) --- # Validate a phone number without adding it to the phone book {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/phones/validatePhone) --- # Get the origination pool and its summary {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/pool/getPool) --- # List numbers in the origination pool {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/pool/getPoolNumbers) --- # Add a webhook config to a project {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/addProjectWebhookConfig) --- # Create an API key for a project {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/createProjectApiKey) --- # Create a new project under a tenant {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/createProjectForTenant) --- # Delete a project {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/deleteProject) --- # Delete an API key from a project {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/deleteProjectApiKey) --- # Delete a webhook config {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/deleteProjectWebhookConfigById) --- # Get details for a specific project {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/getProjectById) --- # Get a single webhook config by id {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/getProjectWebhookConfigById) --- # List a project's webhook configs {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/getProjectWebhookConfigs) --- # List a project's API keys {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/listProjectApiKeys) --- # Get a paginated list of projects {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/listProjects) --- # Partially update a webhook config {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/patchProjectWebhookConfigById) --- # Replace a webhook config {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/replaceProjectWebhookConfigById) --- # Update a project {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/projects/updateProject) --- # Get activity dashboard statistics {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getActivityStats) --- # Get a campaign's stats overview {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getCampaignStatsOverview) --- # Get a communication's stats overview {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getCommunicationStatsOverview) --- # Get the outpost-wide stats overview {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getGlobalStatsOverview) --- # Get message volume over a time range {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getMessageVolume) --- # Get phone counts grouped by country {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getPhonesByCountry) --- # Get a project's stats overview {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getProjectStatsOverview) --- # Get a tenant's stats overview {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getTenantStatsOverview) --- # Get top campaigns by metric {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getTopCampaigns) --- # Get top communications by metric {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/getTopCommunications) --- # Run a free-form stats query {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/stats/queryStats) --- # Create a new tenant {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/tenants/createTenant) --- # Delete a tenant {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/tenants/deleteTenant) --- # Get details for a specific tenant {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/tenants/getTenantById) --- # Get a paginated list of tenants {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/tenants/listTenants) --- # Update a tenant {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/tenants/updateTenant) --- # Outpost event delivery {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- [View this page](https://docs.pro-1139.outpost.24g.dev/docs/api/webhooks/outpost-event)