Skip to main content
Developer guide

Build with ITManagerJobs

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 2. Mint an API key 3. Authenticate 4. Make your first call Job Posting API CV Search API Webhooks Rate limits Errors Versioning OpenAPI & SDKs Support

1. Sign up

You need an employer account on ITManagerJobs 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.

1

Create the account

Create an employer account if you don't already have one. Self-serve, no credit card needed to explore.

2

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.

3

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.

1

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.

2

Pick the scopes

JobPosting covers /api/v1/jobs/...; 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.

3

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.

4

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 Unauthorized immediately, with no body content.
  • The LastUsedAt timestamp 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://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

MethodPathDescriptionCost
GET/api/v1/jobsList your jobs (paginated, filterable by status)Free
GET/api/v1/jobs/{id}Single job + full descriptionFree
POST/api/v1/jobsCreate a draft jobFree
PUT/api/v1/jobs/{id}Patch any subset of mutable fieldsFree
POST/api/v1/jobs/{id}/publishDraft → 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://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://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"

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

EventWhen it firesPayload 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-Signaturet={ts},v1={hmac} as above.
  • X-Webhook-Eventapplication.submitted etc.
  • 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 familyLimit
/api/v1/jobs/... (CRUD)100 req/min per key
/api/v1/candidates/search30 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.

StatusCommon codesWhat to do
400 Bad Requestinvalid_request, invalid_query, invalid_stateFix the request body. Surface the message to your user.
401 UnauthorizedKey is missing, revoked, or expired. Mint a new one.
402 Payment Requiredno_creditsTop up credits — your contract or balance is exhausted. Calls do not consume credits when they 402.
403 ForbiddenKey is valid but doesn't carry the scope the endpoint requires. Mint a new key with the right scope.
404 Not Foundnot_foundTenant-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 RequestsBack off. Honour Retry-After if present.
5xxTransient 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.

OpenAPI & SDKs

Fully machine-readable OpenAPI 3 specification:

/swagger/v1/swagger.json

Generate clients in any language using the OpenAPI generator of your choice (openapi-generator, Kiota, NSwag, etc.) or import the spec into Postman for ad-hoc exploration.

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.