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.
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.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. CallGET /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.
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 withisnap_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 returns402 trial_needs_inbound, and that earlier message was accepted: read it withGET /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.
Footer and welcome message
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_inboundis accepted and makes the trialdormant. Exactly that many sends are possible between two signals from the prospect. - While the trial is
dormant, a send returns402 trial_needs_inbound.GET /v1/messagesandGET /v1/trial/activitykeep working. - At each change to
dormant, iSnap sendstrial.dormantonce, and the prospect receives the notice of Footer and welcome message once.
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_pendingafterpairing_expires_atbecomesrevokedwhenGET /v1/trial/statusis next called for it, and that call returns410 pairing_expired. No event is sent. Until that call,GET /v1/trial/{trial_id}can still returnpairing_pending. GET /v1/trial/statusnever returnsrevoked. For every revoked trial, one that you revoked included, it returns410 pairing_expired.- Fields of
GET /v1/trial/statusdepend on the status.pairing_expires_atis set only while the trial ispairing_pending.pool_line_idis always set.pool_line_number,paired_handleandactivity_rulearenulluntil the prospect is paired. The pairing code intokenis 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.
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
rebindrevokes 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_afterand theRetry-Afterheader give the number of seconds to wait.error.retry_atis the timestamp at which the oldest counted revoke leaves the 24 hours.
200 and send no event, whatever the reason, and are never limited:
- A repeated revoke. The response has
already_revoked: true, andreasonis the reason stored by the first revoke. - A revoke after the pairing window ended, on a trial that never paired. The stored
reasonispairing_expired, and thereasonof your request is ignored.
Limits on live trials
A trial is live while it ispairing_pending inside its pairing window, active or dormant.
- A direct account has one live trial at a time. A second
POST /v1/trial/initreturns409 trial_already_active: revoke the live trial first. - A wholesale partner can start any number of trials. See Accounts.
- 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.
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
Setmetadata 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
PATCHmerges: a key that you send replaces the stored value, a key sent asnullis 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
PATCHworks in every status,revokedincluded, 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.
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 anisnap_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}andPATCH /v1/trial/{trial_id};GET /v1/trial/activity;trial.linked,trial.bind_conflictandtrial.revoked.
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.