Skip to content
Linq

Invitations

An invitation is how you start a conversation with a customer who hasn’t messaged you yet. It’s a branded card sent to their phone number, not a free-text message.

Send: POST /v1/invitations with to, template_id and a reference_id (your order number, case id or similar, echoed on the delivery webhooks). An Idempotency-Key header is required, so a retry never sends a second card.

Send in bulk: POST /v1/invitation_batches takes up to 10,000 recipients on one template, and we check every recipient when you submit, before anything goes out. A batch can be cancelled for anyone not yet reached.

Setup, once: create an invitation template: the brand name printed on the card, and optionally your logo, uploaded in the same request:

POST /v1/invitation_templates
Content-Type: multipart/form-data

name = "Acme Rides"
logo = <PNG file, optional>

The response carries the template’s id (invt_…). A template with a logo sends the logo card, which shows your image; one without sends the plain card. The platform picks the card from the template. Creating a template never depends on approval.

Approval, and your first send. A business must be approved for invitation messages, and for each card separately: approval for the logo card does not cover the plain card, or the other way round. That approval is checked when an invitation is sent, not when you create a template, so send one invitation before your first batch: if the business is not approved yet, that one send tells you, with the reason.

Then, per invitation:

POST /v1/invitations
Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7

{"to": "+15551234567", "template_id": "invt_a4f2c718d0", "reference_id": "order-4417"}

One condition applies: template_id is one of your template ids (GET /v1/invitation_templates lists them). The number’s thread must also be open: a customer who left an earlier invitation closed it, and a send into a closed thread is 409 chat_closed.

If the customer accepts, a chat is created and you message it normally. Each invitation reports its whole lifecycle as invitation.* events: delivery as invitation.sent or invitation.failed, then the customer’s answer as invitation.accepted, invitation.declined, or invitation.opted_out.

When an invitation is not delivered, it arrives as invitation.failed with a reason, and last_error explains it in plain words. It carries the invitation_id and your reference_id, so you can match it to the invitation you sent:

reasonWhat it meansWhat to do
invitation_template_not_allowedThe business is not approved for this template’s card (last_error names it: the logo card or the plain card)Request approval for that card, or send with a template for the other card if that one is approved
invitation_business_not_eligibleThe business is not enabled for invitation messagesRequest invitation access for the business
invitation_undeliverableThe number cannot receive it: no active Apple Account, a device below iOS 18, or the customer opted out of your invitationsReach the customer on another channel; do not resubmit
auth_errorA credentials problem on our sideNothing on yours; we are alerted
platform_errorA defect on our sideDo not resubmit; we are alerted
undeliveredA temporary failure that outlasted our retriesResubmit later

None of these is retried automatically once final, and the first two do not change until the approval does.

The opt-in is yours. You collect it and you hold it; we do not ask for a copy and we refuse nothing for its absence. The rule you sign up to under the channel’s service terms: an invitation goes only to a customer who opted in at your point of sale (your website, app, customer service number or physical location), the opt-in covers the transaction it was given for and lasts at most two years, every invitation offers a way to opt out, and the following sentence appears at the point of sale, where your other legal notices are, before the customer opts in:

Separate from your opt-in to receive Business Updates from us, your use of Apple Messages for Business service is also governed by Apple’s terms, including those at https://www.apple.com/legal/privacy/data/en/messages-for-business/. Unless you have opted-out on your Apple device, Apple may process your personally identifiable information.

Every device on the customer’s Apple Account must be on iOS 18 or macOS 15 or later for the card to be delivered; that check is Apple’s, not something you can see in advance.

When a customer opts out. A customer who types STOP on an invitation thread, or leaves it, is recorded as opted out: GET /v1/invitations/{inv} reads opted_out: true. A later invitation to that number is still sent if the thread is open (it is your assertion that the customer opted in again at your point of sale), and that send is noted once in the chat’s events as send_after_opt_out, after which the thread reads opted_out: false.

POST /v1/invitation_batches submits up to 10,000 invitations at once, all from one template: its name and logo are resolved when you submit and frozen into the run, so editing the template mid-run never changes what queued recipients receive. We check every recipient when you submit, so you learn which numbers are unusable before we send anything. Each recipient is then sent as its own invitation. The run tells you how far it has got (GET /v1/invitation_batches/{batch}); each invitation tells you its result, as invitation.sent or invitation.failed carrying the reference_id you gave that recipient, exactly as a single invitation does. A run that is completed has sent everything it will send; it does not mean every invitation was delivered. POST /v1/invitation_batches/{batch}/cancel stops any recipient we haven’t tried yet.

Send an invitation
POST/v1/invitations
Get an invitation
GET/v1/invitations/{inv}
ModelsExpand Collapse
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"
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.

InvitationRetrieveResponse object { id, chat_id, created_at, 10 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
formatdate-time
delivery: string

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

message_id: string

The send delivery reports on: the same message_id the send response returned. A failed delivery’s reason is not on this read; it arrives in the invitation.failed webhook, and GET /v1/messages/{msg} answers it for a partner that polls.

opted_out: boolean

The invitation thread’s standing opt-out: a STOP typed on it, or the customer leaving it, sets it; your next accepted invitation to the thread clears 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.