Job Opportunities API

Check the data. Then trust it.

api

Authentication

One header, three accepted spellings, and a deliberate refusal to tell an attacker which kind of wrong a wrong key is.

Last verified 2026-08-22 · Assumes: That you have a key, or have read the quickstart. · Markdown copy

1The header

Authorization: Bearer <your key>, on every /v1 request. Nothing at all on /public — a key there is neither required nor useful.

In depthWhy it exists, what it is not, what people get wrong

There is one credential and it is the key's secret. There is no client id, no secret pair, no OAuth flow, no token exchange and no expiry to refresh. A key is created on the dashboard, it works until you revoke it, and it identifies a plan and a meter.

The secret is shown exactly once, when the key is created. It is stored as a SHA-256 hash, so nobody can recover it — if you lose it, rotate the key and it keeps its identity, its plan and its usage history while getting a new secret.

Exact contractTypes, defaults, ranges, errors, edge cases
The canonical form
GET /v1/jobs?country=IE&limit=25 HTTP/1.1
Host: api.jobopportunitiesapi.org
Authorization: Bearer sk_live_your_secret_here
Accept: application/json

The key travels in the header, never in the query string. A key in a URL ends up in browser history, in proxy logs, in Referer headers and in anything that records URLs, and there is no parameter here that accepts one.

A minimal authenticated request
$ export JOA_KEY='your-api-key-here'
$ curl -s -H "Authorization: Bearer $JOA_KEY" \
  'https://api.jobopportunitiesapi.org/v1/jobs?country=IE&limit=2' | jq '.data[].title'
"Senior Software Engineer"
"Customer Success Manager"
Titles will differ; this request is re-sent against the live API on every build of this site.

2Three accepted spellings, and why

Bearer <key>, bearer <key> and the bare <key> with no scheme are all accepted. Bearer is canonical; the tolerance exists for integration platforms.

In depthWhy it exists, what it is not, what people get wrong
Header valueAcceptedComment
Bearer sk_…YesThe canonical form. Write this.
bearer sk_…YesRFC 7235 requires the scheme token to be matched case-insensitively anyway.
BEARER sk_…YesSame rule.
sk_…YesThe bare key, no scheme. Shipped 2026-08-22.
?key=sk_… in the query stringNoThere is no such parameter, deliberately.
X-Api-Key: sk_…NoNot read.

The bare form is not sloppiness, it is a measured response to how the key is actually pasted. Integration platforms — Clay, Bubble, n8n, and every other product with a “paste your API key” box — give the user one field and send its contents verbatim. Refusing that produced a 401 whose only clue was the error message, and generated support mail for a case where the user had done nothing wrong.

Both spellings hash to the same lookup, so nothing about the security boundary moves. Document and write Bearer; expect the bare form to work if a tool sends it.

Exact contractTypes, defaults, ranges, errors, edge cases

The secret is hashed with SHA-256 and compared against the stored hash. There is no prefix-based lookup shortcut that could leak timing information about which keys exist, and a revoked or expired key takes exactly the same path as an unknown one.

3What a rejected key looks like

Missing, unknown, revoked and expired keys all return the same 401 with the same body. The API will not tell a prober which kind of wrong they have.

In depthWhy it exists, what it is not, what people get wrong

Distinguishing “this key was valid once” from “this key never existed” is useful to exactly one person, and it is not you. So all four failures are identical: same status, same error code, same message.

The practical consequence is that when a 401 surprises you, the debugging order is: check the header spelling, check you are on api.jobopportunitiesapi.org, check the key on the dashboard, and only then suspect the key itself.

Exact contractTypes, defaults, ranges, errors, edge cases
The 401, exactly as returned
$ curl -s https://api.jobopportunitiesapi.org/v1/jobs
{
  "error": "unauthorized",
  "message": "Send your key as: Authorization: Bearer YOUR_API_SECRET
               — the key on its own is accepted too.",
  "docs": "https://jobopportunitiesapi.org/api"
}
Captured 2026-08-22. HTTP 401.
SituationStatus`error`What to do
No Authorization header401unauthorizedAdd it.
Unknown secret401unauthorizedCheck the dashboard.
Revoked key401unauthorizedCreate or rotate a key.
Expired key401unauthorizedSame.
Valid key, endpoint not on your plan403upgrade_requiredX-JOA-Required-Feature names the missing entitlement — delta_feed or bulk_export.
Valid key, month's records spent402record_quota_exhaustedDo not retry. See the record meter.
Valid key, too many requests429Wait Retry-After seconds.

4Creating, naming, rotating and revoking

Keys are managed on the dashboard. Rotating replaces the secret and keeps everything else; revoking is permanent and the row is kept for audit.

In depthWhy it exists, what it is not, what people get wrong
Create
Create a free key on /dashboard, or buy a plan at /pricing. The secret is shown once.
Name
Keys can be named, so a list of them is readable. The name is yours and is not sent anywhere.
Rotate
Issues a new secret for the same key. The plan, the meter and the usage history stay attached. Use this after a leak or a lost secret — it is not the same as creating a new key.
Revoke
Immediate and permanent. The key row is kept rather than deleted, because usage attached to it has to stay auditable.
Cancel
Cancels the subscription behind a paid key. See Cancelling.

You can hold more than one key. The usual reason is to separate environments — a key for production and one for a staging job — so that revoking one does not stop the other. The record allowance is per key, not per account, so splitting also splits the meter.

Exact contractTypes, defaults, ranges, errors, edge cases

Key management is done through the website's session, not through the API key itself: the endpoints behind it (POST /public/auth/keys, POST /public/auth/keys/{id}/rotate, .../revoke, .../name) require the signed session cookie the magic link creates, and a key cannot manage itself. That is deliberate — a leaked key cannot be used to rotate away from you.

GET /v1/me is the one place a key can inspect itself: it returns the plan, the status, the per-day and per-minute limits, the created date, the key prefix and today's usage. It costs no records and is safe to poll.

This page was rendered 11 September 2026, 03:39 UTC. Every figure on it comes from the endpoint named beside it, and every published request is re-sent against the live API before this site is allowed to build. If something here is wrong, the thumbs-down above reaches a person.