Skip to main content
A trial lets a prospect exchange messages between their own phone and a line reserved for trials, before they have a line of their own. Your backend starts the trial, the prospect pairs by texting a code, and your backend then sends with a trial key that reaches only that prospect. Read this page when you build a trial step into your product. A trial runs on a pool line. When your account has active test lines, the pool line is one of them. Otherwise it is a line of iSnap’s shared trial pool. Among the candidates, iSnap takes the line with the fewest messages in the last 24 hours. The request has no field to choose the line.

How a trial works

1

Start the trial

Call POST /v1/trial/init from your backend with an account API key. The body is optional, and so is every field in it:Any other field returns 400 validation_failed.The response is 201 with trial_id (trl_ followed by 25 letters or digits), token, pool_line_number, pairing_expires_at, instructions_url and metadata. token is the pairing code. The trial key is not in this response. Store trial_id and token: no operation lists trials.This operation does not honor Idempotency-Key. For a wholesale partner, a repeated call starts another trial. A second POST /v1/trial/init from a direct account returns 409 trial_already_active, a repeat of a call whose response was lost included. If you lost its trial_id before the prospect paired, the limit frees when its pairing window ends, 15 minutes after the call. Retry a write only on an operation that honors Idempotency-Key, with the key of the first attempt. See Retrying safely.
2

Show the pairing code and the number of the pool line

Show token and pool_line_number to the prospect in your own interface. The pairing code has 7 characters, taken from 23456789ABCDEFGHJKMNPQRSTVWXYZ: it never contains I, L, O, U, 0 or 1. No two trials share a pairing code. instructions_url is the address of one iSnap page, the same for every trial.
3

The prospect texts the code

The prospect sends a message that contains the pairing code to pool_line_number, from their own phone, within 15 minutes of your call. pairing_expires_at is the end of those 15 minutes. The code is matched case-insensitively and as a separate word: text around it is allowed, and a code attached to other letters or digits is not matched. A code is matched only on the pool line of its own trial.When the code matches:
  • the trial becomes active;
  • the handle that sent the message becomes paired_handle: a phone number, or the email address of an Apple ID when the prospect’s device sends from one;
  • iSnap issues the trial key and sends trial.linked;
  • the prospect receives one confirmation message from the pool line.
The message that carries the code is not stored as a message of the trial and sends no message.received.
4

Read the trial key from your backend

When trial.linked arrives, call GET /v1/trial/status with the pairing code in token. The first call after pairing returns trial_api_key. Every later call omits it. The key stays available for that first call for 1 hour after pairing. If no call reads it within the hour, no operation returns it and the trial cannot send: revoke the trial and start another.The same response carries pool_line_id and paired_handle, which you need to send.
5

Send with the trial key

Call POST /v1/messages with the trial key as the credential, line_id set to the trial’s pool_line_id, and to set to paired_handle. See Trial key for what the key can do.
GET /v1/trial/status needs no credential: the pairing code is all a caller needs, and the prospect sees that code. The first caller after pairing receives the trial key, and every caller receives paired_handle. Treat the pairing code as a secret between you and the prospect. Read the status from your backend only, never from a browser or a mobile app, and read it as soon as trial.linked arrives.

Start a trial

Response

Read the status and the trial key

The samples return the trial key on the first call after pairing, and tell the other cases apart. Store the key in your secret storage as soon as you receive it. Call GET /v1/trial/status from the backend that stores the key, and never retry it automatically: the first call after pairing returns the trial key once, and a retry after a lost response loses it. In the SDK, pass { maxRetries: 0 } to trial.status, from a client whose timeoutMs covers your network. A call made after the pairing window ended, for a trial that the prospect has not paired, returns 410 pairing_expired and revokes the trial, with no event.
iSnap records the pairing first and sends trial.linked afterward. If the event cannot be sent, the pairing stands. When trial.linked has not arrived, call GET /v1/trial/status from your backend as a fallback, until it returns a status other than pairing_pending or a 410. Each call counts against the request-rate limit.

Send with the trial key

Trial key

A trial key is issued at pairing and belongs to one trial. It starts with isnap_test_, like a test API key of an account, and is 54 characters long: see Trial keys. Three operations accept it: Every other operation that needs a credential returns 401 unauthorized to a trial key. The operations without a credential ignore it. After a revoke, the three operations above return 401 unauthorized too. A trial send follows these rules: When paired_handle is an email address, no send of that trial can succeed, because to must be a phone number. Revoke the trial to free the handle. A trial send does not count against the sending limits of the line, and its response carries no X-iSnap-Quota-* header. The unanswered-send limit of the trial is the only limit on the number of sends. The request-rate limit applies as on every operation. POST /v1/messages honors Idempotency-Key on a trial send:
  • The key is compared with the earlier messages on the pool line, and other trials use the same line. Use a key that is unique across all trials, such as a UUID.
  • The same key with the same body returns the earlier message, and the send does not count against the unanswered-send limit. The status of the trial is checked first: when the earlier send made the trial dormant, the repeat returns 402 trial_needs_inbound, and that earlier message was accepted: read it with GET /v1/messages.
  • The same key with another body returns 409 idempotency_conflict.
  • A key is remembered as long as its message is kept, at least 72 hours after iSnap recorded it: see Operations that honor the key.
iSnap sends two messages to the prospect on its own and appends a footer to yours. You can replace two of the three texts when you start the trial: Footer. iSnap appends the footer to every trial send, after a blank line. It cannot be turned off. The body of the stored message, as the API returns it, includes the footer, and so does the message that the prospect receives. Idempotency-Key compares the body that you sent, without the footer. Confirmation and notice. Each is sent once per event, and its delivery is not guaranteed: a failed delivery is not retried, and a failed confirmation does not undo the pairing. Neither message counts against the unanswered-send limit or against the totals of Activity, and the trial key cannot read them.

Unanswered-send limit

max_outbound_without_inbound is the number of messages that the trial key can send in a row without a signal from the prospect. It is a count, not a timer.
  • Each accepted trial send adds 1 to outbound_since_last_inbound.
  • The send that brings the counter to max_outbound_without_inbound is accepted and makes the trial dormant. Exactly that many sends are possible between two signals from the prospect.
  • While the trial is dormant, a send returns 402 trial_needs_inbound. GET /v1/messages and GET /v1/trial/activity keep working.
  • At each change to dormant, iSnap sends trial.dormant once, and the prospect receives the notice of Footer and welcome message once.
No delay changes the status of a trial: an active or dormant trial stays so until one of the signals below, or until you revoke it. Three signals from the prospect set the counter back to 0: When the trial is dormant, the signal also makes it active again and iSnap sends trial.reactivated. When the trial is active, the counter returns to 0 and no event is sent. A reaction that the prospect removes and a typing indicator do not reset the counter. On an iMessage line, a read by the prospect is not a signal: it neither sets the counter back to 0 nor reactivates the trial. Only a reply or a reaction does.

Statuses

Three behaviors to build for:
  • The end of the pairing window is recorded at the next status call. A trial that is still pairing_pending after pairing_expires_at becomes revoked when GET /v1/trial/status is next called for it, and that call returns 410 pairing_expired. No event is sent. Until that call, GET /v1/trial/{trial_id} can still return pairing_pending.
  • GET /v1/trial/status never returns revoked. For every revoked trial, one that you revoked included, it returns 410 pairing_expired.
  • Fields of GET /v1/trial/status depend on the status. pairing_expires_at is set only while the trial is pairing_pending. pool_line_id is always set. pool_line_number, paired_handle and activity_rule are null until the prospect is paired. The pairing code in token is case-insensitive, and spaces around it are ignored.

Revoking a trial

POST /v1/trial/{trial_id}/revoke ends a trial that you own, in any status other than revoked, a trial that is still pairing included. After a revoke:
  • the trial key returns 401 unauthorized;
  • the prospect’s handle can pair with another trial immediately;
  • a direct account can start another trial;
  • the prospect receives no message, and the messages they send to the pool line afterward are dropped.
The body is optional. Its only field is reason, which is recorded on the trial and carried by trial.revoked: The limits of reason: rebind:
  • When iSnap turned rebind off for your account, the call returns 403 rebind_not_allowed.
  • A direct account can make 3 rebind revokes per rolling 24 hours. A wholesale partner has no limit. iSnap can set another value for an account.
  • Over the limit, the call returns 429 rebind_cap_reached. error.retry_after and the Retry-After header give the number of seconds to wait. error.retry_at is the timestamp at which the oldest counted revoke leaves the 24 hours.
Two cases return 200 and send no event, whatever the reason, and are never limited:
  • A repeated revoke. The response has already_revoked: true, and reason is the reason stored by the first revoke.
  • A revoke after the pairing window ended, on a trial that never paired. The stored reason is pairing_expired, and the reason of your request is ignored.

Limits on live trials

A trial is live while it is pairing_pending inside its pairing window, active or dormant.
  • A direct account has one live trial at a time. A second POST /v1/trial/init returns 409 trial_already_active: revoke the live trial first.
  • A wholesale partner can start any number of trials. See Accounts.
A prospect’s handle is limited too, inside one pool:
  • On iSnap’s shared trial pool, a handle is paired with one pool line at a time.
  • On your own test lines, a handle is paired with at most a number of lines that iSnap sets for your account. That number is 1 unless iSnap changed it.
  • A handle has at most one live trial on one pool line.
When a prospect texts a valid pairing code over one of these limits, the trial is not paired and stays pairing_pending until its pairing window ends. iSnap sends trial.bind_conflict, and the prospect receives no reply. GET /v1/trial/status keeps returning pairing_pending: the event is the only signal. If the earlier trial of that prospect is yours, revoke it, and the handle can pair again immediately.

Metadata

Set metadata when you start a trial, and change it with PATCH /v1/trial/{trial_id}. The body of the PATCH is required and has one field, metadata. Send Content-Type: application/json with every body.
  • The PATCH merges: a key that you send replaces the stored value, a key sent as null is deleted, and the other keys stay.
  • The result contains at most 20 keys and 8,192 bytes once serialized. Beyond that, the call returns 400 metadata_too_large. A request that only deletes keys is never refused for the size of the result.
  • The PATCH works in every status, revoked included, and sends no event.
metadata is returned by POST /v1/trial/init, GET /v1/trial/{trial_id} and PATCH /v1/trial/{trial_id}. GET /v1/trial/status never returns it. Every trial.* event carries the metadata of the trial in data.metadata. data.line_metadata carries the metadata of the pool line when that line is one of your test lines, and null otherwise. The message events of a trial carry the metadata of the message, not of the trial.

Activity

GET /v1/trial/activity returns the counters of one trial. It accepts the key of that trial only: an account API key returns 401 unauthorized. The response also carries trial_id, pool_line_id and paired_handle.

Events

The data of each event is described in Trial events. A message from the paired handle is stored and sends message.received: see Receiving messages. The events of a trial, and the message events of its messages, are tied to the pool line of the trial. A webhook subscription with a line filter receives them only when the filter lists that line. The pool line is chosen when the trial starts: receive trial events on a subscription without a line filter. The trial events announce the five changes above, and no other. No event announces:
  • the start of a trial;
  • the end of a pairing window, or a trial revoked because its window ended;
  • a change of metadata;
  • a message that a sender who is not paired sends to the pool line, which is dropped without a reply;
  • a text that matches no pairing code;
  • the confirmation and the notice that iSnap sends to the prospect, or a failure to deliver them.
Getting a line of your own changes no trial and sends no trial event. Revoke a trial when the prospect no longer needs it. The full list is in What events do not announce. To know the state of a trial, read it with GET /v1/trial/{trial_id}, or with GET /v1/trial/status during pairing. Deduplicate on event_id. Delivery order is not guaranteed. Order events by the timestamps inside data, not by arrival. See Duplicates and Order.

Errors

The refusals of each operation are on its reference page: POST /v1/trial/init, GET /v1/trial/status, GET /v1/trial/activity, GET /v1/trial/{trial_id}, PATCH /v1/trial/{trial_id} and POST /v1/trial/{trial_id}/revoke. The refusals of a send with a trial key are on POST /v1/messages. The errors that every operation shares are in Errors.

Keys and accounts

  • Test keys. An isnap_test_ account key is not a separate test environment: it acts on the same account, lines and billing as an isnap_live_ key. A trial started with a test key is a real trial. See Test keys.
  • Line-scoped keys. The list of lines of a line-scoped key is not applied to trials: such a key can start, read, update and revoke any trial of the account.

Cost

A trial on iSnap’s shared trial pool costs nothing. A test line of your own is billed like a two-way rented line, whether or not trials run on it: see Test lines.

Personal data

paired_handle is the prospect’s phone number, or the email address of their Apple ID. These responses and events carry it:
  • GET /v1/trial/status, without a credential;
  • GET /v1/trial/{trial_id} and PATCH /v1/trial/{trial_id};
  • GET /v1/trial/activity;
  • trial.linked, trial.bind_conflict and trial.revoked.
The trial key reads the text of the messages of the trial through GET /v1/messages. The message events of a trial carry the same text and handles, and message.received adds contact_name when the line’s device reports a display name for the sender. Request this data only when you need it. Do not copy it into logs, tickets or other tools. Refer to a prospect by an id of your own, which you can keep in the metadata of the trial.