JuggleHire API
A REST API for reading and moving your jobs and candidates. It was built so an AI agent — Claude, ChatGPT, a script of your own — can work your pipeline with a token that only carries the permissions you grant it.
Quickstart
Create a token at Organization → API, then call the API with it. This returns the jobs the token's owner can manage:
curl https://app.jugglehire.com/api/v1/jobs \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json"Every response is JSON. Lists are paginated:
{
"data": [
{ "id": 1711, "title": "Senior Software Engineer", "status": "open", ... }
],
"links": { "first": "...", "last": "...", "prev": null, "next": "..." },
"meta": { "current_page": 1, "last_page": 2, "per_page": 15, "total": 26 }
}Authentication
The API uses bearer tokens. Create one in the app at Organization → API, give it a name you will recognise later, and tick only the scopes that integration needs. The token is shown once, at creation — copy it then, because it cannot be retrieved afterwards.
Authorization: Bearer YOUR_TOKEN
Accept: application/jsonTokens expire six months after they are created, and you can revoke one at any time from the same page. Team owners and admins see every token created inside their team, along with who created it and when it was last used — so an unused or forgotten token is easy to spot and kill.
A request with a missing, revoked, or expired token returns 401:
{ "error": { "code": "unauthenticated", "message": "Unauthenticated.", "details": null } }Scopes
A token carries only the scopes you tick. Calling an endpoint without its scope returns 403 insufficient_scope — the request never reaches your data. Start read-only and add write scopes when you actually need them.
| Scope | Grants |
|---|---|
jobs:read | List and read jobs, stages, and pipeline |
jobs:write | Create and update jobs |
candidates:read | Search and read applicants, notes, and ratings |
candidates:write | Move stages, change status, add notes and ratings |
messages:send | Send emails and messages to candidates |
scheduling:write | Schedule and cancel interviews |
messages:send and scheduling:write can be granted today but have no endpoints yet — messaging and interview scheduling are the next additions to this API.
Conventions
- Base URL is
https://app.jugglehire.com. Every path below is relative to it. - A single object comes back under
data; lists come back underdatawithlinksandmeta. Page through with?page=2; pages hold 15 records. - A token can only reach what its owner can reach in the app: every job on teams they own or administer, and on other teams only the jobs they own or are assigned to. Anything outside that returns
404rather than403, so the API never confirms that a job you cannot see exists. - Every write is recorded in your activity log with the token that made it.
Errors
Every error uses the same envelope:
{
"error": {
"code": "validation_failed",
"message": "The title field is required.",
"details": { "title": ["The title field is required."] }
}
}details carries field-level messages on a validation failure and is null otherwise.
| Status | Code | Means |
|---|---|---|
401 | unauthenticated | Missing, revoked, or expired token. |
403 | insufficient_scope | The token lacks the scope this endpoint needs. details.required_scope names it. |
403 | forbidden | Valid scope, but the account is not allowed to do this — a plan limit, for example. |
404 | not_found | No such record, or one the token cannot reach. |
422 | validation_failed | The body did not validate. See details. |
429 | rate_limited | Too many requests. Back off and retry. |
500 | server_error | Something broke on our side. The message is deliberately generic. |
Rate limits
60 requests per minute, counted per user rather than per token — two tokens belonging to the same person share one budget. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a throttled one adds Retry-After in seconds.
{ "error": { "code": "rate_limited", "message": "Too Many Attempts.", "details": null } }Idempotency
Any write accepts an optional Idempotency-Key header. Send the same key twice and the second call returns the first call's stored response instead of acting again — useful when a network error leaves you unsure whether a job was created.
curl -X POST https://app.jugglehire.com/api/v1/jobs \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Idempotency-Key: 8f14e45f-ea6a-4c3b-9f2d-1b7c0a5e6d90" \
-H "Content-Type: application/json" \
-d '{"title": "Backend Engineer"}'- A replayed response carries
Idempotent-Replay: true. - Keys are scoped to your team and remembered for 24 hours.
- Only successful responses are stored, so a call that failed can be retried safely with the same key. Use a fresh UUID per distinct action.
Identity
/api/v1/meany valid tokenWho the token belongs to, and what it can do. The cheapest way to check a token is alive.
{
"data": {
"id": 1,
"name": "Zakir Hossen",
"email": "you@example.com",
"team": { "id": 1, "name": "Lomeyo LLC" },
"scopes": ["jobs:read", "candidates:read"]
}
}Jobs
/api/v1/jobsrequires jobs:readJobs the token can manage, newest first. Query parameters: status (open, draft, or closed — defaults to open) and search, which matches the title.
curl "https://app.jugglehire.com/api/v1/jobs?status=open&search=engineer" \
-H "Authorization: Bearer YOUR_TOKEN"/api/v1/jobs/{job}requires jobs:readOne job in full.
/api/v1/jobs/{job}/stagesrequires jobs:readThe job's pipeline stages. You need these ids to move a candidate.
{ "data": [ { "id": 12, "name": "Applied" }, { "id": 13, "name": "Interview" } ] }/api/v1/jobsrequires jobs:writeCreates a job on the token owner's current team and returns it with 201. Body: title (required, max 255) and description (optional HTML). Everything else takes a sensible default — onsite, full-time, open, with a deadline 30 days out — which you can change afterwards in the app or with a PATCH. Reaching your plan's job limit returns 403.
curl -X POST https://app.jugglehire.com/api/v1/jobs \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Backend Engineer", "description": "<p>Build our API.</p>"}'/api/v1/jobs/{job}requires jobs:writeUpdates title, description, or status. Send only the fields you are changing.
curl -X PATCH https://app.jugglehire.com/api/v1/jobs/1711 \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "closed"}'Candidates
/api/v1/jobs/{job}/candidatesrequires candidates:readApplicants on a job, paginated. Query parameters: search (name or email) and stage, which takes a stage id from the stages endpoint.
{
"data": [
{
"id": 5821,
"name": "Amina Rahman",
"email": "amina@example.com",
"applied": "2026-08-01",
"stage": "Interview",
"status": "Shortlisted"
}
],
"meta": { "current_page": 1, "total": 34 }
}/api/v1/jobs/{job}/candidates/{candidate}requires candidates:readOne candidate, adding resume_url and notes_count. Empty fields are omitted rather than returned as null.
/api/v1/jobs/{job}/candidates/{candidate}/stagerequires candidates:writeMoves the candidate to a stage on that job. Body: stage, an integer id from the stages endpoint. A stage belonging to a different job returns 422; a candidate already in that stage is left alone.
curl -X POST https://app.jugglehire.com/api/v1/jobs/1711/candidates/5821/stage \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"stage": 13}'/api/v1/jobs/{job}/candidates/{candidate}/notesrequires candidates:writeAdds a note, returned with 201. Body: content, up to 5,000 characters.
/api/v1/jobs/{job}/candidates/{candidate}/ratingrequires candidates:writeSets the rating. Body: rating, one of good_fit, maybe, or not_a_fit. Anything else returns 422.
curl -X POST https://app.jugglehire.com/api/v1/jobs/1711/candidates/5821/rating \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rating": "good_fit"}'Something missing?
Messaging and interview scheduling are next. If you need an endpoint that is not here yet, email hello@jugglehire.com and tell us what you are building — it genuinely shapes what we add.
Create your API token