DeepCredentials authenticates B2B callers as DeepAdmin service users. There is no portal-side credential to mint — identity is delegated to the DeepCloud Identity Provider (Keycloak), and every B2B request carries a short-lived JWT obtained from there.
- Your partner client is a Keycloak client created for you by the DeepCloud team. It identifies your integration globally.
- Service users are accounts created under your partner client, typically one per end-client organization you serve. Each one has its own
username+passwordthat your code uses to authenticate. - Organizations are the DeepAdmin entity that resources are scoped to. A service user can be a member of one or many organizations.
On every call, the portal verifies that the service user behind the bearer token is a member of the organization you supply in the X-Org-Id header.
A realm in Keycloak is an isolated tenant inside a Keycloak deployment — its own users, clients, scopes, and signing keys. DeepCloud's realm is named sso, and each environment (int for testing, prod for live) runs its own Keycloak deployment with its own sso realm. A service user provisioned against int.deepcloud.swiss is not valid against deepcloud.swiss and vice versa.
Provisioning happens in DeepCloud, not in DeepCredentials. The canonical, always-current walkthrough is DeepCloud — API Access Setup and Authorization Overview. Read it for the URLs, the form parameters, the authorization-method options, and the credential-delivery modes — we deliberately don't duplicate them here so they can't go stale.
The high-level shape (use the left-nav on the DeepCloud page to jump to each step):
- API Access Setup — the DeepCloud team gives you a
Partner-Service-Client-IDandPartner-Service-Client-Secret(or a JWKS configuration). When you contact them, also ask them to allow thedeepcredentials.*scopes you'll need (see the Scopes table below). - Service User Creation — for every end-client organization you serve, that organization's owner authorizes your partner client and you receive a
service_account_username+service_account_passwordbound to that org. - Store the credentials in your own secret manager — one client-level secret, plus one service-user credential pair per end client.
Use the OAuth 2.0 Resource Owner Password Credentials grant against DeepCloud's Keycloak token endpoint, requesting the scopes your endpoints need:
curl -X POST "https://deepcloud.swiss/auth/realms/sso/protocol/openid-connect/token" \
--data-urlencode "grant_type=password" \
--data-urlencode "username=<service_account_username>" \
--data-urlencode "password=<service_account_password>" \
--data-urlencode "client_id=<Partner-Service-Client-ID>" \
--data-urlencode "client_secret=<Partner-Service-Client-Secret>" \
--data-urlencode "scope=deepcredentials.issue deepcredentials.verify deepcredentials.self-service"The scope parameter is a space-separated list of the OAuth scopes you want on the returned token; ask for the ones your integration uses. If DeepCloud's partner-client config doesn't allow a scope, Keycloak silently drops it from the response — always inspect the scope claim on the issued token to confirm.
For the test environment, replace deepcloud.swiss with int.deepcloud.swiss.
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs…",
"expires_in": 900,
"refresh_expires_in": 36000,
"refresh_token": "eyJhbGciOiJIUzI1NiIs…",
"token_type": "Bearer",
"scope": "deepcredentials.issue deepcredentials.verify deepcredentials.self-service"
}Access tokens are valid for expires_in seconds (900 = 15 min). Cache them and re-use until close to expiry. Re-fetching for every API call drives unnecessary load on DeepCloud's IdP and adds avoidable latency. A common pattern is to refresh when less than 30 seconds of validity remain. The refresh_token lets you extend without re-sending username/password.
The JWKS-based client-assertion variant (signs a JWT with your private key instead of presenting a client_secret) is covered under Token Endpoint in DeepCloud — API Access Setup and Authorization Overview — switch the Authorization Method selector on their page to "JWKS" to surface the relevant request shape and code samples.
You do not need to configure an audience on your side — Keycloak attaches a default aud claim that the portal trusts.
Send three headers on every /b2b/v1/* request:
| Header | Value |
|---|---|
Authorization | Bearer <access_token> from the token response above. |
X-Org-Id | UUID of the organization this call is acting on. The service user must be a member. |
X-Environment | beta or production. Resources are isolated per environment. |
A worked example:
- Developmenthttps://api.dev.deepcredentials.swiss/b2b/v1/health
- Integrationhttps://api.int.deepcredentials.swiss/b2b/v1/health
curl -i -X GET \
https://api.dev.deepcredentials.swiss/b2b/v1/health \
-H 'Authorization: Bearer <YOUR_JWT_HERE>' \
-H 'X-Environment: beta' \
-H 'X-Org-Id: 497f6eca-6276-4993-bfeb-53cbbbba6f08'| Scope | Grants access to |
|---|---|
deepcredentials.issue | Create credential offers, list and inspect issued credentials, suspend / revoke / reactivate. |
deepcredentials.verify | Create and read OID4VP verification sessions. |
deepcredentials.self-service | Read and decide on end-user-initiated self-service requests. |
| Endpoint | Required scope |
|---|---|
POST /b2b/v1/credential-offers | deepcredentials.issue |
GET /b2b/v1/credential-offers[/...] | deepcredentials.issue |
GET /b2b/v1/issued-credentials[/...] | deepcredentials.issue |
PATCH /b2b/v1/issued-credentials/:id/status | deepcredentials.issue |
POST /b2b/v1/verification-sessions | deepcredentials.verify |
GET /b2b/v1/verification-sessions[/...] | deepcredentials.verify |
GET /b2b/v1/self-service/requests/:requestId | deepcredentials.self-service |
POST /b2b/v1/self-service/requests/:requestId/decision | deepcredentials.self-service |
GET /b2b/v1/health | — (authenticated, no scope required) |
Only request the scopes your integration uses. If you only verify identities, scope=deepcredentials.verify is sufficient.
| Status | Code | Cause |
|---|---|---|
400 | invalid_argument | Missing or malformed X-Org-Id / X-Environment header, or a missing required field in the request body. |
401 | unauthenticated | Bearer token failed validation: bad signature, expired (exp in the past), wrong issuer, or wrong audience. |
403 | permission_denied | The token doesn't carry the scope the endpoint requires. Inspect the scope claim on the issued token — if a scope you requested is missing, the partner client isn't configured to grant it (contact DeepCloud). |
404 | not_found | Either the resource ID is unknown, or the X-Org-Id you supplied isn't an organization the authenticated service user is a member of. The portal returns the same code for both to avoid leaking organization existence. |