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.
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
# 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
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.
Built for people and agents
Channels
Conversation
What Jo does
What it knows
Which model
Start with one channel. Add the rest when you are ready.
Connect WhatsApp today and run a journey this week. Instagram, Facebook, Google, YouTube and email join the same inbox and the same report whenever you want them.