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_atcan 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:
- Token format: Ensure the token is exactly as shown in Settings > API (32-character hex string)
- Authorization header: Must be
Authorization: Bearer TOKENorAuthorization: token TOKEN - Token expiration: Check if
expires_athas passed in Settings > API - 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:
- Check required scopes: Each endpoint documents its required scope (e.g.,
job_postings:read) - Add missing scopes: Edit the token in Settings > API and add the needed scope
- Scope format: Must match exactly —
job_postings:read, notjob_postingsorread
Data Access Issues
Empty data array in responses
If the API returns {"data": [], "pagination": {...}} but you expect results:
- Account isolation: API tokens only see data from their bound account
- Verify account: Check which account the token belongs to in Settings > API
- 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:
- Expected behavior: This happens when the token has
applications:readbut notcandidates:read - Add scope: To see full candidate details, add the
candidates:readscope to your token - Privacy by design: This lets integrations track applications without exposing contact info
Query and Filtering Issues
404 when looking up specific records
- Use prefix IDs: Job postings use
job_abc123format, not database IDs like42 - Case sensitivity: Prefix IDs are case-sensitive
- Account scoping: The resource must belong to the token’s account
Filter parameters not working
- Valid status values:
- Job postings:
draft,published,closed(case-sensitive) - Applications:
active,rejected(case-sensitive)
- Job postings:
- Parameter format: Use
?status=published, not?status=Publishedor?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_atin Settings > API to see when the token expires (if set) - Include
Authorization: Bearer TOKENon every request - Use prefix IDs (e.g.,
job_abc123) in URLs, not database IDs - Handle
400and422responses for validation errors - Handle
401responses — your token may be expired or revoked - Handle
403responses — your token may lack a required scope - Use
pageparameter to paginate through large result sets (20 items per page)