Docs / Shared / Versioning & changes
Versioning & changes
What "v1 · stable" commits to, what counts as a breaking change, how much notice you get, and the newest changes.
What “v1 · stable” commits to
Three things, and they are the whole promise:
- A field that exists will not be removed or change meaning inside
v1. - A parameter that works will keep working, and keep meaning the same thing.
- An id stays valid.
<source>:<source_id>is stable for the life of a record — if a record is removed you get a410, never a silently reused id.
What it does not commit to: the data. Sources change what they publish, stop publishing, and get taken down. That is the product, not a version question.
What counts as breaking
| Breaking — you get notice | Not breaking — ships any time |
|---|---|
| Removing a field, or changing its type | Adding a field |
| Removing a parameter, or narrowing what it accepts | Adding a parameter |
Lowering a collection's cap on limit, or billing a route documented as unmetered | Raising a cap, or making a route unmetered |
| Changing what an existing parameter defaults to | Adding an error code for a case that used to be a generic failure |
| Changing an HTTP status for an existing case | Adding an endpoint |
| Making a nullable field non-nullable, or the reverse | Changing a message string (never branch on these — branch on code) |
The line that catches people: a default is part of the contract. Changing what ?active= returns when you do not pass it changes every existing integration's result set without changing one line of their code, so it is treated as breaking even though nothing was removed.
Notice
Some changes never happen inside v1: a field's type, whether it can be null, what a parameter defaults to, and what a field or a parameter means. A change like that alters what an integration receives without anything failing, and no notice on a page makes that safe. If one is ever needed, it ships as a new path, /v2/… beside the /v1/… one, which nobody is moved onto, and /v1 keeps answering, unchanged, for at least 12 months after.
A removal gets 90 days' notice, whether it is a field, a parameter or an operation. A removed one fails loudly, so notice is enough, as long as it reaches your code and not only this page. From the day a removal is announced to the day it takes effect:
- A response it touches carries three headers:
Deprecation, the day it was announced (RFC 9745);Sunset, the day it goes (RFC 8594); andLinkwithrel="deprecation", pointing at its row below. A response is touched when it carries the field, when its request sends the parameter, or when it comes from the operation, and no other response carries them. A browser can read all three. GET /v1/schemalists it under each operation it touches, indeprecations, withannounced,sunsetand what to read instead. A client that keeps a copy of the schema sees it on its next refresh.- What is going keeps being served, unchanged, until that day.
Before a release ships, its GET /v1/schema is compared with the last release's, and a release that removes something not announced here 90 days before, or changes a type, a nullability, a default published in defaults, a page cap, what a route costs or an error's status, or makes a parameter required, is stopped.
Four exceptions, and they are the only four:
- A security fix — as fast as it takes, explained here afterwards.
- A legal removal. When an employer or an individual asks for a record to be removed we act immediately; that is what
410 goneis for, and it is not a version change. - A refusal for an input that could never have matched. Narrowing a parameter to reject a value no row could ever have carried, so the old answer was an empty collection and the new one is a
400. Nothing you could have received is withdrawn — what changes is the status, and the fact that you are now told. It is still a change to an HTTP status for an existing case, which is why it is listed here as an exception rather than moved to the right-hand column, and it is announced in the Changelog on the day it ships rather than 90 days before. The bar is narrow and it is the reason the exception exists: an empty page for a value we could never have held reads as “we hold nothing for you”, which is the one answer this API must never give by accident. Used twice so far — an unknown?source=stem on 2026-08-28, and a?country=that is not an ISO 3166-1 alpha-2 code on 2026-09-05. If either of those broke you, say so: it is the exception most likely to be wrong, because a client that treats every400as fatal is affected by it even though no data moved. - Retiring a dataset no key has called for 30 days. Its retirement is announced here with its date, and its routes carry
DeprecationandSunsetfrom that day until they answer410 gone. A dataset someone still calls gets the full 90 days. This exception was written down on 2026-10-31; the seventeen datasets retired on 2026-10-16 went without notice, as their entry says.
If a change would break you and 90 days is not enough, say so — it is easier to hear that before the change than after.
Announced
Every removal with a date. Each is also listed in GET /v1/schema under the operations it touches, and announced in the responses it touches.
| Change | Announced | Takes effect | What to do now |
|---|---|---|---|
The birjob_source_name key is removed from facts, on GET /v1/jobs, GET /v1/jobs/{id} and GET /v1/companies/{id}/jobs |
2026-10-31 | 2027-01-30 | Only postings imported from the archive that predates this API carry it, and it names, in that archive's spelling, the source that source_name and source name. Read source_name (the source's name) or source (its id) instead: they are on every posting, not just imported ones. Nothing else in facts changes. |
Withdrawn. Three changes were announced here on 2026-10-03: active becoming nullable and the 401 split, both for 2027-01-01, and the ?active= default moving to true on 2027-01-08. All three were withdrawn on 2026-10-15, before any took effect, and nothing changes on those dates. What each was for is served instead as an addition, which leaves everything an integration already reads exactly as it was:
activestays a boolean. A new field on every posting,inactive_reason, says why one is inactive.absent_from_sourceandsource_goneare findings about the posting;source_unreachable,source_origin_downandsource_darkare facts about our access to its source, not about the posting.- A rejected key still answers
401withcode: "unauthorized", and a newerror.reasonsays why:invalid_api_keyorrevoked_api_key. ?active=keeps its default ofany, andrecordsonGET /v1/sourceskeeps its meaning. To show only open postings, passactive=true; for a source's open postings, readlive_records.
If you prepared for the withdrawn changes, nothing you wrote breaks: active will not be null, and invalid_api_key and revoked_api_key arrive as error.reason, never as code.
Queued
Nothing is planned without a date. A removal is listed here as soon as it is planned, so that you do not learn of it from an email, and it moves up to Announced, with a date at least 90 days out, before it ships.
Changelog
The newest changes on v1. The changelog has every one since 2026-08-21, and says of each that was not an addition what it was and why.
2026-10-31
- Announced:
facts.birjob_source_nameis removed on 2027-01-30. Only postings imported from the archive carry the key, and it names the source thatsource_nameandsourcename on every posting: read those instead. Until then a response that carries the key says so in the headers below. Announced. - A response touched by an announced removal says so in three headers,
Deprecation,SunsetandLink, and a browser can read all three.GET /v1/schemalists each announced removal under the operations it touches, indeprecations. Both are additions. Notice. GET /v1/schemapublishes four defaults, indefaultson the operations that have them:limitandorderonGET /v1/jobsandGET /v1/companies/{id}/jobs, andactiveandcountonGET /v1/jobs. They are the defaults the reference already gave, and none changed. An addition.- What
v1never changes is written down: a type, a nullability, a default and a meaning. Such a change would ship as a/v2path, with/v1kept for at least 12 months after. Retiring a dataset no key has called for 30 days is a fourth notice exception. Notice.
2026-10-25
- What the retired datasets collected is deleted. The seventeen datasets retired on 16 October kept what they had collected, as that entry says. On this date we decided to delete it, and it is no longer held. Nothing you can call changes: every route under those datasets still answers
410withcode: "gone",GET /v1/schemastill documents them withstatus: "retired", and/v1/jobs,/v1/jobs/{id},POST /v1/jobs/liveness,/v1/companies/{id}/jobsandGET /v1/sourcesare unchanged. Privacy.
2026-10-22
- The Free plan is 100 requests a month, down from 1,000. It applies to every account on the plan from this date, existing ones included; the paid plans and every rate limit are unchanged. An account already past 100 this month is refused with
429andquota_exceededuntil the allowance resets on the 1st.X-Quota-Limitcarries the figure applied to your account, and an allowance agreed for one account is not affected. No route, field or status changes. Limits.
Earlier changes, from 2026-10-18 back to 2026-08-21, are on the changelog.