Developers

The same surface the product runs on.

Contacts, conversations, journeys, forms, orders and insights are resources. Access is scoped by the same role model the interface uses, and every write lands in the audit chain.

Scoped by RBACAudited by default
API

Resources, not endpoints bolted on later.

A token carries a role. A role carries modules and scope. The API cannot do anything the role could not do in the interface.

  • Row-level security means a token can never read another tenant
  • Writes are recorded in the hash-chained audit log
  • Contact lookups use the blind index, so PII stays encrypted at rest
  • Approval-first applies here too: staged actions need a confirm call
stage a reply, then confirm it
# 1. propose. nothing is sent yet.
POST /v1/conversations/cnv_8f21/messages:stage
Authorization: Bearer $UXJO_TOKEN

{
  "template": "cart_reminder_v3",
  "variables": { "name": "Priya" }
}

# → 201 { "id": "stg_4d90", "state": "awaiting_approval" }

# 2. a person with the approvals scope confirms.
POST /v1/approvals/stg_4d90/confirm

# → 200 { "state": "sent", "audit": "aud_71c…" }
GET/v1/contactsFilter by tag, owner, consent state or suppression.
POST/v1/contacts/searchBlind-index lookup by phone or email.
GET/v1/conversations/:id/messagesThread history with referral attribution attached.
POST/v1/broadcastsCreates a paused broadcast. Launch gates apply.
GET/v1/journeys/:id/runsPer-run state for a triggered journey.
GET/v1/insights/:channelDaily recorded insights per channel.
PATCH/v1/orders/:idOrder state, written back to the contact record.
Events

Subscribe to what happened, not what you polled for.

The same event stream the interface listens to over its realtime connection.

message.receivedAn inbound message arrived, with referral attribution if it came from an ad or post.
approval.stagedAn agent proposed a write. Nothing has executed.
approval.confirmedA person approved a staged action. Carries the audit entry id.
contact.opted_outAn opt-out was captured. Suppression is already enforced.
order.createdAn in-chat checkout completed against a contact.
insights.recordedA day of channel insights finished recording.
Realtime

Live sync while the page is open.

Server-sent events push changes to any open surface, so an inbox or an analytics page updates without a refresh loop.

  • One stream per tenant, scoped to the role holding the connection
  • Reconnects resume from the last event id
  • The installable app uses the same stream, plus push notifications
realtime stream
const es = new EventSource('/v1/stream');

es.addEventListener('approval.staged', (e) => {
  const a = JSON.parse(e.data);
  // a.state === 'awaiting_approval'
  queue.add(a);
});

es.addEventListener('contact.opted_out', (e) => {
  suppression.apply(JSON.parse(e.data).contact_id);
});
Not on this page?

Your journey is the one we have not listed yet.

Every business has a flow that is only theirs. Describe it in a message and we will map it with you, name the triggers, and tell you honestly what runs today and what does not. No form, no wait.

Talk to usStart free