Theme
Open the portal

Reference & appendix

The lookup material: field types and the typed value model, the error catalog, a glossary, the raw encryption spec for non-SDK integrators, and where to find the live API spec.

Field types & the typed value model

A contact-field type is a row, not a name this documentation lists. One route serves every type the platform carries, and everything below — what a value looks like, what makes it valid, which types a request row accepts — is read off those rows. Adding a type is a row, so a list printed here would be stale the day one is added.

The registry route

GET /api/contact-field-types answers a bare JSON array of RAW rows. Your service data client and your account client are both granted it (Authentication), and the SDKs fetch it beside the request-field catalog and hold it for the client's life. Each row:

KeyMeaning
typeThe type's immutable name — what a request field's type and a person's field carry.
parentThe type this one is a KIND OF, or null for a root. That single column is the whole tree.
labelA display label. is_system says which kind: a translation key for a platform type, a literal an operator typed for an added one.
inputThe PRIMITIVE that draws and shapes the value (below).
laneWhere the value is stored: inline (the value itself) or photo/document (a file, fetched from the binary endpoint).
checkA named CHECK applied to the whole value beyond a regex (below).
optionsThe permitted values of a choice type.
fieldsThe sub-field entries of a composite or file type: {key, input, required, check, validation, options} each.
validationA regex the value must match, stored without anchors and applied to the WHOLE value.
is_systemWhether this is a platform type (its label is a translation key) or one an operator added.

A child INHERITS every column it leaves null from its nearest ancestor that sets one — input, lane, check, options, fields. validation is the exception and is additive: a value must match the regex of every ancestor that has one, root first, plus the type's own. Resolving a type means filling those columns in and collecting those regexes; every rule below applies to the RESOLVED definition, never to a raw row.

The tree, and what a request row accepts

Types form a tree. text is the root of the string-like types (email, phone, url, gender, country, nationality, personal_number, place_of_birth, date, and date_of_birth under date); number is the root of integer, decimal and float; document is the root of pdf_document; legal_document is the root of proof_of_identity, which is the parent of the ID-document types passport, photo_id, drivers_license and residence_permit. address, creditcard, bank, photo, select and multiselect are roots with no children today. Walk parent to read the current shape rather than trusting this paragraph.

A request row of type T is answered by a field of T or any descendant of T. Ask for legal_document and the person can answer with their passport; ask for text and they can answer with an email field. That one rule governs request rows, contract-flow fields, suggested fields and Sign-in-with-allme claims alike.

You are never told which type answered, and you do not need to be. The payload keys on your request slot and names YOUR row's type; the person's source field — its id, its slug and its type alike — is not on the wire and there is no provenance field to read. Type a value from your own row's type, which is what the SDKs do. Where the answer is a file, the platform resolves the stored envelope from the field that actually answered, so a legal_document slot answered with a passport serves that passport's file at the same slot-keyed endpoint as any other binary answer.

The two vocabularies

Only two things about a type are fixed code rather than data. The primitives, which say what a value IS:

inputPlaintext shape
lineA string. Free unless a check or a regex narrows it.
dateA string, YYYY-MM-DD, a real calendar date.
listOne string from the resolved options.
multilistA JSON array of strings, each from the resolved options.
country / nationalityAn ISO 3166-1 alpha-2 code (e.g. NL) — a CODE, not a name.
stateA USPS 2-letter state code (US only).
phoneA single E.164-style string with the dial code embedded (e.g. +31612345678): digits with an optional leading +, 4–15 of them once space - ( ) . are removed.
compositeA JSON object keyed by the resolved fields entries — no unknown key, every required entry present, each non-empty entry valid for its own primitive, check and regex.
fileA file on the photo or document lane, served from the binary endpoint. Its fields entries are extra metadata beside the envelope's own members.
pagesThe multi-page ID-document envelope, on the document lane.

personal_number and place_of_birth are line strings. A verified place of birth is <city>, <alpha-2> (e.g. Amsterdam, NL) — a country CODE, not a name.

And the checks, which say what a value must satisfy beyond a regex:

checkRule
urlPrefix https:// when no scheme is given, then the URL grammar.
cardRemove space -, then 12–19 digits passing Luhn.
numberTrim, then a finite number.
integerTrim, then -?[0-9]+.
decimalTrim, then -?[0-9]{1,8}(\.[0-9]{1,2})? — a fixed (10,2) shape, never a per-field precision.
floatTrim, then -?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?, decimal or scientific notation.
pdfA FILE check, applied when a file is written: the file's MIME type is application/pdf and its bytes start with the PDF header. pdf_document carries it.

A value is checked in one order: the primitive's own rule, then the resolved check, then every regex root-first, then the sub-field entries. The check's normalised value is what those regexes see; the primitive's is not. An empty value is valid — whether it is required is your request row's business — and a type your copy of the registry does not carry accepts anything, so an older integration never rejects a value it simply does not know yet.

Binary answers

A binary answer is one whose type resolves to the photo or document lane — today photo, document, pdf_document, legal_document, proof_of_identity and the four ID-document types. For those, the change feed, the webhook and a connection's detail row all carry a value_url instead of an inline value. Fetch it with your client token from GET /api/company-data/connections/{connectionId}/slots/{serviceFieldId}/file. The SDKs expose this as a lazy handle (.bytes() / .save()).

That endpoint has three response shapes, so branch on Content-Type first and then on the body's encrypted member: an answer from a private source field arrives as the familiar {"encrypted": true, "value": <wrapper>} JSON to decrypt off-platform; an answer from a non-private source whose type stores more than one file or declares metadata entries (the ID-document types and legal_document) arrives as {"encrypted": false, "value": "<envelope string>"} — the same envelope, in the clear; every other non-private answer arrives as the raw file with its own content type and nothing to decrypt. Which you get is the person's setting and their choice of field, not yours, and both can change. The envelope inlines every page (or the single file) as a data URI and carries every entry the type declares beside it, so an ID document delivers its pages, its document_number, its expiry_date, its issuing_country and the holder name. Every 200 carries X-Allus-Content-Sha256 over exactly what was served; an expired Share once answer answers 410 company_data.file_expired with that digest and its expired_at. Details: API → binary file.

The reserved type key plugin

plugin is never a registry row. A request row, flow field or claim whose type is plugin is answered through a plugin and carries a plugin spec instead of a registry type; its value is the plugin answer's self-describing JSON string, which the SDKs type as a PluginValue before they consult the registry. No type named plugin can be added to the registry.

The usertype gate

Fields carry a usertype of person, company, or any — it gates which profile type may fill a field, not which clients receive its definition. The field catalog from GET /api/contact-fields is owner-filtered but unfiltered by usertype (your client receives every definition so it can resolve any id in the data). Pass ?usertype=person|company|any for an explicitly filtered view.

The conclusions model (SDK output)

The SDKs return conclusions, not raw rows: for each connected person, your request-field slug → the typed plaintext value, plus whether it is live (auto-updating), its updatedAt, and a verified boolean (verified values — true only when the recomputed sha256(verified_salt ‖ plaintext) matches the server binding; false means "not attested", not "wrong"). Field definitions (label, type, flags) come from a separate call. The person's source field is never exposed — you address everything by your own request-field slug. See SDKs for the per-language shapes.

Plugin routes

The routes behind plugins. The registry routes under /api/account/plugins serve the portal's Configuration › Plugins screen for a company caller; the two pass routes are the ones an integration calls — see Contract flows → Answering a plugin field. A plugin itself is only ever called by the platform's forwarder, never by the API.

RouteWhoWhat
GET /api/account/pluginscompanyYour plugins — id, name, base_url, builtin_key, masked token, field types, last refresh, last error and in_use (the flows, request rows and claims using each) — plus builtin, the platform's built-in plugins with enabled.
POST /api/account/pluginscompany{base_url, token?} → 201 the plugin plus a registry pass for the forwarder.
POST /api/account/plugins/builtin/{key}companySwitch on a built-in plugin → 201 the plugin plus a registry pass.
PUT /api/account/plugins/{id}company{token} — an empty string keeps the stored token, null clears it.
POST /api/account/plugins/{id}/registry-passcompanyA registry pass to refresh the plugin's description.
DELETE /api/account/plugins/{id}companyRemove a plugin; refused 409 plugins.in_use while anything uses it.
POST /api/company-data/flow-runs/{runId}/plugin-passservice data clientA flow pass for your company's party.
POST /api/company-connections/{id}/flow-runs/{runId}/plugin-passperson, or an account client (acct_*)A flow pass for a customer party.

Error keys

Errors return a stable error_key string. The ones you'll encounter integrating the service + portal APIs:

Error keyHTTPMeaning
services.client_exists409A service already has its one registered API client; delete the existing one first.
services.config_conflict409The config_token in a request-fields publish is stale — re-GET and retry.
services.share_code_invalid400A service share code that isn't uppercase [A-Z0-9]{1,8}.
services.share_code_taken409The service share code is already used by another of your services.
services.admin_email_invalid400The required service admin email is missing or invalid.
services.audience_b2b_only403A person tried to connect to a b2b-only service.
profile.company_code_requires_service400A connect lookup used a company code with no /SERVICECODE — the company code alone is never valid.
company_connections.persons_only403 / 400403: the caller is not an account allowed on that connect endpoint. 400: a connect request addressed a company account, or your own.
contact_data.encryption_required400A non-binary value was sent as plaintext where ciphertext is required (person→service data must be encrypted).
documents.contract_requires_person400A contract (kind agreement/subscription, or requires_signature/requires_acceptance set) was created as a broadcast — a contract must target one connected person.
documents.contract_immutable409Re-uploading the file of a contract that has already been signed/accepted (status active).
plugins.url_invalid400A plugin address that can never be a plugin's — not https on port 443, a user name or password, a query or fragment, a refused IP range, localhost, or a host of the platform's own. The reason is in error.
plugins.body_invalid400A plugin add or token update without its base_url or token, or with a token that is not 1–4096 visible ASCII characters.
plugins.not_found404No such plugin for your company.
plugins.duplicate409Your company already has a plugin at that address, or has already switched that built-in plugin on.
plugins.in_use409The plugin is still used by a flow, a request row or a claim; the body names them in in_use.
plugins.builtin_unknown404No built-in plugin has that key.
plugins.builtin_token_locked400A built-in plugin takes no token.
services.request_field_plugin_invalid400A plugin request row is refused; the reason is in error.
oauth_apps.claim_config_plugin_invalid400A plugin claim is refused; the reason is in error.
db.writes_paused503Saving is paused: the platform cannot complete a save in every region right now ("Saving data is not possible right now", sent with Retry-After: 30). Nothing was written — wait Retry-After seconds and repeat the same call. Any call that is not a GET can answer it, as can the change-feed drains GET /api/company-data/changes and GET /api/customer/changes (which then drain nothing) and POST /oauth2/result. Token requests (POST /oauth2/token) keep working. See API → errors.
platform.out_of_order503allme is temporarily out of order: the region serving the call is being rebuilt ("allme is temporarily out of order. Please try again later.", sent with Retry-After: 300). The request was not processed — wait Retry-After seconds and repeat the same call; the platform answers normally again once the region is back in service. Every call can answer it, reads included; the change-feed drains GET /api/company-data/changes and GET /api/customer/changes then drain nothing. A token request with grant_type=client_credentials keeps working; every other grant at POST /oauth2/token, a refresh_token grant included, answers it. See API → errors.

A plugin call refused by the forwarder carries its own plugin.* keys — see Contract flows → Answering a plugin field.

The full, always-current error set is in the OpenAPI spec — open the relevant spec in the API explorer. Messaging keeps its own refusal table at API → messaging errors (entitlement, suspension, the broadcast cap, the 1-on-1 rate limit and the acknowledgement boundary). The SDKs map HTTP/error responses to typed exceptions (ApiError, AuthError, RateLimitError, WebhookError, ConfigError).

Raw encryption spec (non-SDK integrators)

If you decrypt person data without an SDK, here is the exact contract. The SDKs implement all of this for you — this section is for teams writing their own decryption.

For the full picture — the scheme in depth, why company data is stored plaintext, and how clients store data as ciphertext-at-rest — see Encryption & data handling. The contract below is the condensed version.

The {"_enc":1,…} value wrapper

Every person→service value is a hybrid-encrypted JSON wrapper, encrypted for your service public key:

{
  "_enc": 1,
  "k": "base64( RSA-OAEP-SHA256( aes_key, service_public_key ) )",
  "iv": "base64( 12-byte AES-GCM IV )",
  "d": "base64( AES-256-GCM ciphertext, 16-byte tag appended )"
}

To decrypt:

  1. Base64-decode k and RSA-OAEP-decrypt it with your service private key to recover the 32-byte AES key. OAEP uses SHA-256 (MGF1-SHA256) — pin the hash explicitly; do not accept a SHA-1 default.
  2. Base64-decode iv (12 bytes) and d. The last 16 bytes of d are the GCM tag.
  3. AES-256-GCM-decrypt d with the AES key + IV → the plaintext (a string, or JSON to parse per the field type).
Two different OAEP hashes. The inline value wrapper above uses OAEP-SHA256. The webhook account-key envelope (encrypt_payload) uses OAEP-SHA1 (OpenSSL's default). They are deliberately different — don't reuse one routine for both. For the envelope you can decrypt with openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep (default SHA-1).

The service private key (PEM)

Your service private key is downloaded from the service's Overview tab in the portal as an OpenSSL-compatible encrypted PKCS#8 PEM (PBES2: PBKDF2-SHA256 + AES-256-CBC), protected by the passphrase shown once at service creation (recoverable from a saved copy — Recovery). Decrypt it to a usable key with the passphrase, e.g.:

# decrypt the downloaded service PEM with its passphrase
openssl pkcs8 -in service-CRM.pem -passin pass:"$ALLUS_KEY_PASSPHRASE"
# → -----BEGIN PRIVATE KEY----- … (use this to RSA-OAEP-decrypt the "k" field above)

The SDKs load this PEM in memory at startup (from service_private_key + key_passphrase in config.json) and never write the decrypted key back to disk.

Glossary

TermMeaning
ServiceThe unit a person connects to. Owns request/share config, webhooks, an API client, and its own keypair. Connecting always happens via a service.
Connect handleCOMPANYCODE/SERVICECODE — your company profile share code + the service share code. The company code alone is never valid.
Company connectionOne pair-anchor per person↔company pair; created with the first service connection and kept when the last one ends, so a reconnect reuses it. Removed only by the hard purge.
Service connectionA person's link to one service. The unit your /api/company-data/connections list enumerates and DELETE /api/company-data/connections/{connectionId} ends.
Request fieldA slot you ask a connecting person to fill (by type, with an optional suggestion + flags). Identified by your slug in every payload; its internal service_field_id appears only in a binary answer's value_url.
Share fieldA value your service shares back with connections — plaintext.
Company profileYour company-level data, shared with every connection on any of your services — plaintext.
Live vs. one-timeA connected (live) answer auto-updates when the person edits their field; a one-time answer is a frozen snapshot that never updates.
Consent requestA request to an existing connection to provide a newly added request field; declining is grandfathered (the connection stays active).
Change feedThe drain-on-fetch FIFO outbox of person→service changes (GET /api/company-data/changes); the same events webhooks push.
PluginAn HTTPS service a company adds in the portal that answers a field — on a flow step, a request row or a sign-in claim — through picks, typed values and computed outputs. Called only through the platform's forwarder, sealed both ways (Building plugins).
Zero-knowledgePerson→service values are encrypted to your service key; the platform never sees the plaintext or your private key.
Ward / parental gatingFor minor accounts, connect/consent actions are routed through a custodian's approval before they take effect.

Status

The API surface is defined by the generated OpenAPI specs — those are the source of truth. They are generated from the code and regenerated as part of every change that touches an endpoint, so treat the spec as authoritative over any prose on this site.

The live API spec

The interactive OpenAPI explorer hosts five specs in one dropdown: service (the primary integration surface), portal (the advanced config API), plus complete, client, and admin. Deep-link to a spec with ?spec=:

“Complete API (public)” is complete for public use. It documents every endpoint of the platform except the internal administration surface (/api/admin/*) and the development-only debug routes, which are not part of any integration and are deliberately not published. Nothing an integration can call is missing from it.

The service spec is generated from the same pattern list your registered client is actually granted (the whitelist table), so it documents your whole reach and nothing you cannot call.

Open the API explorer ↗