Registration
ComplyOnce staff create your relying party and decide what identity data it may request; there is no self-service sign-up. Everything after that — your client secret, your redirect URIs, and who else from your organisation can change them — you manage yourself in the relying-party portal.
Until your relying party exists and has a secret, nothing authenticates: POST /relying-party/v1/session returns a bare 401, with no error body to read.
Getting into the portal
You sign in to the portal with the ComplyOnce app, the same way your own users will sign in to you. So whoever administers your integration needs the app installed before anything else.
ComplyOnce adds the first person from your organisation: sign in to the portal once, copy your user id from your profile, and send it to ComplyOnce. The portal learns nothing about you beyond that id — no name, no email — which is why it has to be handed over rather than looked up.
After that you invite the rest yourself. An invitation is a one-time link valid for 24 hours, and it is addressed to nobody in particular: whoever opens it and signs in becomes a member. It is exactly as private as the channel you send it over.
What you get
| Value | Where it comes from |
|---|---|
client_id |
A UUID, assigned when ComplyOnce creates your relying party. It is the HTTP Basic username you authenticate with — not a name you choose. |
client_secret |
Generated in the portal and shown once. ComplyOnce keeps only a hash, so a lost secret can be replaced but never recovered. |
name |
Set by ComplyOnce staff. This is shown on the user's phone when they confirm a login, so it must be the name they know your service by, not an internal project code. Ask for it to be changed if it is wrong. |
redirect_uri |
You add these yourself in the portal, as many as you need. See below. |
Because the client_id is a UUID, the credential you build looks like this
rather than like a chosen name:
0f9c2b7e-6d41-4b0a-9c8e-5a2f1d3b7e40:8Ky2…
A username that is not a UUID fails authentication outright — it never reaches an endpoint, so there is no error body explaining it. See Authentication.
The redirect URI is matched exactly
The backend compares the redirect_uri you send at the start of each login
against the registered value as an exact string — no prefix matching, no
wildcards, no normalisation. A mismatch fails with 400 and
errorCode: invalid_request.
So these are all different URIs, and only the registered one works:
https://portal.example.com/auth/callback
https://portal.example.com/auth/callback/
http://portal.example.com/auth/callback
https://portal.example.com:443/auth/callback
The portal stores what you type, character for character, for that reason. It
rejects only what cannot work as a redirect target: a URI must be absolute and
carry its scheme, must not contain a #fragment (which would swallow the
authorization code), and must be at most 2000 characters. One relying party may
hold up to 20.
Practical consequences:
- Register every environment separately. Production, staging, and each
developer's local URL are distinct entries against the same
client_id. - Pin your development port. If your dev server picks a different port when the usual one is busy, the redirect URI silently stops matching and login breaks. Configure the port so it fails loudly instead of moving.
localhostis not usable for phone-based testing. The user's phone is involved in this flow, and a visitor redirected tolocalhostlands on their own machine. For testing across devices, register your machine's LAN address (http://192.168.1.50:5175/auth/callback).
Identity data you may request
A login returns a subjectId by default. If you also want identity attributes
or predicates, ask ComplyOnce: which keys a relying party may request is a staff
decision, not a self-service one, and it is recorded as a per-client allow-list.
A login may then request only from that set; asking for anything else fails with
invalid_scope.
| Grantable key | Kind |
|---|---|
name, personal_code, date_of_birth, country |
Attribute |
age_at_least, country_in |
Predicate |
Grants are by type, so being allowed age_at_least allows it for any number of
years. The and / or composite predicates need no grant of their own. See
Identity properties for how the request and the
returned data are shaped.
Keeping the secret
The client_secret is sent from your server to the ComplyOnce backend as the
HTTP Basic password, on every call — see Authentication.
It must not appear in browser JavaScript, in a mobile app binary, in a query
string, or in a log. Load it from the environment or a secret manager — not from
source control.
You can generate a new one in the portal at any time, which is what to do if it
leaks. Rotation is not overlapping: there is one secret per relying party,
so the old one stops working the moment the new one appears. Between those two
moments every call you make returns 401, so treat it as a short deployment
rather than a background task — have the new value ready to roll out before you
press the button.
The legacy relying_party.token is gone
If you have an older ComplyOnce integration, you may hold a token UUID. It no
longer exists: the column has been dropped, and the push-based signing endpoint
that used it now authenticates with client_id / client_secret like everything
else — see Signing flow.
If that token was your only credential, you need a client_id and
client_secret before anything will authenticate.
Configuration checklist
Once registered, your server needs:
COMPLYONCE_CLIENT_ID=0f9c2b7e-6d41-4b0a-9c8e-5a2f1d3b7e40
COMPLYONCE_CLIENT_SECRET=… # from a secret store, never in git
COMPLYONCE_BACKEND_URL=https://… # every ComplyOnce call goes here
PUBLIC_BASE_URL=https://portal.example.com
PUBLIC_BASE_URL matters more than it looks: your redirect URI is derived from
it, so it has to be the origin the visitor's browser reaches you on. Deriving
it from the request host instead makes the redirect URI vary, and a varying
redirect URI cannot exact-match a registered one.
Next: Authentication, then the login flow.