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
Authentication
Every request needs an Authorization header with a bearer API key:
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:
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 likeGET /positions.enableApplication/enableVouchreflect any per-page override.GET /career-pages/{id}/positions/{positionId}— one position as reached through that page: the full detail (same fields asGET /positions/{id}) with that page’s link fields flattened in —careerPageId,idForEmbedded(<careerPageId>.<positionId>),jobUrl/applyUrl/vouchUrl— instead of thesharingLinks/careerPagesLinksarrays.404if 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/forClienttell you whether it’s the agency’s own overarching careers page or a specific client’s;isDefaultmarks 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:
- In Postman: File → Import
- Choose Link, and paste:
Postman will build a full collection from it, including request/response schemas for every endpoint.