View as Markdown

Standout API

List your open roles to power your own careers page, and add candidates to your Standout pipeline from your own systems: your careers page, your ATS, or your own AI agent.

Every candidate you add lands on the role's board in Pending review, exactly as if a teammate had added them by hand. Standout never contacts a candidate you add through the API.

Authentication

Create a key in Standout under Integrations → Standout API (company admins only). Send it as a bearer token:

Authorization: Bearer sk_standout_...

Keep the key on your server. Never put it in browser code: anyone who has it can add candidates to your roles. You can revoke a key at any time from the same place; it stops working immediately.

Base URL: https://standout.work/api/v1

List your roles

GET /api/v1/jobs

Your live roles, newest first, with everything you need to publish them: title, team, employment type, locations, work modes, the full description (as written and as sanitised HTML), and the salary and equity ranges. Salary, equity and location only appear when the role's disclosure settings allow it, so the response is safe to put on a public page.

Query paramDefaultNotes
limit20Roles per page, 1 to 50.
cursornonenextCursor from the previous page. Treat it as opaque.
curl "https://standout.work/api/v1/jobs?limit=20" \
  -H "Authorization: Bearer $STANDOUT_API_KEY"
{
  "jobs": [
    {
      "id": "3f1c2a4e-8b7d-4c1a-9e2f-5a6b7c8d9e0f",
      "title": "Founding Engineer",
      "team": "Engineering",
      "employmentType": "Full-time",
      "locations": ["Paris", "Remote (France)"],
      "workModes": ["HYBRID"],
      "description": "## About the role\n\nYou'll build ...",
      "descriptionHtml": "<h2>About the role</h2><p>You'll build ...</p>",
      "salary": { "min": 80000, "max": 100000, "currency": "EUR" },
      "equity": { "min": 0.25, "max": 0.5 },
      "createdAt": "2026-09-24T08:14:03.920Z",
      "updatedAt": "2026-09-26T11:02:40.118Z"
    }
  ],
  "nextCursor": "WyIyMDI2LTA5LTI0VDA4OjE0OjAzLjkyMFoiLCIzZjFjMmE0ZS..."
}
  • workModes is any of REMOTE, HYBRID, IN_OFFICE, FLEXIBLE.
  • description is the text as written in Standout (usually Markdown); descriptionHtml is the same text rendered and sanitised, ready to insert.
  • salary and equity are null when unset or not disclosed. Equity is a percentage.
  • nextCursor is null on the last page. Pages are stable: a role published while you page never repeats or shifts one.

Fetch every page:

async function listAllRoles() {
  const roles = [];
  let cursor: string | null = null;
  do {
    const url = new URL("https://standout.work/api/v1/jobs");
    url.searchParams.set("limit", "50");
    if (cursor) url.searchParams.set("cursor", cursor);
    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.STANDOUT_API_KEY}` },
    });
    if (!response.ok) throw new Error(`Standout ${response.status}`);
    const page = await response.json();
    roles.push(...page.jobs);
    cursor = page.nextCursor;
  } while (cursor);
  return roles;
}

Cache the result on your side (a few minutes is plenty) rather than calling Standout on every page view of your careers site.

Add a candidate

POST /api/v1/candidates

Identify the person by their LinkedIn profile whenever you have it. We look them up and fill in their name, title and photo within about a minute. When you only have a name and email (a CV without a LinkedIn link), send firstName and email instead.

FieldTypeRequiredNotes
jobIduuidyesFrom GET /api/v1/jobs.
linkedinUrlstringone of these twoProfile URL or slug, e.g. https://www.linkedin.com/in/ada-lovelace.
firstNamestringone of these twoUsed when there is no LinkedIn profile.
lastNamestringno
emailstringnoStored on the candidate.
phonestringnoStored on the candidate.
githubUrlstringnoGitHub profile URL or handle.
notestringnoShown on the candidate's card to your team, e.g. "Applied via careers page". Max 4000 characters.
requestIduuidnoIdempotency key. Retrying with the same requestId never adds the person twice.
curl -X POST https://standout.work/api/v1/candidates \
  -H "Authorization: Bearer $STANDOUT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "3f1c2a4e-8b7d-4c1a-9e2f-5a6b7c8d9e0f",
    "linkedinUrl": "https://www.linkedin.com/in/ada-lovelace",
    "email": "[email protected]",
    "note": "Applied via careers page",
    "requestId": "9b2e6f1a-3c4d-4e5f-8a7b-1c2d3e4f5a6b"
  }'

201 Created when the candidate was added, 200 OK when they were already on this role (nothing changes):

{
  "candidateId": "5d9e1f2a-6b7c-4d8e-9f0a-1b2c3d4e5f6a",
  "matchId": "9b2e6f1a-3c4d-4e5f-8a7b-1c2d3e4f5a6b",
  "jobId": "3f1c2a4e-8b7d-4c1a-9e2f-5a6b7c8d9e0f",
  "created": true,
  "candidateName": "Ada Lovelace",
  "linkedinId": "ada-lovelace",
  "status": "PENDING",
  "pipelineUrl": "https://standout.work/org?job=3f1c2a4e-8b7d-4c1a-9e2f-5a6b7c8d9e0f"
}

Errors

Every error has the same shape:

{ "error": "Human-readable message", "code": "JOB_UNAVAILABLE" }
StatusCodeMeaning
400INVALID_INPUTThe body or query failed validation (including a bad cursor). details lists each problem.
401UNAUTHORIZEDMissing, unknown or revoked key.
404JOB_UNAVAILABLEThe role doesn't exist, isn't yours, or is archived.
409MATCH_CONFLICTThe person already went through this role's pipeline, or has more than one record on it.
409REQUEST_CONFLICTThis requestId was already used for a different candidate.
422PROFILE_UNREADABLEWe couldn't read that LinkedIn profile. Nothing was added; check the link.
429RATE_LIMITEDA limit below was reached.
500UNEXPECTEDSomething went wrong on our side. Retry with the same requestId.

Limits

LimitValueWhen exceeded
Roles per page50400 INVALID_INPUT
Candidates added per key200 per day (UTC)429 RATE_LIMITED
Role listing requests per key1000 per hour429 RATE_LIMITED, with Retry-After
Requests per IP address3000 per hour429 RATE_LIMITED, with Retry-After

Need more? Tell us.

Example: forward careers-page applications

Call the API from your server when someone applies, never from the browser:

export async function onApplication(application: {
  jobId: string;
  linkedinUrl?: string;
  firstName: string;
  lastName?: string;
  email: string;
  applicationId: string; // a uuid you already store
}) {
  const response = await fetch("https://standout.work/api/v1/candidates", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.STANDOUT_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jobId: application.jobId,
      ...(application.linkedinUrl
        ? { linkedinUrl: application.linkedinUrl }
        : {
            firstName: application.firstName,
            lastName: application.lastName,
          }),
      email: application.email,
      note: "Applied via careers page",
      requestId: application.applicationId,
    }),
  });
  if (!response.ok) {
    const { code, error } = await response.json();
    throw new Error(`Standout ${code}: ${error}`);
  }
  return response.json();
}