---
name: smartrecruiters-oauth-app
description: Establishes which SmartRecruiters credential a connector actually needs and obtains it — customer-generated SmartToken API keys and customer-generated OAuth Client Credentials (both self-serve, Administrator-only, per-company), the partner-gated General Partner Integration that replaced the now-deprecated Authorization Code flow, and legacy Partner API Keys — with the scope catalogue, the System role rule, rate limits and a safe credential handoff. Use when asked to get SmartRecruiters OAuth credentials or an API key, register a SmartRecruiters app, become a SmartRecruiters partner, get listed on the SmartRecruiters Marketplace, or fix a SmartRecruiters auth error like a 401 on every call, a 403 on users or audit endpoints, an `OAuthPermissionsException`, a connection that dies after 28 idle days, or `429` under light load. For any other vendor's developer portal, use that vendor's skill instead.
---

# SmartRecruiters API Credentials

**Read this first: SmartRecruiters has no self-serve developer portal where you register a redirect-based OAuth app
and walk away with a client ID and secret.** There is no "create app" button for a multi-tenant integration. The
registration page says it plainly: to build applications for SmartRecruiters customers and offer them in the
Marketplace "you need to have a partner account," and "all integration requests should be submitted through this
channel" — the Partner Hub. Credentials are issued by hand, by the SmartRecruiters Partner & API Team, after they
review your requested scopes.

**And the flow most people arrive here looking for is closed to newcomers.** SmartRecruiters' Authorization Code page
carries a sunset notice: *"As of January 30, 2026, the Authorization Code flow is deprecated. We are no longer
onboarding new partners with this method. All new integrations must use the OAuth 2.0 General Partner Integration."*
If a runbook, a blog post or an existing connector tells you to send a customer to
`https://www.smartrecruiters.com/identity/oauth/allow`, that is the deprecated path (§ Platform state).

What *is* self-serve is different, and it is what almost every SmartRecruiters integration actually runs on today:
**each customer generates their own credential inside their own SmartRecruiters account**, in Credential Manager,
as an Administrator. Two kinds: a **SmartToken API key** (32 characters, no scopes, no expiry, full access to the
company) or an **OAuth Client Credentials** pair (scoped, no expiry, no consent screen, no refresh token). A
multi-tenant platform can run entirely on these — you ask each customer for a credential and store it per
connection. That makes onboarding an **admin runbook**, not an OAuth button, and you should price that into the
plan before promising anyone a one-click connect (§1, §4).

Three more things that cost real time: the **General Partner Integration is not a redirect OAuth flow at all** — you
register a `consentUrl`, not a `redirect_uri`, and the "authorization" is a credential-exchange API call (§4 Path C,
§5); **scopes are necessary but not sufficient** because every OAuth credential also carries a SmartRecruiters
System role and optional Access Group that cap it independently (§6); and the **public Posting API needs no
authentication at all**, which is why a test that "works" against postings tells you nothing about whether your
credential can read a single candidate (§7).

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **Credential model** | Per-customer SmartToken, per-customer OAuth client credentials, General Partner Integration, or legacy Authorization Code? (§1) |
| **Partner status** | Do you have a SmartRecruiters partner account and an Application ID, or are you collecting per-customer credentials? (§3) |
| **Which API surface** | Customer API, Marketplace/Assessment partner APIs, Posting API, Job Board API — they do not share credentials (§1) |
| **Scope set** | The exact scope strings the connector calls, per object and direction (§6) |
| **Integration name**, logo, short + full description | Shown to the customer on the Apps & Integrations page; the Partner & API Team asks for all three (§4) |
| **consentUrl** and **consentDisplayMode** | `REDIRECT` or `POPUP`; required for General Partner Integration, and neither is a `redirect_uri` (§5) |
| **oauthTokenUrl / callbackUrl** | Only if the integration is bi-directional or you want disable notifications (§4) |
| **Redirect URI(s)** | Every callback host, final, up front — but only if you are an existing Authorization Code partner (§5) |
| **Visibility** | Public to all customers, or private to named ones — you confirm this with the Partner & API Team (§4) |
| **A SmartRecruiters tenant to test against** | SmartRecruiters publishes no self-serve developer tenant (§3) |

## Quick Start

1. Establish which **credential model** this actually needs (§1). Nearly every wrong turn here starts at this step.
2. Read the **Platform state** section before quoting anyone a flow — the Authorization Code path is deprecated.
3. Check whether existing credentials are worth reusing; re-issuing partner credentials orphans every connection (§2).
4. If you need the General Partner Integration: apply through the Partner Hub. This gates everything else (§3).
5. Obtain the credential — the customer's Credential Manager, or the Partner & API Team (§4).
6. Register `consentUrl` / `consentDisplayMode` (partner) or every callback host (legacy auth code) in that same
   request; you cannot self-serve either later (§5).
7. Pin down scopes **and** the System role on the credential — both are required, neither substitutes (§6).
8. Check rate limits and the concurrency limiter before you promise a sync cadence (§7).
9. Verify against a **second** company, with a real read of a scoped resource — not the Posting API (§8).
10. Hand the secret to a human, never to source control (§9).

## Platform state (verified 2026-09-20 — re-verify before trusting)

- **The Authorization Code flow is deprecated.** SmartRecruiters' own page, last updated 2026-01-30, states: *"As of
  January 30, 2026, the Authorization Code flow is deprecated. We are no longer onboarding new partners with this
  method. All new integrations must use the OAuth 2.0 General Partner Integration. We also strongly encourage
  existing partners to migrate."* The page is still published and still documents the full flow, and the
  Authorization Code page **no longer appears in the documentation index** — it is reachable only by direct URL.
  SmartRecruiters has published **no sunset date** for it; their deprecation policy promises at least **10 months**
  of lead time before any public endpoint is sunset. Treat existing Authorization Code connections as supported but
  on borrowed time, and do not plan a new integration on it.
- **The General Partner Integration is the current multi-tenant path, and it is Client Credentials, not a redirect
  flow.** Two credential levels: **Partner Level (Master Credentials)** issued once to you, and **Customer Level**
  credentials that SmartRecruiters mints per company. There is no consent screen you host at a `redirect_uri`;
  SmartRecruiters sends the user to a **`consentUrl`** you register, with `companyId` and (in `REDIRECT` mode) a
  `redirect` query parameter.
- **Both partner paths are gated.** Master Credentials and an **Application ID** come from the Partner & API Team,
  after they review your requested scopes against the Principle of Least Privilege, and they are shared "in an
  encrypted way." Registration requests go through the Partner Hub at `https://www.smartrecruiters.com/partners/`,
  which currently 301-redirects to `partnerhub.smartrecruiters.com`. `partners.smartrecruiters.com` is a separate,
  older Partner Portal tied to the legacy **Partner API Key**.
- **Customers are fully self-serve, in their own account.** Credential Manager lives at
  `https://www.smartrecruiters.com/settings/administration/app-management/custom-applications` (login required).
  **New Credential → API Key** gives a 32-character SmartToken; **New Credential → OAuth client ID** gives a scoped
  client ID + secret. Both are Administrator actions, both are shown **once**, and **neither expires** — they are
  revoked, not rotated on a clock.
- **Token endpoints, all paths:** `https://api.smartrecruiters.com/identity/oauth/token`, `POST`, form-encoded, with
  either `client_secret_post` (credentials in the body) or `client_secret_basic` (`Basic base64(clientID:clientSecret)`).
  API base is `https://api.smartrecruiters.com/`. Access tokens are documented at **`expires_in: 1799`** — under
  30 minutes — on every grant.
- **Refresh tokens exist only on the Authorization Code flow**, live **28 days**, and are **single-use and rotating**.
  Client Credentials returns no refresh token at all; you simply mint a new access token.
- **PKCE is not documented on any SmartRecruiters flow.** Do not assume it is supported or required.
- **`developers.smartrecruiters.com` is the live documentation site.** `dev.smartrecruiters.com` still answers but
  302-redirects there; treat any `dev.` link as an alias, not a second source. Appending `.md` to any docs URL
  returns the markdown source, and `https://developers.smartrecruiters.com/llms.txt` is the full page index.
- **SmartRecruiters publishes no self-serve developer sandbox.** The word "sandbox" does not appear in the developer
  documentation index; a **sandbox environment does exist**, because SmartRecruiters publishes its outbound IP
  addresses separately for production and sandbox. Getting into one is a conversation with SmartRecruiters or with a
  customer, not a signup form (§3).

If Credential Manager or the developer docs do not look like this, stop and report what you actually see.

## 1. Which credential, and who issues it

A reader arriving here is usually holding the wrong one. SmartRecruiters runs several API surfaces and the
credentials do not interchange:

| Credential | Who issues it | How it is sent | Reach |
| --- | --- | --- | --- |
| **API Key (SmartToken)** | The customer, self-serve, **Administrator only**, in Credential Manager | `X-SmartToken: <key>` | **All company data.** No scopes, no expiry. SmartRecruiters recommends it "for testing purposes only" |
| **OAuth 2.0 Client Credentials** | The customer, self-serve, in Credential Manager — or SmartRecruiters, per company, under the General Partner Integration | Exchange at `/identity/oauth/token`, then `Authorization: Bearer <token>` | Limited by the credential's **scopes**, its **System role**, and its optional **Access Group**. No refresh token |
| **General Partner Integration** | SmartRecruiters Partner & API Team: an **Application ID** + **Master Credentials**; customer-level pairs are then minted per company | Master token calls the partner integrations endpoint; per-company pairs then behave as Client Credentials | The scopes approved for your application, per company |
| **OAuth 2.0 Authorization Code** | SmartRecruiters, to partners — **closed to new partners since 2026-01-30** | Authorize at `www.smartrecruiters.com/identity/oauth/allow`, token at `api.smartrecruiters.com/identity/oauth/token` | Acts as the **authorizing user** — capped by that person's own SmartRecruiters permissions |
| **Partner API Key** | Auto-generated on Partner Portal signup (`partners.smartrecruiters.com`) | `X-SmartToken: <64-char key>` or an `apiKey` URL parameter | Legacy Marketplace APIs only. SmartRecruiters: "There are no new APIs planned to support this method" |

Four traps follow directly from this table:

- **`X-SmartToken` carries two different, non-interchangeable credentials.** The customer API Key is 32 characters;
  the legacy Partner API Key is 64. SmartRecruiters calls this out by name: *"please don't be confused, these are not
  the same tokens and cannot be used interchangeably."* Ask for the credential by its full name and note its length.
- **"Partner" means two unrelated things.** The General Partner Integration (current, Client Credentials, an
  Application ID) and the Partner Portal's Partner API Key (legacy, Marketplace APIs) share a word and nothing else.
- **A customer's own OAuth client credentials are a perfectly legitimate multi-tenant strategy**, and the one
  available today without a partnership. The cost is onboarding: an Administrator at each customer must create the
  credential, tick the right scopes, assign a System role, and paste a secret they can never see again. Write that
  runbook before you sell the connector, not after.
- **Marketplace/Assessment partner APIs are a separate integration with separate credentials.** They are not a mode
  of the Customer API and cannot call it. Do not try to make one registration cover both (§3).

> **Product fact — dated.** As of **2026-09-20**, this platform ships **two separate SmartRecruiters connectors**,
> and the distinction matters at registration time.
>
> The **ATS/HRIS connector** calls the Customer API at `https://api.smartrecruiters.com/` and supports **three**
> credential paths. (a) A **company API-key mode**: it asks the customer for one value, sends it verbatim in the
> `X-SmartToken` header, and points them at SmartRecruiters' own API-key documentation page to generate it.
> (b) An **OAuth 2.0 Authorization Code mode** against `https://www.smartrecruiters.com/identity/oauth/allow` and
> `https://api.smartrecruiters.com/identity/oauth/token` — the deprecated flow (§ Platform state). Its authorize
> call sends only `redirect_uri`, `client_id`, a **space-delimited** `scope` and `state`; it sends **no
> `response_type` and no PKCE parameters**. The exchange is a form-encoded POST carrying `grant_type`, `code`,
> `client_id` and `client_secret` **in the body** (`client_secret_post`, not Basic), and refresh is the same shape
> with `refresh_token`. It records the access-token lifetime as **1799 seconds** and sends `Authorization: Bearer`.
> (c) A **General Partner Integration path**, exposed as its own endpoint rather than the normal connect flow: it
> mints a master token by `client_credentials` from stored partner credentials, POSTs an application ID plus the
> customer's `companyId` to `https://api.smartrecruiters.com/apps-integrations/partner-api/integrations`, receives
> the per-company `clientId`/`clientSecret`, stores them, and — since Client Credentials issues no refresh token —
> re-mints the access token from that stored pair whenever it expires, keyed on the SmartRecruiters company ID.
>
> Its requested scope set is grouped per unified object and direction and is drawn from `candidates_read`,
> `candidates_create`, `candidates_manage`, `candidate_status_read`, `candidates_offers_read`,
> `candidates_offers_write`, `jobs_read`, `jobs_manage`, `candidate_applications_manage`, `interviews_read`,
> `interviews_write`, `interview_types_read`, `users_read`, `users_manage`, `reviews_read`, `reviews_write`,
> `reviews_delete`, `assessment_orders_read`, `audit_events_read`, `messages_write`, `messages_manage` and
> `webhooks_manage`. Two things to check against §6 before requesting them: **`candidates_offers_write` appears in
> neither SmartRecruiters' Access Scopes catalogue nor its Permission Reference**, and **`user_me_read` is absent
> from the set** even though the connector reads the current-user endpoint immediately after authorization — the
> endpoint SmartRecruiters documents as requiring exactly that scope. List paging uses SmartRecruiters' cursor
> (`pageId` / `nextPageId`), a maximum page of 100, `updatedAfter` for incremental sync and `q` for search.
>
> The **Assessment connector** is the *partner* side of the Marketplace Assessment API and **cannot call the normal
> SmartRecruiters API**. It holds two credential pairs per connection — one SmartRecruiters-issued pair it uses to
> PATCH results back with the `assessment_result_manage` scope, and one pair it issues to SmartRecruiters so
> SmartRecruiters can call it. Registering it is a different track with a different team ask (§3).
>
> **Confirm all of this with the connector's owner before acting on it** — connector configuration changes
> independently of this skill.

## 2. Reuse the existing credentials, or request new ones

Re-issued **Master Credentials or a new Application ID** mean every customer-level pair minted under the old
application is orphaned, and every customer re-runs the consent and exchange. Reuse what exists for: adding or
changing a scope, updating `consentUrl`, rotating a compromised master secret, or diagnosing a failure. All of those
are handled on the existing application by the Partner & API Team (§4).

Request a **new** application only when the user explicitly wants one: a separate product, a replacement for a
compromised application, or a deliberate move off the deprecated Authorization Code flow — which is a migration
project, not a config change, because the credential model underneath is different.

For **per-customer** credentials the calculus is much gentler: each customer's key or pair is independent, so
revoking one affects exactly one company. There is no rotation clock — SmartRecruiters states plainly that neither
an API Key nor a Client Credential has an expiration date. Rotation is a deliberate revoke-and-regenerate, and it is
**immediate and total** for that customer. Say which path you are taking before you start.

## 3. Accounts: the partner programme, and a tenant to test against

**The partner programme.** SmartRecruiters routes every integration request through the **Partner Hub** at
`https://www.smartrecruiters.com/partners/` (which redirects to `partnerhub.smartrecruiters.com`), for new and
existing partners alike. The alternative channel SmartRecruiters names for Marketplace distribution is its **Partner
Success Team** at `partners@smartrecruiters.com`; the same address handles requests to add a standard candidate
**source** for a productised integration, which is worth batching into the same conversation if you create
candidates. What the run needs to know:

- **SmartRecruiters publishes no partner tiers, no mutual-customer minimum, no review timeline, no cost and no SLA**
  on its developer documentation. Do not invent any of them, and do not carry over numbers from another ATS vendor's
  programme. Plan in months and say that you are estimating.
- **The scope review is real and is a gate.** The Partner & API Team will either approve your requested scopes or
  "ask you to adjust requested access scopes based on the Principle of Least Privilege." Arrive with a
  scope-by-capability justification, not a wish list (§6).
- **Visibility is decided with them, not by you.** An application on the General Partner Integration appears on the
  customer's **Apps & Integrations** page and "can be made available to all customers or, if you're building a
  private integration, only to selected ones. You should confirm your application's visibility with the
  SmartRecruiters Partner & API Team." That confirmation is the Marketplace-listing decision; treat it as an explicit
  agenda item rather than something that happens automatically when the app works.
- **The Assessment/Marketplace partner track is separate.** It has its own partner app specification, its own
  configuration registration, and a bilateral credential exchange where SmartRecruiters calls *you*. If the ask
  covers both an ATS integration and an assessment integration, that is **two registrations**.
- The agreement is a **business and legal decision**. Do not fill in revenue, volume, security or compliance claims
  on the user's behalf — collect the questions and hand them back.

**A tenant to test against.** There is no documented self-serve developer tenant, and "sandbox" appears nowhere in
the developer documentation index — although a sandbox environment demonstrably exists, since SmartRecruiters
publishes separate outbound IP ranges for production and sandbox. In practice that leaves three options, in
descending order of safety: a sandbox obtained through the partner relationship; a customer's own non-production
company; or a real production company, which means **testing writes against live hiring data**. Say that out loud
before anyone runs a create. Note also that SmartRecruiters' own "make a test API call" walkthrough has you create a
**real user** in the company — do not run it against a customer's production tenant as a smoke test.

Anything requiring a human — Partner Hub registration, email verification, being made an Administrator, requesting a
sandbox — hand back rather than looping.

**If this session has no browser automation** (the usual case for a CLI or cloud run), do not pretend to click. Give
the user an exact, ordered click path with the literal values to paste — the scope strings from §6, the `consentUrl`
or the redirect URIs from §5 — or a ready-to-send request body for the Partner & API Team, then continue once they
report back with the Application ID or the credential.

## 4. Obtaining the credentials

### Path A — Customer-generated API Key (SmartToken), self-serve, available today

The fastest route to a working call, and the one to be most careful with:

1. The customer must have **Administrator access**.
2. Credential Manager → **New Credential** → **API Key**.
3. Give it a **Credential name** and **Description** that say which integration it is for — an admin will later have
   to decide whether revoking it breaks anything.
4. **Generate.** The 32-character key appears in a pop-up **once**: *"admins will not be able to retrieve or see it
   after the initial pop-up is closed."*
5. Send it as `X-SmartToken: <key>`.

**It has no scopes and no expiry, and it grants full access to the organization's resources.** SmartRecruiters
recommends it for testing, for a partner trying the API out, or for a customer building their own solution — not for
a third-party platform holding many customers' keys. If you use it in production anyway, that is a deliberate
security decision the customer should make knowingly, and the honest framing is "you are handing us an unscoped
master key to your ATS." Revocation is a **Revoke** button in Credential Manager and takes effect immediately.

### Path B — Customer-generated OAuth Client Credentials, self-serve, available today

The right self-serve default for a multi-tenant platform:

1. The customer must have **Administrator access**.
2. Credential Manager → **New Credential** → **OAuth client ID**.
3. Define **Credential Name**, **Description**, and **Scope** (§6). There is no limit on how many scopes you select.
4. Assign a **System role** — required, either a predefined role or a custom one. An **Access Group** is optional.
   SmartRecruiters warns directly: *"even if the required OAuth scopes are selected, access to certain
   functionalities may still be restricted unless an appropriate System role is assigned."* This is §6's rule, and it
   is where most "my scopes are perfect and I still get 403" tickets end.
5. **Generate.** The **Client ID** and **Client secret** appear together in a pop-up **once**.
6. Exchange at `POST https://api.smartrecruiters.com/identity/oauth/token`, `Content-Type:
   application/x-www-form-urlencoded`, with `grant_type=client_credentials` plus either `client_id` and
   `client_secret` in the body, or `Authorization: Basic base64(clientID:clientSecret)`.
7. Call the API with `Authorization: Bearer <access_token>`. `expires_in` is **1799**; there is **no refresh token**
   — when it expires, repeat step 6 with the same credential.

The pair does not expire. Revoking it in Credential Manager cuts access immediately.

### Path C — General Partner Integration, for a multi-tenant connector (partner-gated)

The current path for an application distributed to many companies. It is Client Credentials end to end; the only
browser step is a consent page **you** host.

**Step 1 — get Master Credentials.** Give the Partner & API Team, via the Partner Hub:

- **scopes** — the exact strings (§6); they review these before approving.
- **consentUrl** — the URL SmartRecruiters sends the user to during setup. SmartRecruiters appends `companyId` and,
  in `REDIRECT` mode, `redirect`.
- **consentDisplayMode** — `REDIRECT` (the user leaves SmartRecruiters) or `POPUP` (an iFrame widget stays on the
  SmartRecruiters page). SmartRecruiters' partner guidance specifies the widget as **600 px wide by 45% of screen
  height**, with a scrollbar beyond that — design the consent page to fit before you pick `POPUP`.
- **oauthTokenUrl** (optional) — your token endpoint, so SmartRecruiters can call *your* API on the customer's behalf.
- **callbackUrl** (optional) — receives `POST {"eventType": "INTEGRATION_DISABLED"}` when a customer turns the
  integration off. **It is never called unless `oauthTokenUrl` is also defined.** Register both or neither; half of
  this pair is silence.
- **logo** and **description** — displayed on the customer's Apps & Integrations page.

They return an **Application ID** (a UUID) and a **Master `clientId`/`clientSecret`**, shared encrypted.

**Step 2 — mint a master token.**

```
POST https://api.smartrecruiters.com/identity/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=<master id>&client_secret=<master secret>
```

**Step 3 — exchange for customer-level credentials.** When a customer enables your app, SmartRecruiters `GET`s your
`consentUrl?companyId=...&redirect=...`. After they authenticate on your side, you call:

```
POST https://api.smartrecruiters.com/apps-integrations/partner-api/integrations
Authorization: Bearer <master access token>
Content-Type: application/json

{ "appId": "<your app id>", "companyId": "<from the query string>",
  "clientId": "<optional, yours>", "clientSecret": "<optional, yours>" }
```

The published OpenAPI specification for this endpoint declares the required scope as **`partner_integrations_manage`**
— a partner-level scope that is not in the customer scope catalogue. If your master token cannot call this endpoint,
that scope is the first thing to raise with the Partner & API Team. `appId` and `companyId` are required; `clientId`
and `clientSecret` are yours to generate, are required for bi-directional integrations, and **must be unique across
all of your integrations**. SmartRecruiters returns `200` with `{ clientId, clientSecret, integrationId }` — a unique
pair **per company**.

**Step 4 — finish the transition.** In `REDIRECT` mode, send the user back using the `redirect` parameter; in
`POPUP` mode SmartRecruiters closes the widget and refreshes.

From then on, treat the per-company pair exactly as Path B: client-credentials exchange, `expires_in` 1799, no
refresh token, re-mint on expiry. **Store `integrationId` and `companyId` alongside the pair** — `companyId` is the
tenant key, and a disable notification identifies the customer by the `clientId` associated with the token.

### Path D — Authorization Code (existing partners only; deprecated)

Documented here so you can *recognise* and *maintain* it, not so you can choose it. New partners are not onboarded.

- **Authorize:** `https://www.smartrecruiters.com/identity/oauth/allow` with `client_id`, `redirect_uri`, `scope`
  (space-separated, `%20`-encoded) and optional `state`. SmartRecruiters' own example sends **no `response_type`**.
  Treat `state` as mandatory regardless — it is your only CSRF defence. An **empty `scope=` is invalid**: omit the
  parameter or send a non-empty list. If you omit it, the **default scopes fixed at app registration** are used.
- **Callback:** `?code=...`, or `?error=access_denied&error_description=...` on decline. **The code expires in
  30 seconds.** Not 30 minutes — thirty seconds. Any human step, queue hop or retry between callback and exchange
  kills it, and this is the single most common failure on this flow.
- **Exchange:** `POST https://api.smartrecruiters.com/identity/oauth/token`, `Content-Type:
  application/x-www-form-urlencoded`, either `client_secret_post` (`client_id`, `client_secret`, `code`,
  `grant_type=authorization_code` in the body) or `client_secret_basic`. Note that SmartRecruiters' published
  examples set a form content type but show a **JSON-shaped `-d` body** — the two examples disagree with each other.
  Send genuine form encoding, and if you are debugging someone else's client, check which shape it actually puts on
  the wire before blaming the credentials.
- **Response:** `{ token_type: "bearer", access_token, expires_in: 1799, refresh_token }`.
- **Refresh:** same endpoint, `grant_type=refresh_token`. **The refresh token lives 28 days and can be used only
  once** — every refresh returns a new one. Store the new value from every response, or the second refresh fails.
  A connection idle for 29 days is dead and the user must re-authorize from scratch, so refresh on a schedule rather
  than lazily.
- **The token acts as the authorizing user**, so that person's own SmartRecruiters permissions cap it (§6).

## 5. Redirect URLs — and the consentUrl that replaces them

**These are two different things and the difference is the whole point of §4 Path C.**

For the **General Partner Integration** there is no `redirect_uri`. You register a **`consentUrl`**; SmartRecruiters
appends `companyId` and, in `REDIRECT` mode, a `redirect` URL to send the user back with. If a multi-region platform
routes customers to per-region hosts, that is a conversation about what `consentUrl` should be — SmartRecruiters
documents a single value, so a platform with several regions needs either one entry host that routes internally, or
a separate application per region. **Raise this with the Partner & API Team before registration**, because it shapes
the whole setup.

For the **legacy Authorization Code flow**, register every callback host up front — you do not edit them in a
console. For this platform these are one per data center; confirm the current list with the platform owner rather
than assuming:

```
https://api.unified.to/oauth/code          # us (default)
https://api-eu.unified.to/oauth/code       # eu
https://api-au.unified.to/oauth/code       # au
https://api-dev.unified.to/oauth/code      # dev
```

SmartRecruiters does not publish its `redirect_uri` matching rules. Assume exact string matching with no
trailing-slash forgiveness, and register the whole set on day one — discovering a missing region later is an email
and a wait, not a config change. Neither the SmartToken nor the customer Client Credentials path has a redirect at
all.

## 6. Scopes — and the two permission rules that outrank them

Scopes are lowercase, underscore-separated, and **space-delimited** on the authorize URL (`%20` when encoded) or
selected as checkboxes in Credential Manager. SmartRecruiters publishes two lists and **they do not agree**, so check
both: the **Access Scopes** catalogue (the user-facing list, with the prompt each scope shows the customer) and the
**Permission Reference** (endpoint-by-endpoint, the authoritative one when you are asking "what does this call
need?"). `reviews_delete`, for example, is in the Permission Reference but not the catalogue.

Mapping the scopes to the surfaces a hiring connector actually touches:

| Surface | Read | Write |
| --- | --- | --- |
| **Candidates** | `candidates_read`, `candidate_status_read` | `candidates_create`, `candidates_manage` |
| **Applications** (candidate-side apply) | `job_applications_read` | `job_applications_manage`, `candidate_applications_manage` |
| **Jobs** | `jobs_read` | `jobs_manage` |
| **Job publications / postings** | `internal_postings_read` (internal postings only — §7) | `jobs_publications_manage` |
| **Offers** | `candidates_offers_read` | *(no documented write scope — see below)* |
| **Interviews** | `interviews_read`, `interview_types_read`, `interview_templates_read`, `self_schedules_read`, `schedule_preferences_read` | `interviews_write`, `interview_types_write`, `interview_templates_manage`, `self_schedules_manage` |
| **Reviews / scorecards** | `reviews_read` | `reviews_write`, `reviews_delete` |
| **Assessments** | `assessment_orders_read` | *(results are written on the separate partner track — §1)* |
| **Users** | `users_read`, `user_me_read` | `users_manage` |
| **Company / configuration** | `company_read`, `configuration_read` | `configuration_manage` |
| **Audit** | `audit_events_read` | — |
| **Webhooks** | `webhooks_read` | `webhooks_write`, `webhooks_delete`, or `webhooks_manage` for all three |
| **Messages** | `messages_read` | `messages_write`, `messages_manage` |
| **Reporting** | `reporting_read` | `reporting_write` |

Two catalogue details that trip integrators specifically:

- **There is no documented offer-write scope.** Both SmartRecruiters lists contain `candidates_offers_read` and
  nothing else for offers; the Permission Reference describes the Offer API as letting developers *retrieve* offers.
  A connector requesting a write variant is requesting a string SmartRecruiters does not publish. If you need to
  write offers, ask the Partner & API Team whether the capability exists at all before treating it as a registration
  detail — it may be a product answer, not a scope you forgot.
- **`user_me_read` is its own scope.** Reading the current user after authorization — the obvious way to identify
  whose connection this is — requires `user_me_read`, and it is *not* implied by `users_read`. A connect flow that
  calls it without asking for it will authorize successfully and then fail on the identity lookup.

**Rule one: the System role outranks the scopes.** Every OAuth credential is "subject to the same authorization
rules as all users on the SmartRecruiters platform." A System role is **required**, and SmartRecruiters states that
access may still be restricted "even if the required OAuth scopes are selected." Several scopes carry an explicit
role note in the catalogue: `users_read`, `users_manage`, `configuration_read` and `configuration_manage` all say
**Requires an Admin user role**; the Audit API reference says **the `ADMINISTRATOR` role is required**; and the
webhook scopes require an Admin role *or* the **Manage webhooks** toggle enabled on a custom role. Tell customers
which role to assign at the same moment you tell them which scopes to tick, or you will get a credential that
authorizes cleanly and 403s on users, config, audit and webhooks.

**Rule two: the Access Group silently narrows the data.** An Access Group is optional on a credential and restricts
which jobs and candidates it can see. That produces a **200 with a short list**, not an error — the same
partial-data failure shape as a wrong permission anywhere else. And webhooks make it worse in an instructive way:
SmartRecruiters states that the webhook system **does not factor in access groups when sending notifications**, and
that notifications carry only resource IDs. So a credential with an Access Group will be told about a candidate it
then cannot fetch. Expect 403s on notification follow-up reads and treat them as configuration, not as a bug.

**On the Authorization Code flow a third cap applies:** the token acts as the authorizing *individual*, and returns
only "the data the individual user has access to." Whole-organization access needs an **Admin** authorizer. Client
Credentials is the opposite — it acts at organizational level, bounded by the role on the credential.

Ask for the narrow set. Adding a scope later means a customer regenerates a credential (self-serve paths) or another
round with the Partner & API Team (partner paths). SmartRecruiters' documented failure mode for calling outside your
granted scopes is an **`OAuthPermissionsException`**.

## 7. Rate limits, the Posting API trap, IPs, listing

- **Two limiters, both per credential.** A **rate limiter** of **10 requests per second** on most endpoints, and a
  **concurrency limiter** of **8 concurrent requests**. SmartRecruiters is explicit that "an API user is defined by
  the credential it uses" — so per-customer credentials each get their own budget, while a shared credential shares
  one.
- **The exceptions are the ones that hurt.** `GET /candidates` allows **1 concurrent request**. A parallel candidate
  backfill is therefore not just throttled, it is serialised by design, and this is usually the binding constraint on
  initial sync time. `POST` and `DELETE /jobs/{id}/publication` are capped at **2 requests per second**; so is
  `GET /offers/{id}/documents/{id}` per the rate-limiting page.
- **Read the headers, do not hardcode.** `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Concurrent-Limit`
  and `X-RateLimit-Concurrent-Remaining` are returned on **every** response, violation or not. `Retry-After` is
  documented only for the Analytics response-size policy, so do not depend on it generally — back off exponentially
  from the headers. Over-limit is **HTTP 429**.
- **Set client timeouts to at least 128 seconds.** SmartRecruiters asks for this explicitly and promises a response
  (success or error) within it. A 30-second default HTTP timeout will manufacture failures that look like outages.
- **For bulk analysis, use the Reporting API**, which streams and is designed for volume, rather than paginating the
  Customer API — SmartRecruiters recommends this directly.
- **The Posting API trap.** Published job postings are readable with **no authentication at all**. That means a
  "connection test" against postings proves nothing about your credential. Note the nuance: SmartRecruiters
  documents the *public* Posting API as requiring no auth, while **internal** postings need an API key or an OAuth
  token carrying `internal_postings_read` — and warns that for that API, "the system role and access group
  configuration assigned to the credentials are not enforced," so anyone with a valid credential can read all
  internal postings. Test authentication against a scoped resource such as candidates or users instead.
- **IP allowlisting.** SmartRecruiters publishes its own outbound IPs (separately for production and sandbox) for
  customers to allowlist *inbound* webhook traffic. They are explicitly "subject to change," and the changelog RSS
  feed is the notification channel. This is about traffic **from** SmartRecruiters to you; it is not a list of
  addresses you call.
- **Listing.** Appearing on customers' Apps & Integrations page comes with the General Partner Integration, and
  whether that is public to all customers or private to named ones is confirmed with the Partner & API Team (§3).
  SmartRecruiters publishes no self-serve certification checklist on its developer site — ask, rather than assuming
  a process exists that you can pre-satisfy.

## 8. Verify end-to-end

Do not stop at "the token came back." Test what a customer's tenant actually does:

1. Mint a token and make one **scoped** read — candidates, jobs or users. **Not** a Posting API call (§7).
2. Read the **current user** endpoint if your connect flow identifies the connection that way. It needs
   `user_me_read` specifically (§6), and it is the most common scope missing from an otherwise complete set.
3. Read a **users** or **audit** endpoint if either is in scope. These exercise the System role rule, and they fail
   differently from a scope problem — this is the two-minute test that separates the two.
4. **Count.** Compare a candidate or job count against what the customer says they have. A short list with a 200 is
   the Access Group narrowing your view, not an empty company (§6).
5. On the General Partner Integration: run the full **consentUrl → exchange → per-company token** round trip against
   a **second** company, and confirm you stored `companyId` and `integrationId`. Then let the access token expire and
   confirm you re-mint from the stored pair rather than looking for a refresh token that does not exist.
6. On the legacy Authorization Code flow: exchange the code **immediately** — you have 30 seconds. Then **refresh,
   and refresh again using the token the first refresh returned.** That catches a client that ignores rotation, and
   it is a two-minute test for a failure that otherwise surfaces weeks later.
7. Watch `X-RateLimit-Concurrent-Remaining` during a real candidate sync, not a single call. The concurrency limit of
   1 on `GET /candidates` will find you.
8. If you subscribe to webhooks, confirm you can actually **fetch** a resource you were notified about. Access groups
   are not applied to notifications but are applied to the follow-up read (§6).

| Symptom | Cause |
| --- | --- |
| 401 on every call, credential looks correct | Wrong header for the credential type — SmartToken goes in `X-SmartToken`, an OAuth token in `Authorization: Bearer` (§1) |
| 401 with a key that "looks right" | A 32-character customer API Key and a 64-character Partner API Key both use `X-SmartToken` and are not interchangeable (§1) |
| **403 on users, configuration, audit or webhooks; 200 elsewhere** | The credential's **System role**, not its scopes — several of these require Admin / `ADMINISTRATOR` (§6) |
| `OAuthPermissionsException` | The token was not granted the scope that endpoint requires; check the **Permission Reference**, not the catalogue (§6) |
| 403 on the current-user read right after a successful authorization | `user_me_read` was not requested; it is not implied by `users_read` (§6) |
| **200 with a short list of candidates or jobs** | An **Access Group** on the credential is narrowing visibility. Not an error, not an empty tenant (§6) |
| Webhook fires, the follow-up fetch 403s | Notifications ignore access groups; the read does not (§6) |
| "Authorization code expired" on the legacy flow | The code lives **30 seconds** — any queue hop or human step kills it (§4 Path D) |
| Second refresh fails, first succeeded | The 28-day refresh token is **single-use**; store the new one from every response (§4 Path D) |
| Connection dies after a quiet month | 28-day refresh-token lifetime on the legacy flow; refresh on a schedule (§4 Path D) |
| Looking for a refresh token on a client-credentials connection | There isn't one. Re-mint from the stored pair (§4 Paths B, C) |
| Token exchange rejected although the credentials are right | Form encoding vs the JSON-shaped body in SmartRecruiters' own example; send real `x-www-form-urlencoded` (§4 Path D) |
| `invalid` / rejected authorize request with no scopes | An empty `scope=` is invalid — omit it to use registration defaults, or send a non-empty list (§4 Path D) |
| Partner master token 403s on the integrations endpoint | The master credential lacks `partner_integrations_manage` — a Partner & API Team question (§4 Path C) |
| `409` from the integrations endpoint | That company is already integrated with your application, or the `clientId` you generated is not unique across your integrations (§4 Path C) |
| Customer never receives a disable notification | `callbackUrl` is not called unless `oauthTokenUrl` is also registered (§4 Path C) |
| `429` under light load | 10 req/s, **8 concurrent — and 1 concurrent on `GET /candidates`** (§7) |
| Timeouts that look like an outage | Client timeout below the 128 s SmartRecruiters asks you to allow (§7) |
| "It works!" on a posting read, nothing else works | The public Posting API needs no authentication; that test proves nothing (§7) |
| Worked yesterday, 401 today, nothing changed | An admin clicked **Revoke** in Credential Manager. Revocation is immediate and total; credentials never expire on their own (§4) |
| Customer says Credential Manager has no such option | They are not an Administrator (§4) |

## 9. Hand off — never commit the key

- **Do not** write an API key, client secret, master secret, partner key or refresh token into source control, a
  test, a fixture, a committed `.env`, a ticket, a PR body or a chat channel. Values go to the human running this,
  for their secret store or console. A **`companyId`**, an **`integrationId`**, an Application ID and a client ID are
  not secrets and may be recorded; the secret that arrives in the same message is — separate them before you paste
  anything.
- **Watch what gets logged.** The General Partner Integration's setup call returns a customer's `clientSecret` in a
  response body. Anything that logs whole request/response objects or whole credential records at info level will
  put live customer secrets into a log aggregator, where they are retained, indexed and widely readable. Review the
  logging on that code path specifically, not just the storage.
- If a code change is needed (a scope string, a `consentUrl`, a token-endpoint shape), keep it secret-free and say
  plainly what the human must set out of band.
- Report a secret once so it can be pasted into the secret store, say clearly that it is now in the transcript and
  can be revoked, then move on.
- Close with: which credential model you ended up on; the Application ID and `companyId` if partner; where the
  secret was delivered; the token endpoint and the API base; the exact scope strings requested and any
  SmartRecruiters refused; **the System role the customer must assign** and whether an Access Group is in play; the
  re-mint or refresh cadence the 1799-second token demands; the visibility decision (public vs private on the Apps &
  Integrations page); and whatever is still waiting on SmartRecruiters.

## Stop and ask

Hand back to a human rather than guessing when: **the ask is a new Authorization Code registration** — say in the
first reply that SmartRecruiters stopped onboarding partners onto it on 2026-01-30, rather than starting a
registration that cannot complete; there is no partner account and the ask is Master Credentials; the Partner & API
Team comes back asking to narrow the scope set, or refuses a capability outright (that is a product answer, not a
registration mistake); a multi-region platform needs more than one `consentUrl` and SmartRecruiters documents one;
someone proposes re-issuing Master Credentials or a new Application ID on a live integration (every customer
re-consents); the only available test tenant is production and the task involves writes; a customer will not make
anyone an Administrator, so no credential can be created; the plan depends on customers pasting unscoped SmartTokens
and nobody has accepted that security trade-off explicitly; the 1-concurrent limit on candidate reads makes a
promised sync window impossible; an offer-write or other capability has no documented scope; or Credential Manager
and the developer documentation do not match the **Platform state** section.

## References

Verified to resolve on 2026-09-20.

- Authentication overview and method comparison — https://developers.smartrecruiters.com/docs/authentication
- Get Started (customer vs partner credential routing) — https://developers.smartrecruiters.com/docs/get-started
- API Key / SmartToken — https://developers.smartrecruiters.com/docs/authentication-api-key
- OAuth 2.0 Client Credentials grant — https://developers.smartrecruiters.com/docs/authentication-oauth-client-credential
- OAuth 2.0 General Partner Integration — https://developers.smartrecruiters.com/docs/oauth-20-general-partner-integration
- OAuth 2.0 Authorization Code grant (deprecated) — https://developers.smartrecruiters.com/docs/authentication-oauth-authorization-code
- OAuth 2.0 Register an app (Partner Hub) — https://developers.smartrecruiters.com/docs/register-an-app
- Partner API Key (legacy) — https://developers.smartrecruiters.com/docs/partner-api-key
- Access Scopes catalogue — https://developers.smartrecruiters.com/docs/access-scopes
- Permission Reference (endpoint → scope) — https://developers.smartrecruiters.com/docs/permission-reference
- Apps Integrations API overview — https://developers.smartrecruiters.com/reference/apps-integrations-api
- Apps Integrations OpenAPI specification — https://api.smartrecruiters.com/apps-integrations/api-docs
- Changelog: General Partner Integration introduction — https://developers.smartrecruiters.com/changelog/general-partner-integration-introduction
- Rate Limiting — https://developers.smartrecruiters.com/docs/rate-limiting
- Throttling Policies — https://developers.smartrecruiters.com/docs/throttling-policies
- Error Handling — https://developers.smartrecruiters.com/docs/error-handling
- Deprecation and Sunset Policies — https://developers.smartrecruiters.com/docs/deprecation-and-sunset-policies
- Public IP addresses (production and sandbox) — https://developers.smartrecruiters.com/docs/public-ip-addresses
- Posting API (no-auth vs internal postings) — https://developers.smartrecruiters.com/docs/posting-api
- Webhooks (Admin role and Manage webhooks toggle) — https://developers.smartrecruiters.com/docs/webhooks
- Creating webhook subscriptions (access groups) — https://developers.smartrecruiters.com/docs/creating-webhook-subscriptions
- Pagination — https://developers.smartrecruiters.com/docs/pagination
- Marketplace API overview and integration options — https://developers.smartrecruiters.com/docs/partners-overview
- Customer Experience (Apps & Integrations page, consent widget) — https://developers.smartrecruiters.com/docs/customer-experience
- Assessment partner app overview — https://developers.smartrecruiters.com/reference/assessment-partner-app-overview
- Audit API (ADMINISTRATOR role required) — https://developers.smartrecruiters.com/reference/auditget-1
- Full documentation index — https://developers.smartrecruiters.com/llms.txt
- Developer changelog — https://developers.smartrecruiters.com/changelog
- Partner Hub (registration channel) — https://www.smartrecruiters.com/partners/
- Partner Portal (legacy Partner API Key) — https://partners.smartrecruiters.com
