# Invitations

## Send an invitation

**post** `/v1/invitations`

Sends a templated invitation to a phone number: the one way to start a
conversation with a customer who has not messaged you first.

**Requirements**

- `Idempotency-Key` header: required. This is business-initiated
  contact, and a blind retry must never send two.

**Behavior**

- Refusals are checked in a fixed order, most actionable first:
  validation, then idempotency, then template authorization, then
  backlog admission, then chat state, then consent.
- HTTP 429 `code` 1002 is admission control on your
  backlog, not a rate limit: your queued-but-unsent invitations plus
  this one would take longer to deliver than the delivery horizon
  allows, so the request is refused rather than queued: an invitation
  delivered hours late could go out against consent revoked in the
  meantime. `Retry-After` reflects when your backlog will actually
  clear and can legitimately be hours; retrying sooner is refused
  again. Nothing is queued and no key is recorded, so the same key
  succeeds on retry.
- A backlog refusal is checked before the chat-state and consent gates
  (validation still runs first): a recipient who has also opted out
  still fails with the consent error once there is room.

**Errors**

- HTTP 403: `invitation_card_not_granted` (your brand is no longer
  approved for the card shape the template renders), `consent_required`
- HTTP 409: `idempotency_key_reused`, `idempotency_key_processing`,
  `chat_superseded`, `chat_closed`
- HTTP 422: field-level validation; `unknown_template`;
  `field_removed` if the request still carries the retired
  `parameters` or `brand_logo_att_id` fields
- HTTP 429: `backlog_horizon_exceeded`, with `Retry-After`

### Header Parameters

- `"Idempotency-Key": string`

### Body Parameters

- `reference_id: string`

  REQUIRED correlation handle: an order number, case id or similar. Apple bounds its length and does not allow quotes. Echoed on the delivery webhooks.

- `template_id: string`

  One of YOUR invitation template ids (`invt_…`, from `POST /v1/invitation_templates`): the card's name and logo come from the template, and the platform picks the right card from whether it carries a logo. Unknown returns HTTP 422 `code` 1081.

- `to: string`

  The customer's phone number, bare E.164 (partner formatting tolerated); malformed returns HTTP 422 `code` 1071.

- `locale: optional string`

  Optional locale for the card rendering.

### Returns

- `id: string`

  Invitation id (`inv_…`).

- `chat_id: string`

  The `tel`-handled chat carrying the invitation delivery; NOT addressable for organic sends.

- `delivery: "queued"`

  - `"queued"`

- `message_id: string`

  The invitation delivery's message id; poll it via GET /v1/messages/{msg}.

- `to: string`

  The canonical `tel:+E164` handle form of the request's `to`.

### Example

```http
curl https://messages.api.linqapp.com/v1/invitations \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY" \
    -d '{
          "reference_id": "ride-88214",
          "template_id": "invt_a4f2c718d0",
          "to": "+15555550142"
        }'
```

#### Response

```json
{
  "id": "inv_6b2e04",
  "chat_id": "chat_4f81b2",
  "message_id": "msg_2c7d90",
  "to": "tel:+15555550142",
  "delivery": "queued"
}
```

## Get an invitation

**get** `/v1/invitations/{inv}`

Retrieves an invitation's status: delivery, the customer's response, and the chat it opened. Unknown ids, and another brand's; return HTTP 404.

### Path Parameters

- `inv: string`

### Returns

- `id: string`

  Invitation id (`inv_…`).

- `chat_id: string`

- `created_at: string`

- `delivery: string`

  The underlying message's delivery state; same values as GET /v1/messages/{msg}.

- `opted_out: boolean`

  Derived from the consent record: a later tel STOP flips it. Independent of `response`.

- `parameters: unknown`

  The parameters as sent (JSON `null` when none).

- `reference_id: string`

- `response: string`

  `none`, `accepted`, `declined`, or `superseded` (an outstanding card on a thread a sibling's accept superseded).

- `response_source: string`

  How `response` was resolved: `tap`; Apple forwarded the customer's own
  interaction and we correlated it by the card's `requestIdentifier`;
  `inference`; we concluded it from message ordering (a reply on the thread
  while the card was outstanding), which a later tap may correct; `none`;
  unresolved, and for the derived `superseded` response, which is a fact
  about the chat rather than an answer from the customer.

  Reconcile on this when a deduced decline must not be treated like an
  answered one.

- `template_id: string`

- `accepted_chat_id: optional string`

  The opaque chat the customer landed in; this card's accept, or the sibling accept that superseded it.

- `locale: optional string`

  Omitted when the send carried none.

### Example

```http
curl https://messages.api.linqapp.com/v1/invitations/$INV \
    -H "Authorization: Bearer $LINQ_AMB_API_KEY"
```

#### Response

```json
{
  "id": "inv_6b2e04",
  "chat_id": "chat_4f81b2",
  "template_id": "invt_a4f2c718d0",
  "reference_id": "ride-88214",
  "locale": "en-US",
  "parameters": {
    "brandName": "Acme Rides"
  },
  "delivery": "sent",
  "response": "accepted",
  "response_source": "customer",
  "accepted_chat_id": "chat_4f81b2",
  "opted_out": false,
  "created_at": "2026-08-06T13:58:02Z"
}
```

## Domain Types

### Invitation Create Response

- `InvitationCreateResponse object { id, chat_id, delivery, 2 more }`

  The accepted invitation. A replay of the same Idempotency-Key within 24h returns these exact bytes; past that window the key is forgotten and the request executes again.

  - `id: string`

    Invitation id (`inv_…`).

  - `chat_id: string`

    The `tel`-handled chat carrying the invitation delivery; NOT addressable for organic sends.

  - `delivery: "queued"`

    - `"queued"`

  - `message_id: string`

    The invitation delivery's message id; poll it via GET /v1/messages/{msg}.

  - `to: string`

    The canonical `tel:+E164` handle form of the request's `to`.

### Invitation Retrieve Response

- `InvitationRetrieveResponse object { id, chat_id, created_at, 9 more }`

  The composed invitation status; every field derived at read time from the table that owns it, so nothing can drift.

  - `id: string`

    Invitation id (`inv_…`).

  - `chat_id: string`

  - `created_at: string`

  - `delivery: string`

    The underlying message's delivery state; same values as GET /v1/messages/{msg}.

  - `opted_out: boolean`

    Derived from the consent record: a later tel STOP flips it. Independent of `response`.

  - `parameters: unknown`

    The parameters as sent (JSON `null` when none).

  - `reference_id: string`

  - `response: string`

    `none`, `accepted`, `declined`, or `superseded` (an outstanding card on a thread a sibling's accept superseded).

  - `response_source: string`

    How `response` was resolved: `tap`; Apple forwarded the customer's own
    interaction and we correlated it by the card's `requestIdentifier`;
    `inference`; we concluded it from message ordering (a reply on the thread
    while the card was outstanding), which a later tap may correct; `none`;
    unresolved, and for the derived `superseded` response, which is a fact
    about the chat rather than an answer from the customer.

    Reconcile on this when a deduced decline must not be treated like an
    answered one.

  - `template_id: string`

  - `accepted_chat_id: optional string`

    The opaque chat the customer landed in; this card's accept, or the sibling accept that superseded it.

  - `locale: optional string`

    Omitted when the send carried none.
