How it works
The mental model behind every allus integration — services, connect handles, connections, request vs share fields, consent, one-time vs live data, and the zero-knowledge encryption model. No code; just the concepts you'll build on.
Services
A service is the unit a company connects through — think of it as one integration surface (a CRM, a support desk, a loyalty program). A company can have several. Each service has:
- its own zero-knowledge keypair — the public key people encrypt to, and a passphrase-encrypted private key you download from the portal (the passphrase is shown once; the portal keeps a copy only your account password can open);
- a per-company share code (1–8 uppercase chars), the second half of its connect handle;
- an audience —
b2p(business↔person),b2b, orany. People connect tob2p/anyservices;b2bis reserved for company↔company; - a request-field config (what it asks of people), a share-field config (what it shares back), webhooks, and registered API clients.
Connecting always happens via a service — never to a company directly. A service accepts connections, and the company-data API answers for it, once your company is on the Basic level or above; below that the service presents as one that does not exist.
Connect handles
People connect using a connect handle: two case-insensitive URL segments, COMPANYCODE/SERVICECODE.
ACME01 / CRM
─────── ─────
the company's the service's
profile share code share code
The company code alone is an error everywhere on the person-side lookup (profile.company_code_requires_service) — a person must always name the service they're connecting to. The handle resolves to a connect screen showing the company + service identity and the list of fields the service requests.
Connections & service connections
Two layers describe a relationship:
- A company connection is the pair anchor — exactly one row per company↔person pair. It's created implicitly with the first service a person connects to, and removed when the last one disconnects.
- A service connection is the person's link to one service. A single company connection can hold N service connections (the person uses your CRM and your support desk).
person ──(1)── company connection ──(N)── service connection ──(N)── answered request slots
(one per pair) (one per service) (the person's field per slot)
From the API you read company connections and, inside each, the per-service answered values. Each connected person is one row in GET /api/company-data/connections; the SDKs surface it as a Connection with a values map keyed by your request slugs.
Request fields, share fields, and the company profile
Data flows in both directions, configured separately:
Request fields — what you ask for
A service's request fields declare what it wants from connecting people, by field type plus an advisory suggested field. Each request field has a stable slug you choose (e.g. work_email) — that slug is your contract and the key every value comes back under. You can rename the label freely; the slug never changes. Request fields carry flags:
one_time— the person answers once with a frozen snapshot (never auto-updates);mandatory_provide— the person must answer to connect at all;mandatory_connected— the answer must be a live (auto-updating) field; impliesmandatory_provide, and is mutually exclusive withone_time.
Request fields are edited as a draft and published; adding a field to a live service fans out a consent request to existing connections (see below). Which types you may ask for is served, not listed here — every type the platform carries except an option-less choice type — and a row asking for a PARENT type is answered by any type below it. See Field types & the typed value model.
Share fields — what you share back
A service's share fields are company-owned fields you publish to every connected person on that service (a support email, an address, a logo). These are plaintext by design — see the encryption model below.
The company profile
Your company profile is shared with every connection across any of your services (not per-service). It's also plaintext.
The person-connect & consent flow
Connecting is synchronous — there's no company approval step. The flow, from a person's app:
1. Resolve handle GET …/lookup/COMPANYCODE/SERVICECODE
→ company + service identity, request fields, what the
service shares back (plaintext preview)
2. Decide per slot pick one of their matching fields, OR type a one-time value,
OR a per-company custom field (suggested field pre-selected)
3. Encrypt each chosen value → encrypted to YOUR service public key
4. POST connect one transaction: pair anchor + service connection +
the answered slots; the company's shared data returns
instantly in the 201 response
After connecting, the person sees your service shares and company profile immediately, and you start receiving their answers.
Consent requests
If you add request fields to a service that already has connections, each connection gets a pending consent request (one merged item per connection). The person can provide the new fields or decline. Declines are grandfathered: the connection stays fully active even for fields you marked mandatory — mandatory only applies at first-connect time.
One-time vs live (auto-updating) fields
This distinction is the heart of "auto-updating data":
| Live (connected) | One-time (snapshot) | |
|---|---|---|
| What it is | The answer is mapped to one of the person's living profile fields. | A frozen value captured at answer time. |
| Updates? | Yes — when the person edits that field, a re-encrypted copy is pushed to your service automatically. | Never — it's a point-in-time snapshot. |
| You see | live = true on the value. | live = false on the value. |
| On disconnect | Removed. | Removed. |
A live value's lifecycle: the person's edit re-encrypts the new plaintext to your service key and emits a field_updated change, and so does the field gaining or losing its verification. A one-time value is written once and never touched again. The SDKs expose this as the live flag on each value.
The zero-knowledge encryption model
This is the most important concept to internalize. Data direction decides everything:
| Direction | Encryption | Who encrypts |
|---|---|---|
| Person → service (the values you receive) | Encrypted to your service public key | The person's client, before the value ever leaves their device |
| Company → person (share fields + company profile) | Plaintext | n/a — stored as plain values |
PERSON's device allus platform YOUR backend
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ plaintext │ │ │ │ plaintext │
│ "alice@..." │ ──encrypt(svc pub)──▶│ CIPHERTEXT │──ciphertext──▶│ via the SDK │
│ │ │ {"_enc":1,…}│ │ decrypt with │
└──────────────┘ │ (can't read)│ │ service priv │
└──────────────┘ └──────────────┘
Concretely:
- Each person→service value is a hybrid wrapper:
{"_enc":1,"k":rsa_oaep_sha256(aesKey),"iv":…,"d":aes256gcm(payload)}. The AES-256 key is wrapped with RSA-OAEP-SHA256 to your service public key; the value is AES-256-GCM encrypted. - The platform only ever holds ciphertext for person data. It cannot read it. Your SDK loads the service private key once at startup and decrypts every value locally.
- The service private key is zero-knowledge: generated client-side at service creation, its passphrase shown once, downloadable as an OpenSSL-encrypted PKCS#8 PEM. allus stores only the encrypted blob.
- Company→person data is plaintext on purpose. Your business-card details (support email, address, logo) are public to the people you connect with; storing them plaintext removes any passphrase/sync friction and lets new connections see them instantly. Server-side type validation applies to these values (the one place the platform can see plaintext).
- Binary fields (photo/document) are fetched lazily from a slot-keyed file endpoint. A file from a private field arrives encrypted to your service key; a file from a non-private field arrives as the file itself, because no encrypted copy of it exists.
There's also a company account key, separate from every service key. It encrypts webhook payloads when you enable encrypt_payload (see Webhooks), and it is the key another company encrypts to when your company connects to its service as a customer (Account automation clients). You download its private half in the portal under Settings → Account key.
Ward / parental gating
Some allme accounts are wards with active parental controls. When a ward connects to your service (or answers a new consent request), the action doesn't take effect immediately — it returns a pending state and a custodian must approve it. From your side this is transparent: you simply see the connection or values appear once a custodian approves. Declining a consent is never gated. You don't implement anything for this; it's handled entirely on the allme side.
Putting it together
You define request fields once. People connect via your handle and choose how to answer each slot, encrypting to your service key. You receive their answers — live ones keep updating — keyed by your slugs, decrypted by the SDK, while the platform never sees the plaintext. Next:
- Getting started — stand up a working integration.
- Service API reference — the endpoints and the change-feed semantics.
- SDKs — the six-language clients and the canonical config.