Skip to main content

OAuth Integrations

The /oauth/* endpoints are a different mechanism from your API token. They don't authenticate you against the Oho API — they help Oho obtain and store credentials for third-party providers on your behalf. They run in the operator's authenticated browser session, so they carry no API token or signature of their own (security: [] in the spec). You will rarely call them directly; the Oho app drives them during integration setup. They're documented here so the flow isn't a black box.

Looking for your API token?

This page covers the server-side proxy endpoints that broker third-party integrations. For the bearer token you send on every API request — scopes, 401 vs 403, and rotation — see Authentication & Tokens.

There are two flows today, defined in api/v1/oauth.openapi.yaml.

JobAdder token exchange

POST /oauth/jobadder/token is a CORS-safe, server-to-server proxy for the OAuth authorization_code exchange against https://id.jobadder.com/connect/token. The browser holds the one-time authorization code but never the client secret — it sends only a clientSecretRef, the name of an encrypted secret stored server-side (created in Settings → Secrets), which Oho resolves and decrypts before calling JobAdder. The 200 response is a verbatim passthrough of JobAdder's token JSON.

curl -sS -X POST "$OHO_BASE/oauth/jobadder/token" \
-H "Content-Type: application/json" \
-d '{
"code": "<one-time-authorization-code>",
"clientId": "<jobadder-client-id>",
"clientSecretRef": "jobadder-client-secret",
"redirectUri": "https://<tenant>.weareoho.com/oauth/jobadder/callback"
}'
200 — passthrough of JobAdder's token response
{
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 3600
}

redirectUri must match the one used in the authorize step exactly. Replace <tenant> with your organisation's subdomain — the Oho app is served per-tenant at <tenant>.weareoho.com, and the callback path the app registers is /oauth/jobadder/callback. Notable failure modes:

StatusMeaning
400clientSecretRef couldn't be resolved or decrypted
504JobAdder didn't respond within 15 seconds
500Unexpected internal error during the exchange
defaultJobAdder rejected the exchange (e.g. 401/403); its status is passed through, with the body wrapped in Oho's error envelope

The NCC (police-check provider) flow is a browser-redirect consent handshake across two endpoints, scoped to one organisation:

GET /oauth/ncc/authorize?organizationUrn=... mints a CSRF state, builds the NCC consent URL (response_type=code, client_id, redirect_uri, scope, state), and returns a 302 redirect to it. The operator approves consent at NCC. If the deployment's internal NCC app or authorize URL isn't configured, it returns 503 instead.

curl -sS -i "$OHO_BASE/oauth/ncc/authorize?organizationUrn=urn:li:organisation:org_V1StGXR8Z5jdHi6B"
# 302 Location: https://<ncc-consent-url>?response_type=code&...

GET /oauth/ncc/callback is the redirect target NCC sends the operator back to. It validates the state, exchanges the code for tokens, and persists the refresh token as an encrypted per-tenant secret, updating the organisation's police-check settings to point at it. This is the provider credential Oho rotates against silently from then on, so the operator never handles the NCC token themselves.

callback parameters
GET /oauth/ncc/callback?code=<auth-code>&state=<csrf-state>
# or, on denial:
GET /oauth/ncc/callback?error=access_denied&error_description=<msg>

The callback always returns 200 with a small self-closing HTML page that postMessages the result back to the window that opened it:

// posted to window.opener — the message always carries type, ok, and error
{ type: "policecheck.ncc.oauth", ok: true, error: "" }
// every failure mode — consent denied, missing/expired state, expired session,
// exchange or persistence failure — also returns 200, with ok: false and a message
{ type: "policecheck.ncc.oauth", ok: false, error: "<msg>" }

Because there is no non-200 path, the opener window must branch on the ok field rather than on an HTTP status.

Not in the live Swagger UI

The JobAdder and NCC controllers are annotated @Hidden, so they do not appear in the live springdoc Swagger UI or the API Reference. api/v1/oauth.openapi.yaml is the hand-maintained contract for them — treat it as the source of truth.

Where to go next