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:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute for this key
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix 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.

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.