> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.vouch.careers/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.vouch.careers/_mcp/server.

# Getting Started

The Vouch public API is a read-only REST API for positions, their candidates, performance data, and — for agencies — their clients.

## Base URL

```
https://vouch.careers/api/v1
```

## Authentication

Every request needs an `Authorization` header with a bearer API key:

```
Authorization: Bearer vch_live_...
```

Get a key from **Settings → API + Webhooks** in your Vouch business portal. The full key is shown once, at creation — copy it then, since it can't be retrieved again afterwards. A key inherits the access of whoever created it (company-wide, or restricted to specific positions if you chose to scope it down when creating the key).

## Rate limits

Requests are capped per API key, by default **60 requests/minute**. Some keys may be configured with a different limit. Every response includes:

| Header                  | Meaning                                         |
| ----------------------- | ----------------------------------------------- |
| `X-RateLimit-Limit`     | Requests allowed per minute for this key        |
| `X-RateLimit-Remaining` | Requests left in the current window             |
| `X-RateLimit-Reset`     | Unix timestamp (seconds) when the window resets |

Exceeding the limit returns `429` with a `Retry-After` header (seconds until you can retry).

## Pagination

`GET /positions`, `GET /positions/{id}/candidates`, `GET /career-pages` and `GET /career-pages/{id}/positions` are cursor-paginated: pass `take` (max 100, default 50) and `cursor` (the `nextCursor` from the previous response) to page through results. `nextCursor` is `null` on the last page.

## Filtering positions by status

`GET /positions` accepts `?status=<status>` to filter to one publish status — see `status` on the Position schema for what each value means. Combine freely with `clientId`/`clientSlug` and pagination.

## Clients (agencies)

If your API key belongs to an agency that manages other companies' positions, `GET /clients` lists those clients — full company detail, including `contentHtml` and `locations`, same as a position's `company` object. `GET /clients/{id}` fetches one, by either its ID or its slug. For companies that aren't agencies, `GET /clients` just returns an empty list.

To see only a specific client's positions, filter `GET /positions` with `?clientId=<id>` or `?clientSlug=<slug>` — a position managed on that client's behalf carries a `subCompanyId` pointing at them. Passing a `clientId`/`clientSlug` that doesn't match one of your clients returns `400`, not an empty list, so a typo doesn't silently look like "no positions."

## Career pages

A careers page is a company-level page listing many positions at once (as opposed to a sharing link, which is tied to one position). `GET /career-pages` lists the active careers pages for your company and — for an agency — its clients (`forAgency`/`forClient`, `isDefault`). The Vouch marketplace (vouch.careers) is modelled as a careers page too and is included, flagged `isMarketplace: true`. Filter with `?forAgency=true|false` (your own pages vs. clients' pages) and `?isDefault=true|false`; default pages sort first. Each page's `id` is its referral link ID, the same value in its public `url`.

* `GET /career-pages/{id}/positions` — the positions currently live on that page: published, not paused or expired, and enabled on the page. Cursor-paginated like `GET /positions`. `enableApplication`/`enableVouch` reflect any per-page override.
* `GET /career-pages/{id}/positions/{positionId}` — one position as reached through that page: the full detail (same fields as `GET /positions/{id}`) with that page's link fields flattened in — `careerPageId`, `idForEmbedded` (`<careerPageId>.<positionId>`), `jobUrl`/`applyUrl`/`vouchUrl` — instead of the `sharingLinks`/`careerPagesLinks` arrays. `404` if the position isn't live on that page.

## Sharing links

A position (`GET /positions/{id}`) has two arrays for every active way a candidate can reach it:

* `sharingLinks` — direct channels: job board posts, ads, referral networks.
* `careerPagesLinks` — entries on a company careers page. `forAgency`/`forClient` tell you whether it's the agency's own overarching careers page or a specific client's; `isDefault` marks the default one.

Both share the same shape for the rest of their fields: pre-built `jobUrl`/`applyUrl`/`vouchUrl` links, plus `id` (the referral link ID) and `idForEmbedded` for building your own embedded widget — see the Embedded Widget guide. For a `sharingLinks` entry the two are the same value; for a `careerPagesLinks` entry they differ (`idForEmbedded` is `id.<positionId>`, since a careers page isn't scoped to one position) — always use `idForEmbedded` when embedding, not `id`. The Vouch marketplace (vouch.careers) appears in `careerPagesLinks` like any other careers page, flagged `isMarketplace: true` — so a `PUBLISHED` position always has at least one entry across the two arrays.

## Image galleries

`contentHtml` (on `GET /positions/{id}` and the `company` object, including `GET /clients`) is built from content sections, but image gallery sections have no inline-HTML equivalent — a photo grid doesn't translate to a paragraph of markup — so they're never part of `contentHtml`. They're surfaced separately instead, as `images`: an array of arrays, one entry per image gallery section (in section order), each holding that section's image URLs in order, already resolved to full, directly-usable URLs. Empty (`[]`) if there are no image gallery sections.

## Errors

Errors are returned as `{ "error": string, "code": string }` with a matching HTTP status — `401` (missing/invalid key), `403` (key doesn't have access to the requested company or position), `404` (not found), `429` (rate limited).

## Importing into Postman

Rather than a separately generated collection file (Fern's Postman generator is no longer maintained), import the live spec directly — it's always in sync with what's actually deployed:

1. In Postman: **File → Import**
2. Choose **Link**, and paste:
   ```
   https://vouch.careers/api/v1/openapi.json
   ```

Postman will build a full collection from it, including request/response schemas for every endpoint.