Build with IT Manager Jobs (Demo)
Programmatic access to job posting, candidate search, and pipeline events for ATS partners (Bullhorn, Vincere, Mercury, JobAdder, custom in-house tools). Stable v1 contract, OpenAPI-described, signed webhooks, no extra licence fee.
1. Sign up
You need an employer account on IT Manager Jobs (Demo) before you can access the API. The same account that posts jobs through the website is the one your ATS integration will run against โ credits, contracts, and pipeline data are all shared between the two.
Create the account
Create an employer account if you don't already have one. Self-serve, no credit card needed to explore.
Pick a package or contract
API credits are the same as portal credits โ buy a job-posting bundle from our pricing page, or talk to sales about an unlimited contract for higher-volume use.
Be an employer admin
Only users with the EmployerAdmin role can mint API keys. Have your account owner do step 4, or have them invite you as an admin from the Team page.
2. Mint an API key
Sign in to the employer portal as an admin, then open API keys under your account.
Click "Create key"
Give the key a descriptive name โ usually the integration that'll use it ("Bullhorn production", "internal HR Slack bot"). One key per integration is the right pattern; revoking a single integration is then a single click.
Pick the scopes
JobPosting covers /api/v1/jobs/...; Applications covers the recruiter applicant pipeline (/api/v1/applications/..., plus tags, notes and interviewer scorecards); CvSearch covers /api/v1/candidates/.... Pick only what the integration needs โ endpoints reject keys that don't carry the right scope, and a narrow scope limits the blast radius if a key leaks.
Optionally set an expiry
Production integrations usually leave this blank. Internal scripts and time-bounded data migrations should set a date โ expired keys auto-disable.
Copy the key, then wait for approval
The plaintext pk_live_โฆ value is shown exactly once on creation. Store it in your secrets manager (AWS Secrets Manager, HashiCorp Vault, 1Password, GitHub Actions secrets, etc.) before closing the dialog. We only store a hash; we cannot recover the key for you.
Most keys for your own job-posting flow are auto-approved instantly — you can call the API immediately. Keys with the CvSearch scope, and keys for accounts without an active contract or credit balance, queue for one-business-day platform review. You'll get an email the moment the key is approved; until then API calls return 401 Unauthorized — that's expected, not a key error.
Trusted partners (signed integrations, repeat customers) can be flagged for instant approval on every key — talk to us if your integration is shipping to multiple environments and you don't want each one to wait. Lost a key? Revoke it on the API keys page and mint a new one.
3. Authenticate
Send the key as a Bearer token (preferred) or as a dedicated X-Api-Key header. Both work; pick whichever fits your HTTP client cleanest.
Authorization: Bearer pk_live_AbCdEf...
# or
X-Api-Key: pk_live_AbCdEf...
- Keys are tenant-scoped to the employer that minted them โ a key from one employer can never read another employer's data.
- Each key carries a fixed set of scopes chosen at creation time. Endpoints reject keys missing the required scope with
403 Forbidden. - Revoked, expired, or unknown keys return
401 Unauthorizedimmediately, with no body content. - The
LastUsedAttimestamp is updated on every successful call, visible on the API keys management page.
4. Make your first call
The simplest verification is to list your jobs โ empty, but a 200 confirms auth + scope are right:
curl -H "Authorization: Bearer pk_live_โฆ" \
https://demo.it-manager-jobs.com/api/v1/jobs
# {"total":0,"page":1,"pageSize":50,"items":[]}
If you got 401: the key is wrong or revoked. If you got 403: the key was minted without the JobPosting scope โ mint a new one with both scopes, or with the right one for your integration.
Job Posting API Scope: JobPosting
| Method | Path | Description | Cost |
|---|---|---|---|
| GET | /api/v1/jobs | List your jobs (paginated, filterable by status) | Free |
| GET | /api/v1/jobs/{id} | Single job + full description | Free |
| POST | /api/v1/jobs | Create a draft job | Free |
| PUT | /api/v1/jobs/{id} | Patch any subset of mutable fields | Free |
| POST | /api/v1/jobs/{id}/publish | Draft โ Active. Sets postedDate and expiryDate. | 1 Job Posting credit |
| DELETE | /api/v1/jobs/{id} | Deactivate (soft-close). Existing applications are preserved. | Free |
Example โ create then publish
curl -X POST https://demo.it-manager-jobs.com/api/v1/jobs \
-H "Authorization: Bearer pk_live_โฆ" \
-H "Content-Type: application/json" \
-d '{
"title": "Senior Platform Engineer",
"reference": "PL-001",
"description": "Build and run the platform...",
"city": "London",
"country": "United Kingdom",
"employmentType": "FullTime",
"remoteType": "HybridRemote",
"applyMethod": "EmailApply",
"applyEmailAddress": "[email protected]"
}'
# 201 Created
# {"id":1234,"status":"Draft", ... }
curl -X POST https://demo.it-manager-jobs.com/api/v1/jobs/1234/publish \
-H "Authorization: Bearer pk_live_โฆ" \
-H "Content-Type: application/json" \
-d '{"expiryDate":"2026-06-01T00:00:00Z"}'
# 200 OK โ status now "Active"Applications API Scope: Applications
Read the applicant pipeline for jobs you own, fetch the AI suitability assessment computed when the application landed, move candidates through stages, and write the ATS surface โ tags, recruiter notes, and interviewer scorecards. Tenant-scoped to the key's employer; cross-tenant calls return 404.
| Method | Path | Description | Cost |
|---|---|---|---|
| GET | /api/v1/jobs/{jobId}/applications | Paginated list of applications for one of your jobs (filterable by status) | Free |
| GET | /api/v1/applications/{id} | Single application โ candidate name, email, headline, contact details, status, screener answers | Free |
| GET | /api/v1/applications/{id}/ai-assessment | AI suitability score + verdict + rubric breakdown. Reads the persisted score; never re-runs the model. | Free |
| POST | /api/v1/applications/{id}/status | Move the application to a new stage (New, Screened, Shortlisted, Interviewing, Interviewed, Offered, Hired, Rejected, Withdrawn). Writes an audit-history row. | Free |
| GET | /api/v1/tags | Your employer's tag dictionary with per-tag usage counts | Free |
| GET | /api/v1/applications/{id}/tags | Tags assigned to the candidate behind this application | Free |
| POST | /api/v1/applications/{id}/tags | Assign a tag (auto-created on first use; case-folded so "Hot" and "HOT" share one tag) | Free |
| DELETE | /api/v1/applications/{id}/tags/{label} | Unassign a tag from the candidate | Free |
| GET | /api/v1/applications/{id}/notes | Recruiter notes (application-scoped + candidate-level) | Free |
| POST | /api/v1/applications/{id}/notes | Create a note. Attributed to the user who minted the key. | Free |
| PUT | /api/v1/notes/{id} | Edit a note. Only the original author can. | Free |
| DELETE | /api/v1/notes/{id} | Soft-delete a note. Only the original author can. | Free |
| GET | /api/v1/applications/{id}/scorecards | All interviewer scorecards on this application | Free |
| PUT | /api/v1/applications/{id}/scorecards/{stage} | Upsert your scorecard for the given pipeline stage. Recommendation: StrongNo, No, Hold, Yes, StrongYes. | Free |
| DELETE | /api/v1/applications/{id}/scorecards/{stage} | Delete your scorecard for the given stage. | Free |
Example โ list, read, move stage
curl -H "Authorization: Bearer pk_live_โฆ" \
"https://demo.it-manager-jobs.com/api/v1/jobs/1234/applications?status=New&pageSize=20"
curl -H "Authorization: Bearer pk_live_โฆ" \
https://demo.it-manager-jobs.com/api/v1/applications/9876/ai-assessment
# 200 OK โ { score, verdict, summary, rubricScores: [...] }
curl -X POST https://demo.it-manager-jobs.com/api/v1/applications/9876/status \
-H "Authorization: Bearer pk_live_โฆ" \
-H "Content-Type: application/json" \
-d '{ "status": "Shortlisted", "note": "Top of stack โ book a screen" }'
Note attribution. Notes and scorecards record an author user id. If the API key was minted without an associated user (rare โ sales-side mint), note / scorecard writes return 403 unattributable. Re-mint the key from your own account to enable those writes.
Candidate API Scope: Profile, Applications
Candidate-self surface โ read & update your own profile, list your own applications. Authenticated by a candidate-issued API key (separate from the recruiter key flow), minted from your candidate API keys page. Lets you wire up AI assistants (Claude, Copilot, Cursor) or the jbme CLI to drive your job-search workflow conversationally.
Isolation. Every candidate key only ever resolves to the owning candidate's own data โ there's no way for a key to see another candidate's profile or applications. Validated in a separate database table from recruiter keys; the auth schemes don't cross-pollinate.
| Method | Path | Description | Cost |
|---|---|---|---|
| GET | /api/v1/me/profile | Full profile bundle โ identity, contact, socials, headline, summary, address, plus preferences, skills, languages, nationalities, employment history, education. Includes a 0-100 completeness score. | Free |
| PUT | /api/v1/me/profile | All-optional patch โ every field is optional, null means "leave alone". Covers identity, contact, socials, address, and the preferences subset (career status, salary expectations, notice period, job-type prefs). Returns the full bundle on success. | Free |
| GET | /api/v1/me/applications | Paginated list of your own applications (filterable by status) | Free |
| GET | /api/v1/me/applications/{id} | Single application โ job, employer, status, applied + updated timestamps, cover letter, external resume URL | Free |
| POST | /api/v1/me/applications | Apply to one job on the candidate's behalf. Scope Apply. Required body fields: jobId (one per call โ no array), confirm: true (a missing flag returns 400 confirmation_required). Same eligibility gates as the portal apply (consent freshness, screening-question requirements). Successful submissions surface to the recruiter with an "AI-assisted" badge. | Free |
Apply safeguards
The apply endpoint is the highest-risk surface in the candidate API because AI fan-out can spam-apply on the candidate's behalf. The platform enforces several guardrails server-side:
- Explicit per-call confirmation. Every request body must carry
"confirm": true. The MCP tool description spells out "you are applying on behalf of the candidate โ confirm with them first"; missing the flag means a clean400rather than a silent submission. - 10 successful applies per UTC day per candidate. Enforced across every key the candidate holds โ revoking and re-minting doesn't reset the counter, and an AI client running on two devices can't split traffic to exceed the cap. Hitting it returns
429with aRetry-Afterheader pointing at the next free slot. - One
jobIdper call. No array; no bulk-apply. Each application requires its own deliberate tool invocation. - Same consent gate as the portal. Candidates whose data-processing consent has lapsed are blocked here too โ the v1 surface MUST NOT be a way around the platform's GDPR posture.
- Recruiter visibility. Successful submissions carry an
IsAiAssistedflag and surface as a small "AI-assisted" badge in the recruiter's applications list + detail page. Recruiters can weight the application accordingly; the lifecycle is otherwise identical to a portal apply.
Example โ read, update, list applications
curl -H "Authorization: Bearer pk_live_โฆ" \
https://demo.it-manager-jobs.com/api/v1/me/profile
# 200 OK โ full profile bundle
curl -X PUT https://demo.it-manager-jobs.com/api/v1/me/profile \
-H "Authorization: Bearer pk_live_โฆ" \
-H "Content-Type: application/json" \
-d '{
"headline": "Senior Platform Engineer",
"expectedSalaryMin": 95000,
"expectedSalaryMax": 120000,
"expectedSalaryCurrency": "GBP",
"careerStatus": "OpenToOffers"
}'
# 200 OK โ returns the patched profile
curl -H "Authorization: Bearer pk_live_โฆ" \
"https://demo.it-manager-jobs.com/api/v1/me/applications?status=Interviewing&pageSize=10"
curl -X POST https://demo.it-manager-jobs.com/api/v1/me/applications \
-H "Authorization: Bearer pk_live_โฆ" \
-H "Content-Type: application/json" \
-d '{
"jobId": 1234,
"confirm": true,
"coverLetter": "Quick note from the candidate โ strong on Kubernetes."
}'
# 201 Created โ body includes { isAiAssisted: true, aiAppliesUsedToday, aiAppliesLimitPerDay }
# 400 if confirm is missing; 409 if you've already applied to this job;
# 429 if you've hit 10 AI applies in the last 24 hours.CV Search API Scope: CvSearch
| Method | Path | Description | Cost |
|---|---|---|---|
| POST | /api/v1/candidates/search | Boolean search. Returns masked rows (no contact details). | 1 CV Search credit |
| GET | /api/v1/candidates/{id} | Full profile + email. Free if you've unlocked the candidate within the last 30 days. | 1 CV Download credit (or free) |
Boolean query syntax matches Elasticsearch's query_string: phrase quoting, AND/OR/NOT, parentheses, wildcards. Examples:
"Process Engineer" AND "HVAC"(("High" OR "Low") AND "voltage") AND "Engineers" AND "Dubai"kubernetes AND (aws OR gcp) AND NOT junior
Example
curl -X POST https://demo.it-manager-jobs.com/api/v1/candidates/search \
-H "Authorization: Bearer pk_live_โฆ" \
-H "Content-Type: application/json" \
-d '{
"query": "\"Process Engineer\" AND \"HVAC\"",
"preferredCountryIds": [44],
"activeWithinDays": 90,
"pageNumber": 1,
"pageSize": 20
}'
# 200 OK โ items are masked (e.g. "fullName": "P*** E***")
# Each item carries an "isUnlocked" flag.
curl -H "Authorization: Bearer pk_live_โฆ" \
https://demo.it-manager-jobs.com/api/v1/candidates/12345
# 200 OK โ full profile + email
# Subsequent calls within 30 days are free (uses the existing unlock).Webhooks
Register an HTTPS endpoint and receive signed event POSTs whenever something happens to your jobs or applications. You don't need to poll the API.
Available events
| Event | When it fires | Payload includes |
|---|---|---|
application.submitted |
A candidate submits an application to one of your jobs (any apply method) | applicationId, jobPostId, jobTitle, candidateId, appliedAt, applicationType |
application.status_changed |
A recruiter moves an application through the pipeline (New โ Screened โ Interviewed โ ...) | applicationId, previousStatus, newStatus, changedAt |
job.expired |
A posting reaches its expiryDate and is auto-expired by the platform |
jobPostId, expiredAt |
Subscribe
Subscriptions are managed at /employer/webhooks. Each subscription gets a fresh HMAC-SHA256 signing secret, returned exactly once on creation.
Verifying signatures
Every delivery includes an X-Webhook-Signature header of the form t={unix-ts},v1={hex-hmac}. Recompute the HMAC on your side and compare in constant time:
// Node.js
const [tPart, v1Part] = req.headers['x-webhook-signature'].split(',');
const t = tPart.split('=')[1];
const v1 = v1Part.split('=')[1];
const signed = `${'$'}{t}.${'$'}{rawBody}`;
const expected = crypto.createHmac('sha256', secret)
.update(signed)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) {
return res.status(403).end();
}
if (Math.abs(Date.now()/1000 - Number(t)) > 300) {
return res.status(400).end(); // older than 5 minutes โ replay
}
Headers on every delivery
X-Webhook-Signatureโt={ts},v1={hmac}as above.X-Webhook-Eventโapplication.submittedetc.X-Webhook-Delivery-Idโ stable id for this delivery attempt; useful for de-duplication on retries.
Retries and dead-lettering
Respond with any 2xx within 10 seconds to mark a delivery successful. Anything else (including timeouts) is retried with exponential backoff: 1m โ 5m โ 15m โ 1h โ 6h โ 24h โ 24h โ 24h. After eight failed attempts the delivery is dead-lettered. After ten consecutive failures across any deliveries, the subscription is auto-disabled and the employer admin is emailed.
Idempotency is your responsibility โ retries can deliver the same event twice. Key off X-Webhook-Delivery-Id or the entity id in the payload.
Rate limits
| Endpoint family | Limit |
|---|---|
/api/v1/jobs/... (CRUD) | 100 req/min per key |
/api/v1/applications/... (reads & writes) | 100 req/min per key |
/api/v1/me/... (candidate self) | 100 req/min per key |
/api/v1/candidates/search | 30 req/min per key |
/api/v1/candidates/{id} | 60 req/min per key |
| Webhook deliveries (outbound) | 50 in flight per subscription |
Rate-limited requests return 429 Too Many Requests. Back off and retry โ credits are not consumed by rate-limited requests.
Errors
Every 4xx response from /api/v1/... follows the same envelope:
{
"code": "no_credits",
"message": "Insufficient job posting credits."
}
code is the machine-readable identifier you switch on. message is the human-readable summary, suitable for surfacing in your own UI.
| Status | Common codes | What to do |
|---|---|---|
| 400 Bad Request | invalid_request, invalid_query, invalid_state | Fix the request body. Surface the message to your user. |
| 401 Unauthorized | โ | Key is missing, revoked, or expired. Mint a new one. |
| 402 Payment Required | no_credits | Top up credits โ your contract or balance is exhausted. Calls do not consume credits when they 402. |
| 403 Forbidden | โ | Key is valid but doesn't carry the scope the endpoint requires. Mint a new key with the right scope. |
| 404 Not Found | not_found | Tenant-scoped โ the object exists but doesn't belong to your employer, or doesn't exist at all. The two are deliberately indistinguishable. |
| 429 Too Many Requests | โ | Back off. Honour Retry-After if present. |
| 5xx | โ | Transient on our side. Retry with backoff; keep idempotency in mind. |
Versioning
The public surface is versioned in the URL. v1 is a frozen contract: field renames, removals, status-code changes, or error-envelope tweaks are breaking changes and ship under /api/v2/..., not in v1.
- New additive changes (extra optional fields on responses, new endpoints) ship within v1 and are non-breaking.
- v1 stays available for an announced deprecation window when v2 ships โ minimum 12 months, more if partner adoption justifies it.
- Snapshot tests in our CI guard the v1 contract; an accidental shape change breaks the build long before it can ship.
Install the CLI
Drive the platform from your terminal with the jbjobs tool โ job posting, applicant pipeline, AI assessments, candidate workflows. Self-contained binary, no .NET runtime install required.
Windows (PowerShell)
iwr -useb https://it-manager-jobs.com/install/jbjobs.ps1 | iex
macOS / Linux
curl -fsSL https://it-manager-jobs.com/install/jbjobs.sh | sh
The installer downloads the latest signed binary from our GitHub releases, verifies its SHA-256 against the published checksums, and drops it on your PATH. Per-user install โ no admin / sudo required. Re-running the same command upgrades in place.
After install, point the tool at your tenant:
# Windows (PowerShell)
$env:JOBBOARD_API_BASE = "https://your-tenant.example.com"
$env:JOBBOARD_API_KEY = "pk_live_โฆ"
# macOS / Linux
export JOBBOARD_API_BASE="https://your-tenant.example.com"
export JOBBOARD_API_KEY="pk_live_โฆ"
jbjobs --help
jbjobs jobs list --status Active
Already have the .NET SDK? You can also install via NuGet: dotnet tool install --global JobBoardCli. The one-line installer above doesn't require .NET โ the binary it downloads is self-contained.
OpenAPI & Postman
Two stable, machine-readable definitions for the public surface โ pick whichever your toolchain prefers:
- Download the OpenAPI 3.0 spec (YAML) โ hand-curated, contract-locked. Import into openapi-generator, Kiota, NSwag, Stoplight, Insomnia, etc. to generate clients in any language.
- Download the Postman collection (v2.1) โ every endpoint pre-populated with example payloads. Set the
base_urlandapi_keycollection variables and start sending requests. - Live, auto-generated reflection of the running service: https://demo.it-manager-jobs.com/swagger/v1/swagger.json โ useful for cross-checking against the running build, but the hand-curated YAML above is the contract.
We don't yet ship language-specific SDKs; the OpenAPI document is the canonical source. If demand for a packaged client emerges, the most-requested language will land first โ let us know via the contact form.
Support
Stuck? Have a partnership question? Need a higher rate limit, an unlimited contract, or a custom event we haven't built yet? Contact us โ we read every message and we're happy to walk you through how the API would fit your team's workflow.
Production incidents that affect API availability are tracked at /healthz/ready (returns 200 when the platform is healthy). For ongoing partnerships we'll hand you a dedicated technical contact.