Appearance
API reference
What a product's client calls: an anonymous identity the service makes, reports and their answers (live), a contact only if the user gives one, moving the identity to another device, and the user's own data. Every request carries the product's key (X-Alias-Key); all but three carry the user too (X-Alias-User, X-Alias-Secret, as POST /api/v1/identity or a transfer gave them).
Errors are {"error": "…"} with a status: 400 (what's wrong, in words), 401 (no such key, id or secret), 403 (blocked, or a browser origin the product doesn't allow), 404, 409, 413, 429 (a limit: try later), 5xx. The clients follow this description: the same calls and names in each.
The spec: OpenAPI 3.1.0, openapi.yaml or openapi.json, for code generators, Postman, Insomnia and the like.
Headers. Every request carries the product's key, X-Alias-Key. Calls marked user also carry the user's X-Alias-User and X-Alias-Secret.
Operations
getProduct
GET /api/v1/product · key only
The product's public settings (for the client's UI).
Answers:
newIdentity
POST /api/v1/identity · key only
A new anonymous identity, made by the service (or a new secret for an id support reset).
Limited to 20 an hour per address per product, and by a budget per key. If the product asks for proof of work (identityChallenge says how many bits), the request carries X-Alias-Work: <challenge>:<nonce>, where SHA-256 of that whole value begins with that many zero bits. Without it, or with a challenge out of time or used: 428 (ask for another challenge).
| Parameter | In | Type | |
|---|---|---|---|
X-Alias-Work | header | string | <challenge>:<nonce>, when the product asks for proof of work. |
Body (optional): object
| Field | Type | |
|---|---|---|
id (optional) | string | An id whose secrets support reset. |
Answers:
identityChallenge
GET /api/v1/identity/challenge · key only
The proof of work a new identity costs, if any (a challenge good for five minutes, once).
bits 0: none, post the identity as it is. Otherwise find a nonce such that SHA-256 of <challenge>:<nonce> begins with bits zero bits (about 2^bits tries), and send that as X-Alias-Work. The bits can go up at any time (an attack, a key in quarantine), so a client always asks.
Answers:
200The work asked for: objectbits: integerchallenge: stringexpires_at: string (date-time)
- otherwise an Error
claimTransfer
POST /api/v1/transfer/claim · key only
This device gets the identity a transfer code names, with a secret of its own.
10 tries an hour per address per product; a code ends after 5 wrong tries, or 10 minutes.
Body: object
| Field | Type | |
|---|---|---|
code | string |
Answers:
me
GET /api/v1/me · user
The id and secret are good; the id's devices and contact.
Answers:
eraseMe
DELETE /api/v1/me · user
Erase the id and everything of it (reports, attachments, contact), for good.
Answers:
200Erased: objecterased: stringtickets: integer
- otherwise an Error
myData
GET /api/v1/me/data · user
A copy of everything kept about the id, as JSON (attachments base64).
Answers:
200The copy: object- otherwise an Error
getContact
GET /api/v1/me/contact · user
The contact given (null none), and the product's policy.
Answers:
200The state: ContactState- otherwise an Error
setContact
PUT /api/v1/me/contact · user
Give (or change) a contact, with the user's consent, if the product takes one (else 409).
Only after the user agreed to the product's contact_consent text, shown whole: consent.version is that text's version. Without it 400; with an older text's 409 (show the current one and ask again). The consent is recorded with the text; a new contact ends the one before (replaced).
Body: object
| Field | Type | |
|---|---|---|
kind | email, other | |
value | string | up to 254 characters |
consent | object |
Answers:
200Given: ContactState- otherwise an Error
withdrawContact
DELETE /api/v1/me/contact · user
Withdraw the contact (deleted at once; its consent's record kept, ended, until the identity goes).
Answers:
200Gone: ContactState- otherwise an Error
newTransfer
POST /api/v1/me/transfer · user
A code to type on another device within 10 minutes.
Answers:
tickets
GET /api/v1/tickets · user
The user's tickets, newest first (50), without their threads.
Answers:
report
POST /api/v1/tickets · user
Send a report.
JSON, or multipart/form-data with files (the product's attachments limits and types).
Body: Report
Answers:
ticket
GET /api/v1/tickets/{id} · user
One ticket, with its thread.
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) |
Answers:
reply
POST /api/v1/tickets/{id}/comments · user
Reply (a ticket waiting on the user, or resolved, opens again; a closed one is 409).
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) |
Body: Reply
Answers:
requestLive
POST /api/v1/tickets/{id}/live · user
Ask to talk about the ticket live.
If someone at the desk is taking live conversations, the ticket goes live at once (mode live); otherwise it's marked (live_requested_at) and goes live when support takes it. 403 when the product doesn't let users ask (live.user_may_ask).
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) |
Answers:
typing
POST /api/v1/tickets/{id}/typing · user
The user is typing (a live ticket's; at most one every 2 s is passed on).
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) |
Answers:
204Noted- otherwise an Error
markRead
POST /api/v1/tickets/{id}/read · user
The user has read the thread up to now (support sees it).
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) |
Answers:
204Noted- otherwise an Error
attachment
GET /api/v1/tickets/{id}/attachments/{aid} · user
One of the user's own attachments, as kept.
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) | |
aid | path | integer (int64) |
Answers:
200The file- otherwise an Error
remoteRequests
GET /api/v1/remote · user
The user's remote assistance requests still going, across their tickets.
What a device's agent watches (with the remote events): support asked to open a shell (SSH) or see the screen (VNC) on this device. Nothing connects until the device accepts. offered: the product offers remote assistance at all on this deployment.
Answers:
200The requests: objectremote: array of RemoteRequestoffered: boolean
- otherwise an Error
ticketRemote
GET /api/v1/tickets/{id}/remote · user
A ticket's remote assistance requests, all of them, newest first.
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) |
Answers:
200The requests: objectremote: array of RemoteRequestoffered: boolean
- otherwise an Error
answerRemote
POST /api/v1/tickets/{id}/remote/{rid} · user
The device's answer to a request.
Yes: the relay's session is made and relay is this device's side of it: dial url (a WebSocket) with token as the bearer and connect it to what the channel is on this device (the device's own setting: 127.0.0.1:22 for shell, :5900 for screen), never anything support names. The session is single attach: when either side leaves it's over. Asking yes again before anyone's on it gives the same again. 409: it's not waiting for an answer any more.
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) | |
rid | path | string |
Body: object
| Field | Type | |
|---|---|---|
accept | boolean |
Answers:
200The request, as it is now (withrelaywhen accepted): RemoteRequest- otherwise an Error
endRemote
DELETE /api/v1/tickets/{id}/remote/{rid} · user
The device ends the session (or takes its yes back).
| Parameter | In | Type | |
|---|---|---|---|
id | path | integer (int64) | |
rid | path | string |
Answers:
200Ended- otherwise an Error
updates
GET /api/v1/updates · user
The tickets support answered or changed since a time (polling).
now is the time to ask from next, by the service's clock and a moment early, so an update can come twice (by poll and stream, or two polls): drop what's been seen, by ticket and at.
| Parameter | In | Type | |
|---|---|---|---|
since | query | string (date-time) | The last answer's now; none: everything support ever touched. |
Answers:
200Updates, and the time to ask from next: objectupdates: array of Updatenow: string (date-time)
- otherwise an Error
events
GET /api/v1/events · user
The same, pushed (Server-Sent Events) while open.
An Update as data, its kind the event: answer; on a live ticket also mode (it went live or back), typing (support is typing) and read (support read up to at); remote when a remote assistance request is asked or changes. A comment line every 25 s. At most 3 streams per user. A client that can't send headers on a stream (a browser's EventSource) polls /api/v1/updates instead.
Answers:
200The stream: string- otherwise an Error
Objects
Error
| Field | Type | |
|---|---|---|
error | string | |
code (optional) | key_revoked | What kind of refusal, when it's one a client acts on. key_revoked: this build's key was revoked. |
reason (optional) | string | key_revoked: support's words for the user ("This version is no longer supported"), when they gave some. Show it as it is.; up to 300 characters |
RemoteRequest
| Field | Type | |
|---|---|---|
id | string | |
ticket | integer (int64) | |
channel | shell, screen | shell: SSH to the device's sshd (its own sign-in still applies). screen: VNC; not encrypted end to end, the relay's operator could see it. |
state | asked, accepted, open, ended, declined, expired | |
asked_by | string | The support person's name. |
asked_at | string (date-time) | |
deadline | string (date-time) | asked: answer by; accepted, open: the session's end. |
end_reason (optional) | string | left, ended_by_support, ended_by_user, time_up, nobody_came, relay, not answered. |
relay (optional) | object | The device's side, once accepted (answerRemote's answer only). |
Identity
| Field | Type | |
|---|---|---|
id | string | A UUID v7.; e.g. 01a1a2b3-9cdf-71e1-b2d5-91d78e28a35a |
secret | string | Shown this once: keep it (only its hash is kept here).; pattern ^[0-9a-f]{64}$ |
Field
| Field | Type | |
|---|---|---|
name | string | |
label (optional) | string | |
type | text, number, bool, geo, link, json | |
template (optional) | string | A link's: {field} replaced by that field's value. |
Product
| Field | Type | |
|---|---|---|
id | string | |
name | string | |
privacy_url (optional) | string | |
branding (optional) | object | |
fields | array of Field | What a report's context may carry; the rest is dropped. |
contact | off, optional | |
contact_consent (optional) | ConsentText or null | With contact optional: the text to show by the contact field. |
contact_kinds | array of email, other | |
keeping | object | |
redact (optional) | array of RedactRule | Applied, in order, to what the user sends, before it leaves the device; the service applies them again as it comes in. Skip a rule that's server_only (hash) or a strategy the client doesn't know; a client:<name> strategy is the app's own. |
removable (optional) | array of string | Paths (as a rule's) the user may take out before sending: show the telemetry, offer these. |
attachments (optional) | AttachmentLimits | |
formats (optional) | array of Format | |
telemetry_max (optional) | integer | The telemetry's bytes at most, as JSON. |
conversations (optional) | object | How the product's tickets run. |
live (optional) | object | Live now, as the client should offer it. |
RedactRule
| Field | Type | |
|---|---|---|
path | string | /telemetry/… (a JSON pointer, * for every key or element), /context/<field>, or /note (a note and a reply's text).; e.g. /telemetry/peers/*/ip |
strategy | string | drop, mask, truncate, round, coarsen, regex, hash (server only), or client:<name>. |
server_only | boolean | |
keep (optional) | integer | mask: ••••, then the last keep characters (leading • aren't counted). |
max (optional) | integer | truncate: the characters kept. |
step (optional) | number | round: to the nearest multiple. |
digits (optional) | integer | coarsen: a number, or a geo value's lat and lon, to these decimals. |
pattern (optional) | string | regex: RE2, in every text under the path. |
replace (optional) | string | regex: what a match becomes, literally ([redacted]). |
AttachmentLimits
| Field | Type | |
|---|---|---|
max | integer | Files a report or a reply may carry. |
bytes | integer | Their bytes together. |
types | array of image/jpeg, image/png, image/webp, text/plain, application/json | Told from the bytes, not the name or the part's type. |
Attachment
| Field | Type | |
|---|---|---|
id | integer (int64) | |
name | string | Letters, digits, . - _; unique in its report or reply: what |
type | string | |
size | integer | |
created_at | string (date-time) |
Format
text/markdown: CommonMark with GFM's tables, strikethrough and autolinks; no raw HTML, and no images but the report's own attachments (). Render it, or show it as it is.
text/plain, text/markdown
Me
| Field | Type | |
|---|---|---|
id | string | |
created_at | string (date-time) | |
devices | integer | Devices holding a secret for this id. |
contact (optional) | Contact or null |
Contact
| Field | Type | |
|---|---|---|
kind | email, other | |
value | string | |
given_at | string (date-time) | |
consent (optional) | Consent |
ConsentText
| Field | Type | |
|---|---|---|
text | string | Show it, whole, where the user gives a contact; send nothing until they agree to it. |
version | string | The text's hash: send it back with the contact, as proof of which text was shown. |
Consent
What the user agreed to in giving a contact, as recorded. Kept, ended or not, until the identity goes.
| Field | Type | |
|---|---|---|
id | integer (int64) | |
purpose | contact | |
version | string | |
text | string | The text as it was when they agreed. |
kind | email, other | |
app_version (optional) | string | |
given_at | string (date-time) | |
ended_at (optional) | string (date-time) | |
ended (optional) | withdrawn, replaced |
ContactState
| Field | Type | |
|---|---|---|
contact | Contact or null | |
policy (optional) | off, optional | |
kinds (optional) | array of string | |
consent (optional) | ConsentText or null | The text to show now (as the product's contact_consent). |
Report
| Field | Type | |
|---|---|---|
note | string | up to 4000 characters |
format (optional) | Format | |
screenshot (optional) | string | A JPEG, up to 3 MB (JSON only: as multipart, send it as an attachment). |
context (optional) | object | The product's fields (GET /api/v1/product). |
app_version (optional) | string | up to 40 characters |
telemetry (optional) | object | Free JSON up to the product's size, matching its schema if it has one (else 400, saying where). |
Reply
| Field | Type | |
|---|---|---|
body | string | up to 4000 characters |
format (optional) | Format | |
screenshot (optional) | string | |
context (optional) | object | |
telemetry (optional) | object |
Entry
| Field | Type | |
|---|---|---|
id | integer (int64) | |
author | user, support | |
name (optional) | string | A support person's name. |
agent (optional) | boolean | Support's, but written by an agent (a service account: an AI agent, a script), not a person. Show the user it was. |
kind | comment, status, mode | status: body is the new status; mode: the ticket's new mode. |
body | string | |
format (optional) | Format | |
created_at | string (date-time) | |
context (optional) | object | |
attachments (optional) | array of Attachment | |
has_screenshot (optional) | boolean | An image among the attachments. |
Status
open, in_progress, waiting, resolved, closed
Mode
tickets: request and response; live: a conversation, as it happens.
tickets, live
Ticket
| Field | Type | |
|---|---|---|
id | integer (int64) | |
product | string | |
status | Status | |
note | string | |
format | Format | |
context (optional) | object | |
app_version | string | |
attachments | array of Attachment | The report's own (a reply's are its entry's). |
has_screenshot | boolean | An image among the attachments. |
created_at | string (date-time) | |
updated_at | string (date-time) | |
support_at (optional) | string (date-time) | Support's last answer or status change. |
mode (optional) | Mode | |
live_requested_at (optional) | string (date-time) | The user asked for live and nobody was there yet. |
user_read_at (optional) | string (date-time) | The user read the thread up to then. |
support_read_at (optional) | string (date-time) | Support read the thread up to then. |
thread (optional) | array of Entry |
Update
| Field | Type | |
|---|---|---|
kind | answer, mode, typing, read, remote | Polling (updates) has answers only; the stream has all five. remote: a remote assistance request changed (look it up: remoteRequests). |
ticket | integer (int64) | |
status | Status | |
at | string (date-time) | |
mode (optional) | Mode | |
who (optional) | support | typing, read: who. |
name (optional) | string | typing: the support person's name. |
remote (optional) | string | remote: the request's id. |
channel (optional) | shell, screen | remote: which. |
state (optional) | string | remote: what it is now (RemoteRequest's state). |
Transfer
| Field | Type | |
|---|---|---|
code | string | Typed on the other device (any case; O, I, L read as 0, 1, 1).; pattern ^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$ |
expires_at | string (date-time) |
WebhookEvent
What a webhook endpoint receives (Standard Webhooks): signed with the webhook-id, webhook-timestamp and webhook-signature headers. The fields marked "content" are there only if the endpoint has content on, and are redacted by the product's rules.
| Field | Type | |
|---|---|---|
type | ticket.created, ticket.replied, ticket.answered, ticket.status_changed, ticket.deleted, ticket.live_started, ticket.live_ended, webhook.test | |
timestamp | string (date-time) | |
data | object |
WebhookTicket
| Field | Type | |
|---|---|---|
id | integer (int64) | |
status | open, in_progress, waiting, resolved, closed | |
mode | tickets, live | |
app_version (optional) | string | |
created_at | string (date-time) | |
updated_at | string (date-time) | |
url (optional) | string (uri) | the ticket on the panel (with the deployment's public_url) |
user (optional) | string | content: the user's anonymous id |
note (optional) | string | content |
format (optional) | text/plain, text/markdown | content |
context (optional) | object | content |
telemetry (optional) | object | content |
attachments (optional) | array of WebhookFile | content |
WebhookEntry
What happened (ticket.replied, .answered, .status_changed, .live_started, .live_ended).
| Field | Type | |
|---|---|---|
id | integer (int64) | |
author | user, support | |
kind | comment, status, mode | |
created_at | string (date-time) | |
status (optional) | string | kind status: the new status |
mode (optional) | tickets, live | kind mode: the new mode |
body (optional) | string | content |
format (optional) | string | content |
name (optional) | string | content: the support person's name, on an answer |
agent (optional) | boolean | by an agent (a service account), not a person |
attachments (optional) | array of WebhookFile | content |
WebhookFile
| Field | Type | |
|---|---|---|
name (optional) | string | |
type (optional) | string | |
size (optional) | integer |