Register a Client
Before your app can log users in, it needs an OAuth client registered in Camel Accounts: a client_id, one or more exact redirect_uris, and an allowed scope list.
There are two ways to create one:
- Developer Console UI — sign in at
https://console.camelcreatives.comand use the projects/clients screens (recommended) - Console API — everything below can also be done over HTTP with a session cookie
Both are the same backend. This page shows the API path so you can script it.
Client types: public vs confidential
public | confidential | |
|---|---|---|
Has a secret (cask_…) | No — PKCE is the only protection | Yes, required at /oauth/token |
| Typical use | SPAs, mobile apps (custom-scheme redirects) | Server-rendered apps / backends that can keep a secret |
| Token endpoint auth | client_id + code_verifier only | + client_secret |
| Created via console API | "client_type": "public" | "client_type": "confidential" |
All current first-party clients (Bajeti/Simamia) are public. If you build a traditional server-side app with a private backend, choose confidential and store the secret in your server's environment — never in frontend code.
1. Create a project
Clients live inside projects. Each owner may hold up to 25 projects.
curl -s -b cookies.txt -X POST "$ISSUER/console/projects" \
-H "Content-Type: application/json" \
-d '{"name":"Bajeti"}'{
"success": true,
"data": {
"id": "9f8e7d6c-5b4a-3210-fedc-ba9876543210",
"name": "Bajeti",
"slug": "bajeti",
"created_at": "2026-08-26T09:30:00Z",
"archived": false
}
}The slug becomes part of every client ID you create in this project.
2. Create the client
curl -s -b cookies.txt -X POST "$ISSUER/console/projects/$PROJECT_ID/clients" \
-H "Content-Type: application/json" \
-d '{
"name": "Bajeti Web",
"client_type": "public",
"redirect_uris": ["https://bajeti.cameltech.co/callback"],
"allowed_scopes": ["openid", "profile", "email"]
}'{
"success": true,
"data": {
"client": {
"id": "bajeti-web-x7k2p9",
"name": "Bajeti Web",
"type": "public",
"status": "testing",
"redirect_uris": ["https://bajeti.cameltech.co/callback"],
"allowed_scopes": ["openid", "profile", "email"],
"has_secret": false,
"created_at": "2026-08-26T09:31:12Z"
},
"client_secret": null
}
}Notes:
- The
idis generated for you as<project-slug>-<6 random chars>(e.g.bajeti-web-x7k2p9) — this is yourclient_id. - New clients start in status
testing(see publishing below). - Up to 25 clients per project, each with 1–20 redirect URIs.
Confidential? Copy the secret NOW
For "client_type": "confidential" the same call returns the plaintext secret exactly once:
{
"client": { "id": "bajeti-api-m3v8qk", "...": "..." },
"client_secret": "cask_9tX4kPqWmE7rJ2nF6hD0sY5uLbGzA1cV8oN4iK3xMwT"
}The value is shown once, then only stored as a SHA-256 hash. Lose it → rotate it.
3. Redirect URI rules
Validation is strict — these rules are enforced at creation time by the console API:
| Rule | Detail |
|---|---|
| Count | 1–20 URIs |
| Length | ≤ 512 characters each |
| Format | Absolute URI, scheme://… |
| Allowed schemes | https, http (dev), or a custom app scheme like bajeti (lowercase letters/digits/dots/dashes) |
| Wildcards | Forbidden — no * or ? anywhere |
Matching at authorization time is exact string equality against this list. https://app.example.com/callback/ does not match https://app.example.com/callback.
Platform conventions:
| Platform | Redirect URI style | Example |
|---|---|---|
| Android | Custom scheme | bajeti://oauth/callback |
| Web SPA | HTTPS URL | https://bajeti.cameltech.co/callback |
| React widget (SDK) | Issuer widget callback | https://accounts.camelcreatives.com/widget-callback |
Register one client per platform (your-app-android, your-app-web) even if it's conceptually the same app — different schemes need different registrations, and it lets users revoke just their mobile connection.
4. Scopes
Pick from the supported set: openid, profile, email, phone. Whatever you allow here bounds what the app may request at /oauth/authorize; requesting anything outside allowed_scopes is rejected.
See Scopes & UserInfo for what each scope returns.
5. Publish to production
Clients start in testing. Status transitions:
testing ──► production ──► disabled
│ ▲ │
▼ │ │
testing ◄──────────────── disabledtesting → productionrequires at least onehttps://redirect URI and ahomepage_url— the API rejects otherwise.disabledclients are refused at/authorize, consent re-check, and token exchange.
curl -s -b cookies.txt -X PATCH "$ISSUER/console/projects/$PROJECT_ID/clients/$CLIENT_ID" \
-H "Content-Type: application/json" \
-d '{"status":"production","homepage_url":"https://bajeti.cameltech.co"}'Rotate a secret
curl -s -b cookies.txt -X POST \
"$ISSUER/console/projects/$PROJECT_ID/clients/$CLIENT_ID/rotate-secret"{
"success": true,
"data": {
"client": { "id": "bajeti-api-m3v8qk", "...": "..." },
"client_secret": "cask_NewValue43charsBase64url…",
"note": "copy it now — it will not be shown again; the previous secret is already invalid"
}
}Rotation invalidates the old secret immediately — plan a coordinated deploy.
Housekeeping
- Archive a project:
DELETE /console/projects/{project_id}— soft-archive that instantly disables every client inside it. - Delete a client:
DELETE …/clients/{client_id}— cascades: all refresh-token families, grants, and live access tokens for that client die instantly. - Audit trail: every mutation is appended to
GET /console/projects/{project_id}/audit-logs. - Quotas: 25 projects per owner, 25 clients per project.
Full endpoint details: Developer Console API.