Skip to Content
ReferencePublic Developer API

Public Developer API

The public developer API lets server-to-server integrations read and safely write core recruiting data with a scoped, organization-owned API key. It is a thin, stable surface over the same recruiting graph the dashboard uses, with a curated field contract that never exposes internal scoring, attribution, or free-form Json blobs.

Base URL: https://api.vitae.ai/v1/public-api

Every public endpoint is mounted under /v1/public-api/<resource>. The v1 is the global API version; public-api separates the key-authenticated developer surface from the session-authenticated app API.

Authentication

Public endpoints are authenticated by an API key, not a user session. Present the key as a bearer token:

curl https://api.vitae.ai/v1/public-api/candidates \ -H "Authorization: Bearer vit_3f9c1e7a2b8d4f60_..."

An X-API-Key header is also accepted for clients that reserve Authorization for other purposes:

curl https://api.vitae.ai/v1/public-api/candidates \ -H "X-API-Key: vit_3f9c1e7a2b8d4f60_..."

Keys have the shape vit_<prefix>_<secret>: a vit namespace, a 16-character hex prefix used for fast lookup, and a high-entropy secret. Only the prefix and a hash of the full key are ever stored.

Keys are minted from the dashboard (see Managing keys). The plaintext key is shown exactly once at creation — only a hash and a short prefix are stored — so capture it then. Each key is bound to one organization, and every request is implicitly scoped to that organization; there is no cross-tenant access and no organization id is ever taken from the request.

Scopes

A key carries an explicit set of scopes. A request to an endpoint whose scope the key lacks is rejected with 403 Forbidden before any work is done. Scopes default to none — a key with no scopes can authenticate but call nothing.

ScopeGrants
candidates_readList and read candidates
candidates_writeCreate candidates
jobs_readList and read jobs
jobs_writeCreate jobs
applications_readList and read applications
applications_writeUpdate an application’s stage and notes
placements_readList and read placements
webhooks_readList webhook subscriptions and delivery attempts
webhooks_writeCreate, update, and deactivate webhook subscriptions

Grant the minimum scopes an integration needs. There is no placements_write scope — placements are read-only over the public API.

Rate limiting

Every key has a per-minute request budget (default 120, configurable 1–10,000 at mint time). Each response carries the current budget:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute for this key
X-RateLimit-RemainingRequests left in the current window
Retry-AfterSeconds to wait before retrying (only on 429)

When the window is exhausted the API returns 429 Too Many Requests with a Retry-After header. Back off for that many seconds rather than retrying immediately.

Pagination

List endpoints use offset pagination with page (1-based, default 1) and limit (default 25, max 100) query params:

GET /v1/public-api/candidates?page=2&limit=50

The page metadata is returned under meta.pagination:

{ "data": [], "meta": { "pagination": { "page": 2, "limit": 50, "total": 320, "totalPages": 7, "hasNextPage": true, "hasPreviousPage": true, "nextPage": 3, "previousPage": 1 } } }

Single-resource reads return a bare { "data": { ... } } envelope. Unknown query params are rejected with 400 Bad Request.

Candidates

GET /v1/public-api/candidates candidates_read GET /v1/public-api/candidates/:id candidates_read POST /v1/public-api/candidates candidates_write

GET /candidates accepts an optional status filter alongside page/limit. POST /candidates requires fullName; all other fields are optional. The owning organization and the creating user are taken from the key — they cannot be set or overridden by the client.

curl -X POST https://api.vitae.ai/v1/public-api/candidates \ -H "Authorization: Bearer vit_3f9c1e7a2b8d4f60_..." \ -H "Content-Type: application/json" \ -d '{ "fullName": "Ada Lovelace", "email": "ada@example.com", "skills": ["analytical-engines", "mathematics"] }'

The candidate projection includes: id, fullName, email, phoneNumber, city, state, country, location, status, skills, minSalary, maxSalary, salaryCurrency, bio, createdAt, updatedAt.

Jobs

GET /v1/public-api/jobs jobs_read GET /v1/public-api/jobs/:id jobs_read POST /v1/public-api/jobs jobs_write

GET /jobs accepts an optional status filter. POST /jobs requires subject; the job is created with the platform’s safe defaults (active, public, draft approval). The job projection includes the public posting fields: id, subject, label, description, category, type, industry, role, country, location, city, salaryMin, salaryMax, salaryCurrency, salaryFrequency, experience, status, visibility, workplaceType, skills, createdAt, updatedAt.

Applications

GET /v1/public-api/applications applications_read GET /v1/public-api/applications/:id applications_read PATCH /v1/public-api/applications/:id applications_write

GET /applications accepts optional jobId, candidateId, and stage filters.

Application creation is intentionally not exposed — it is entangled with the internal matching pipeline. The only public write is a narrow PATCH of the pipeline stage and/or free-form notes; at least one of the two must be provided, or the request is rejected with 400.

curl -X PATCH https://api.vitae.ai/v1/public-api/applications/app_123 \ -H "Authorization: Bearer vit_3f9c1e7a2b8d4f60_..." \ -H "Content-Type: application/json" \ -d '{ "stage": "interview", "notes": "Strong systems background" }'

The application projection includes: id, jobId, candidateId, clientId, stage, type, source, notes, isMatched, approvalStatus, createdAt, updatedAt.

Placements

GET /v1/public-api/placements placements_read GET /v1/public-api/placements/:id placements_read

Placements are read-only. GET /placements accepts an optional status filter. The placement projection includes: id, applicationId, candidateId, jobId, clientId, status, startDate, endDate, closureReason, notes, createdAt, updatedAt.

Errors

Public endpoints use the standard error envelope. Common statuses:

StatusWhen
400 Bad RequestValidation failed, unknown query param, or no writable field supplied
401 UnauthorizedMissing, malformed, expired, or revoked key
403 ForbiddenKey lacks the scope the endpoint requires
404 Not FoundResource does not exist in the key’s organization
429 Too Many RequestsPer-key rate limit exceeded; see Retry-After

Managing keys

API keys are created and rotated from the dashboard. These management endpoints are authenticated by a logged-in organization user (a session bearer token), not by an API key:

POST /v1/api-keys Mint a key (plaintext returned once) GET /v1/api-keys List active keys (newest first) POST /v1/api-keys/:id/rotate Rotate a key (new plaintext returned once) DELETE /v1/api-keys/:id Revoke a key (irreversible)

Rotate keys when a teammate or vendor loses access, and revoke any key you believe is compromised. Treat keys like passwords: never embed them in client-side code or commit them to source control.

Automation webhook subscriptions

Zapier and Make use API-key-authenticated webhook-subscription endpoints to create and remove REST-hook subscriptions without a dashboard session:

POST /v1/public-api/webhook-subscriptions webhooks_write GET /v1/public-api/webhook-subscriptions webhooks_read GET /v1/public-api/webhook-subscriptions/:id webhooks_read PATCH /v1/public-api/webhook-subscriptions/:id webhooks_write DELETE /v1/public-api/webhook-subscriptions/:id webhooks_write GET /v1/public-api/webhook-subscriptions/:id/deliveries webhooks_read

Use these endpoints for no-code platform hook lifecycle. The dashboard management endpoints under /v1/webhook-subscriptions are still available for human-managed subscriptions.

Security

  • Always call over HTTPS.
  • Scope keys to the minimum access an integration needs.
  • Rotate on personnel or vendor changes; revoke on suspected compromise.
  • Set an expiresAt on keys issued to short-lived integrations.
  • Resource ids are passed in the path, never PII — keep emails, phone numbers, and names out of URLs.
Last updated on