Logo StartupKit
Integrations

API Reference

Authenticate and query the Hiring API for job postings, candidates, and applications.

Why It Matters

The Hiring API gives external systems read access to your hiring data — job boards pull published postings, HRIS systems sync candidate records, dashboards track pipeline metrics. Token scopes ensure each integration only sees what it needs.

Base URL

All API endpoints are accessed via:

https://yourdomain.com/api/v1

Replace yourdomain.com with your actual Kit domain.

Authentication

Every request requires an API token in the Authorization header:

Authorization: Bearer YOUR_TOKEN

Alternative format (both work):

Authorization: token YOUR_TOKEN

Create tokens at Settings > API. Each token must have explicit scopes to access hiring endpoints.

Scope Access
job_postings:read Job postings and stages
candidates:read Candidate PII (name, email, phone)
applications:read Applications and stage history

Tokens without any scopes cannot access hiring endpoints.

Token Format and Lifecycle

  • Format: 32-character hexadecimal string (e.g., a1b2c3d4e5f6...)
  • Last used tracking: Every successful request updates last_used_at
  • Expiration: Optional expires_at can be set when creating the token
  • Inactivity alerts: Tokens unused for 30+ days trigger admin notifications
  • Auto-revocation: Tokens are automatically revoked when a user is removed from the account

Best practice: Create separate tokens per integration with minimal scopes needed.

Account Scoping

Each API token is bound to a specific account. The account is derived automatically from the token — no account_id parameter is needed.

When a team member is removed from an account, their tokens for that account are revoked automatically.

Error Responses

Status Meaning Common Causes
400 Bad request Account context missing (edge case in session-based auth)
401 Unauthorized Missing, invalid, or expired token
403 Forbidden Token lacks required scope for this endpoint
404 Not found Resource doesn’t exist or doesn’t belong to your account
422 Unprocessable entity Validation errors (primarily for non-hiring endpoints)

Pagination

List endpoints return paginated results:

{
  "data": [...],
  "pagination": {
    "current_page": 1,
    "total_pages": 3,
    "total_count": 42,
    "per_page": 20
  }
}

Pass ?page=2 to fetch subsequent pages.


Job Postings

Scope required: job_postings:read

List Job Postings

GET /api/v1/job_postings

Example:

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://yourdomain.com/api/v1/job_postings?status=published

Parameters:

Param Type Description
status string Filter by draft, published, or closed
page integer Page number

Response:

{
  "data": [
    {
      "id": "job_abc123",
      "title": "Senior Rails Developer",
      "status": "published",
      "department": "Engineering",
      "location": "New York, NY",
      "employment_type": "full_time",
      "remote": true,
      "salary_min": "120000.0",
      "salary_max": "180000.0",
      "salary_currency": "USD",
      "salary_period": "year",
      "published_at": "2026-01-15T10:00:00Z",
      "closed_at": null,
      "created_at": "2026-01-10T08:30:00Z",
      "updated_at": "2026-02-01T14:20:00Z",
      "stages": [
        {
          "id": "stg_def456",
          "name": "Screening",
          "stage_type": "default",
          "position": 0
        }
      ],
      "counts": {
        "total_applications": 24,
        "active_applications": 18,
        "rejected": 6
      }
    }
  ],
  "pagination": { ... }
}

Get Job Posting

GET /api/v1/job_postings/:id

Returns the same shape as an item in the list response.

List Stages

GET /api/v1/job_postings/:job_posting_id/stages

Response:

{
  "data": [
    {
      "id": "stg_def456",
      "name": "Screening",
      "stage_type": "default",
      "position": 0
    }
  ]
}

Stages are ordered by position. No pagination.


Candidates

Scope required: candidates:read

List Candidates

GET /api/v1/candidates

Example:

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://yourdomain.com/api/v1/[email protected]

Parameters:

Param Type Description
search string Search by name or email
page integer Page number

Response:

{
  "data": [
    {
      "id": "cand_ghi789",
      "first_name": "John",
      "last_name": "Doe",
      "email": "[email protected]",
      "phone": "+1-555-0100",
      "status": "active",
      "github_username": "johndoe",
      "created_at": "2026-01-20T09:15:00Z",
      "applications": [
        {
          "id": "app_jkl012",
          "job_posting_id": "job_abc123",
          "job_posting_title": "Senior Rails Developer",
          "current_stage_name": "Interview",
          "status": "active",
          "submitted_at": "2026-01-20T09:15:00Z"
        }
      ]
    }
  ],
  "pagination": { ... }
}

Get Candidate

GET /api/v1/candidates/:id

Returns the same shape as an item in the list response.


Applications

Scope required: applications:read

List Applications

GET /api/v1/applications

Example:

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://yourdomain.com/api/v1/applications?job_posting_id=job_abc123&status=active

Parameters:

Param Type Description
job_posting_id string Filter by job posting prefix ID
status string Filter by active or rejected
page integer Page number

Response:

{
  "data": [
    {
      "id": "app_jkl012",
      "job_posting_id": "job_abc123",
      "candidate_id": "cand_ghi789",
      "current_stage_name": "Interview",
      "status": "active",
      "submitted_at": "2026-01-20T09:15:00Z",
      "created_at": "2026-01-20T09:15:00Z",
      "updated_at": "2026-02-10T11:30:00Z",
      "candidate": {
        "id": "cand_ghi789",
        "first_name": "John",
        "last_name": "Doe",
        "email": "[email protected]"
      },
      "stage_history": [
        {
          "id": "sp_mno345",
          "stage_name": "Screening",
          "stage_type": "default",
          "status": "completed",
          "started_at": "2026-01-20T09:15:00Z",
          "completed_at": "2026-01-25T16:00:00Z"
        },
        {
          "id": "sp_pqr678",
          "stage_name": "Interview",
          "stage_type": "interview",
          "status": "in_progress",
          "started_at": "2026-01-25T16:00:00Z",
          "completed_at": null
        }
      ]
    }
  ],
  "pagination": { ... }
}

Get Application

GET /api/v1/applications/:id

Returns the same shape as an item in the list response.

Candidate PII Redaction

When a token has applications:read but not candidates:read, candidate data in application responses is redacted to only the ID:

{
  "candidate": {
    "id": "cand_ghi789"
  }
}

No first_name, last_name, or email fields are included. This lets job board integrations track application status without exposing candidate contact information.


Troubleshooting

Authentication Issues

401 Unauthorized

If you’re getting a 401 response, check:

  1. Token format: Ensure the token is exactly as shown in Settings > API (32-character hex string)
  2. Authorization header: Must be Authorization: Bearer TOKEN or Authorization: token TOKEN
  3. Token expiration: Check if expires_at has passed in Settings > API
  4. Account membership: Verify the token owner is still a member of the account

403 Forbidden

When you get a 403, the token is valid but lacks required scope:

  1. Check required scopes: Each endpoint documents its required scope (e.g., job_postings:read)
  2. Add missing scopes: Edit the token in Settings > API and add the needed scope
  3. Scope format: Must match exactly — job_postings:read, not job_postings or read

Data Access Issues

Empty data array in responses

If the API returns {"data": [], "pagination": {...}} but you expect results:

  1. Account isolation: API tokens only see data from their bound account
  2. Verify account: Check which account the token belongs to in Settings > API
  3. Cross-account access: Tokens cannot access data from other accounts, even if the user is a member

Candidate PII is redacted

If you see only {"candidate": {"id": "cand_..."}} without name or email:

  1. Expected behavior: This happens when the token has applications:read but not candidates:read
  2. Add scope: To see full candidate details, add the candidates:read scope to your token
  3. Privacy by design: This lets integrations track applications without exposing contact info

Query and Filtering Issues

404 when looking up specific records

  1. Use prefix IDs: Job postings use job_abc123 format, not database IDs like 42
  2. Case sensitivity: Prefix IDs are case-sensitive
  3. Account scoping: The resource must belong to the token’s account

Filter parameters not working

  1. Valid status values:
    • Job postings: draft, published, closed (case-sensitive)
    • Applications: active, rejected (case-sensitive)
  2. Parameter format: Use ?status=published, not ?status=Published or ?filter[status]=published

Frequently Asked Questions

How does pagination work?

All list endpoints return 20 items per page (fixed). Use ?page=2 for the next page. The pagination object shows total_pages and total_count.

Do tokens expire automatically?

Only if you set expires_at when creating the token. Otherwise, tokens remain valid until:

  • Manually revoked in Settings > API
  • User is removed from the account
  • Account is deleted

Should I use one token for multiple integrations?

No. Best practice is one token per integration with minimal scopes. This makes it easier to:

  • Audit which integration is making requests (via last_used_at)
  • Revoke access to a specific integration without affecting others
  • Grant different permissions to different systems

How do webhooks relate to the API?

Webhooks notify you of events (new application, stage change). The API lets you query current state. Use webhooks to trigger your system, then use the API to fetch full details.

Can I get more than 20 items per page?

No. The page size is fixed at 20 items to ensure consistent performance. Use the page parameter to iterate through all results.

Quick Checklist

  • Create an API token at Settings > API — each token is bound to that account
  • Assign only the scopes your integration needs (best practice: one token per integration with minimal scopes)
  • Check expires_at in Settings > API to see when the token expires (if set)
  • Include Authorization: Bearer TOKEN on every request
  • Use prefix IDs (e.g., job_abc123) in URLs, not database IDs
  • Handle 400 and 422 responses for validation errors
  • Handle 401 responses — your token may be expired or revoked
  • Handle 403 responses — your token may lack a required scope
  • Use page parameter to paginate through large result sets (20 items per page)

Type to search...