Skip to content

Authentication

DIDHub APIs use two authentication mechanisms depending on how you're calling.

MechanismUse it forWhere it goes
Session cookieThe DIDHub dashboard SPA (browser sessions for human users)didhub_sid cookie, set by /v1/auth/login
Bearer API keyServer-to-server calls from your backend to DIDHubAuthorization: Bearer didhub_live_… header

For most integrations you want a Bearer API key. The session cookie is intended for the official dashboard at https://dashboard.didhub.io.

API keys

Sign in to the dashboard and go to Settings → API keys → Create key.

Each key has:

  • Name — what you'll see it as in the list (free-text label, e.g. Production webhook signer)
  • Scopes — currently all keys are scope * (full access); granular scopes are on the roadmap
  • Expiry — optional; DIDHub will email a reminder 14 days before the date
  • Prefix display — the visible portion (didhub_live_UQcs…) so you can identify a key without seeing the secret

The raw key is shown once at creation time. Store it immediately — there's no way to retrieve it later. If you lose it, revoke it and create a new one.

text
Authorization: Bearer didhub_live_UQcsmrT6I5FS18Z6xwgHYjVYq37X43g6WatYHVrL3k8

Treat API keys like passwords

WARNING

  • ✅ Store in a secrets manager (1Password, Vault, AWS/GCP Secrets Manager)
  • ✅ Rotate periodically — set an expires_at when creating a key
  • ✅ Use a separate key per application or environment so revocation is targeted
  • ❌ Never commit a key to git
  • ❌ Never paste a key into a chat / ticket / screenshot
  • ❌ Never put a key in a URL query string (it ends up in server logs)

Beta

Programmatic API keys are currently in beta. The issuance + revocation endpoints work today, and we're staging the request-authentication middleware for general availability. Contact support@didhub.io to enable API keys for your account.

Revoking a compromised key

Either:

  • Dashboard: Settings → API keys → click the key → Revoke. Effect is immediate.
  • API: DELETE /v1/api-keys/{id} with a still-active key or session cookie.

OAuth 2.0 — authorizing a third-party app

If you're building an application that DIDHub users will sign into with their own DIDHub account — a Zapier integration, a desktop client, a multi-tenant SaaS — use OAuth, not API keys. DIDHub acts as the OAuth 2.0 Authorization Server; your app is the client.

The flow is Authorization Code with PKCE (RFC 6749 + 7636). PKCE is mandatory — there is no implicit-grant path, no code_challenge_method=plain, no skipping code_challenge. This applies to confidential clients too.

1. Register your app

Sign in to the dashboard and go to Settings → OAuth apps → Register OAuth app. You'll provide:

  • Name — shown to users on the consent screen.
  • Description / homepage URL / logo URL — also shown on the consent screen. Optional but recommended; users are far more likely to authorize an app they recognize.
  • Redirect URIs — one per line. Must match exactly at /oauth2/authorize. https:// only, except http://localhost / http://127.0.0.1 (any port) for local development. Custom schemes like com.example.app://oauth are allowed for native apps.
  • Scopes — the maximum set of scopes the app is allowed to request. Users may further narrow this on the consent screen.

On submit you receive a client_id and a client_secret. The secret is shown once — store it immediately in your secrets backend. If you lose it, click Rotate secret to mint a new one (the previous secret is rejected immediately).

2. Send the user to /oauth2/authorize

Generate a PKCE pair (code_verifier is a 43–128 character random string; code_challenge is BASE64URL(SHA256(code_verifier))), then redirect the user's browser to:

text
https://api.didhub.io/oauth2/authorize?
  response_type=code
  &client_id=didhub_oauth_…
  &redirect_uri=https://app.example.com/oauth/callback
  &scope=numbers:read%20cdrs:read
  &state=<random-csrf-token>
  &code_challenge=<base64url-sha256-of-verifier>
  &code_challenge_method=S256

state is a random string you generate and verify on the callback — protects against CSRF, per RFC 6749 §10.12.

The user lands on DIDHub's consent screen at dashboard.didhub.io/oauth-consent. If they're not logged in, they sign in first. They click Authorize, and DIDHub redirects back to your redirect_uri:

text
https://app.example.com/oauth/callback?code=<one-shot-code>&state=<your-state>

If they click Cancel, you receive ?error=access_denied&error_description=…&state=….

3. Exchange the code for tokens

Server-side, POST to /oauth2/token with Content-Type: application/x-www-form-urlencoded:

http
POST /oauth2/token HTTP/1.1
Host: api.didhub.io
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<one-shot-code-from-callback>
&redirect_uri=https://app.example.com/oauth/callback
&client_id=didhub_oauth_…
&client_secret=didhub_oauths_…
&code_verifier=<the-original-pkce-verifier>

Client credentials can also go in an Authorization: Basic header (base64(client_id:client_secret)); see RFC 6749 §2.3.1.

Response:

json
{
  "access_token": "eyJhbGciOiJIUzI1NiJ9...",
  "refresh_token": "didhub_oauthr_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "numbers:read cdrs:read"
}
  • The access token is a JWT, 1 hour lifetime by default. Send it as Authorization: Bearer eyJ… to any /v1/* endpoint that the granted scopes cover. It is stateless — /v1/* verifies it without a DB hit (other than checking the refresh-row hasn't been revoked, which is cached).
  • The refresh token is opaque. Store it securely server-side. Use it to mint a new access token before the current one expires.

Codes are single-use. Replaying a code revokes the access + refresh tokens it originally minted (RFC 6749 §10.5) — your integration is then broken until the user re-authorizes.

4. Refresh before the token expires

http
POST /oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=didhub_oauthr_…
&client_id=didhub_oauth_…
&client_secret=didhub_oauths_…

Returns a fresh access_token and a new refresh_token — DIDHub rotates refresh tokens on every use (RFC 6749 §10.4 best practice). Save the new refresh token and discard the old one. If you ever present the old (rotated) refresh token again, DIDHub detects the replay and revokes the entire token chain for that grant; the user has to re-authorize.

You can narrow scope on refresh (&scope=numbers:read) but you cannot widen it — widening requires a fresh /oauth2/authorize.

5. Revoke a token

http
POST /oauth2/revoke HTTP/1.1
Content-Type: application/x-www-form-urlencoded

token=didhub_oauthr_…
&client_id=didhub_oauth_…
&client_secret=didhub_oauths_…

Per RFC 7009, this endpoint always returns 200 (so attackers can't probe for valid tokens by status code). Only refresh tokens can be revoked here — access tokens are stateless JWTs that expire naturally within ~1 hour.

Users can also revoke from their dashboard at Settings → OAuth apps → Connected applications → Disconnect. That revokes the consent grant + all live refresh tokens for that user/app pair.

Discovery (.well-known)

If your OAuth library auto-configures from a discovery URL, point it at:

text
https://api.didhub.io/.well-known/oauth-authorization-server

That returns the RFC 8414 metadata: endpoint URLs, supported grant/response types, supported scopes, supported PKCE methods.

OAuth scopes

ScopeWhat it grants
account:readRead your DIDHub account (name, email, country, settings)
account:writeUpdate your DIDHub account
numbers:readList phone numbers, status, routing
numbers:writeOrder, route, attach compliance, release numbers
sip_trunks:read / sip_trunks:writeRead / manage SIP trunks + gateways
routing:read / routing:writeRead / manage call-routing profiles
cdrs:readList Call Detail Records
billing:readRead balance, transactions, saved payment methods
compliance:read / compliance:writeRead / manage end-users, documents, bundles

:write scopes implicitly grant :read for the same resource — granting numbers:write is enough to call GET /v1/numbers.

Intentionally not OAuth-grantable: billing:write (moving money — too sensitive for OAuth; use a session cookie or hand-issued API key) and oauth:apps (managing OAuth apps via OAuth would be a privilege-escalation loop).

When to use OAuth vs API keys

You're building…Use
Your own backend talking to your own DIDHub accountAPI key
An integration where each end-user logs in with their own DIDHub accountOAuth
A multi-tenant SaaS that resells DIDHub to its customersOAuth, one app per integration
A CLI / dashboard for your teamAPI key (or session cookie if it's a browser tool)

API keys never expire on their own, can't be granularly scoped per-user, and don't give the user a "Disconnect this app" UI. They're the right tool when you are the customer; OAuth is the right tool when your users are the customer.

Errors

A missing or invalid token returns 401 Unauthorized with the standard error envelope:

json
{
  "ok": false,
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key"
  }
}

A valid token without the required scope returns 403 Forbidden:

json
{
  "ok": false,
  "error": {
    "code": "forbidden",
    "message": "API key lacks scope: numbers:write"
  }
}

Validation errors return 422 Unprocessable Entity with a fields object showing per-field problems:

json
{
  "ok": false,
  "error": {
    "code": "invalid_input",
    "message": "Invalid input",
    "fields": { "country": "Required, 2-letter ISO code" }
  }
}

See the interactive API explorer for per-endpoint details.