Filter the job feed like the Feed Builder
POST /api/jobs/feed now takes every Feed Builder condition as a request filter, so you can stream a narrow slice without saving a feed first.How they match:
- Occupation and company filters take
{"include": [...], "exclude": [...]}, case-insensitively. Different filters combine with AND. - Occupation filters (against each job’s
occupation) andcompaniesmatch as onPOST /api/jobs/search.GET /api/occupationslists every value; filter by id, since names can improve between taxonomy releases. company_is_agency: falseleaves out jobs that recruitment and staffing agencies post for other employers;truereturns only theirs.- An
exclude_list leaves out jobs with any listed value. It takes the same values as the include list of the same name; as withlanguages, language names and locales map to ISO 639-1 codes. - Include and exclude lists combine:
{"sources": ["greenhouse", "lever"], "exclude_sources": ["lever"]}streams Greenhouse only. - Jobs or companies without a value, such as an unknown work model, never match an
includelist and are kept by anexcludeorexclude_list.
400 for an unknown size or revenue band, more than 1,000 values in one list, or any of these filters with stable_scan: false.companies entries are matched on the first page, and the cursor keeps the matched company ids, so a scan’s companies are fixed when it starts. An entry that matches no company is not a 400. The first page reports it in a new warnings array, and the codes repeat on the X-Jobo-Warnings header:companies_unmatched: anincludeentry matched no company.companies_exclude_unmatched: anexcludeentry matched no company.companies_outside_sources: the matched companies are on none of the requestedsources.
include entry matches, or every match is outside sources, the first page is empty (has_more: false, estimated_total: 0) and is not billed. warnings appears on the first page only, and only when there is something to report. Requests without the new fields are unchanged.Feed Builder conditions whose request field has another name or shape:The public Feed Builder page lets you pick these filters from lists and writes the JSON body and
curl for you.POST /api/jobs/feed/managed does not take the occupation, company, exclude_ or companies filters. To filter your managed companies’ jobs this way, save a Feed Builder feed that starts from your Managed feed and read it with POST /api/jobs/feed/{feed_id}.On the first page of POST /api/jobs/feed, estimated_total is now exact when 1,000 or fewer jobs match. Above that it is an estimate that can run a few percent high, so first pages return sooner.Every job now carries the occupation key, set to null when the job is not classified; before, the key was omitted. This applies to search, GET /api/jobs/{id}, every jobs feed and document outbound feeds (webhook, Algolia, Meilisearch, Elasticsearch, MongoDB). Code that checks whether the key exists should check for null instead.Outbound feeds from saved feeds
An outbound feed now takes its jobs from one source, chosen under Jobs to deliver: All jobs or one of your saved feeds from the Feed Builder. Every filter now lives in the Feed Builder: location, ATS, work model, employment type, experience level, posted after, company conditions, occupation and excluded companies. Outbound setup and settings no longer offer their own filters or the managed-companies option; save a feed whose source is your Managed feed instead. The saved feed’s rules alone decide what is delivered.Editing a saved feed’s source, conditions or excluded companies re-sends it in full to every outbound feed that delivers it, on that feed’s next sync. Jobs that no longer match are removed. Like any full re-send, the delivered jobs are billed per job, except on the Unlimited plan. Renaming a feed or changing its description re-sends nothing.A feed that an outbound feed delivers can’t be deleted. The Feed Builder names the outbound feeds that use it, so give them another source first. If the feed becomes unavailable anyway, the outbound feed pauses as Source unavailable and keeps everything it already delivered. Choose another source, then resume it.Active outbound feeds set up with their own filters are moved to saved feeds in your Feed Builder with the same rules, and keep syncing without re-sending anything. Other outbound feeds keep their original filters until you choose a saved feed. Those filters still narrow what is delivered, and the feed’s settings show them read-only under Data included. An outbound feed that already delivers your managed companies keeps doing so until you choose another source. Billing is unchanged.Occupation support in outbound feeds:
- Postgres, Supabase and Bolt: Indexes gains Occupation, a GIN index on the
occupationcolumn for filters by job category, subcategory or occupation. See Occupation. - Algolia: new outbound feeds facet on
occupation.categoryandoccupation.subcategory, and can filter onoccupation.id(asfilterOnly(occupation.id)). - Meilisearch: new outbound feeds can filter on
occupation.category,occupation.subcategoryandoccupation.id. - Existing Algolia and Meilisearch feeds keep their settings. To use these, add them to Attributes for Faceting (Algolia) or Filterable Attributes (Meilisearch).
POST /api/jobs/feed/{feed_id}, feed_revision now changes only when the feed’s source, conditions or excluded companies change. Renaming a feed or changing its description no longer restarts a scan with 409 feed_changed or calls for a full scan. Responses are otherwise unchanged.Job occupations
Jobs now carry an
occupation: one occupation in a category › subcategory › occupation taxonomy (for example Healthcare › Nursing › Registered Nurse). It has a stable id, its ISCO-08 code and its O*NET-SOC codes. It appears on search results, GET /api/jobs/{id} and the jobs feeds. When a job is not classified, the key is omitted (not null); since 2026-11-03 it is present and null.Search can filter on it:GET /api/jobstakes comma-separatedjob_categories,job_subcategoriesandoccupation_ids.POST /api/jobs/searchtakes them as include/exclude objects.- Three opt-in facets,
job_categories,job_subcategoriesandoccupations, return buckets that also carrynameandparent, so you can nest category › subcategory › occupation. - The free
GET /api/occupationslists every value. Filter occupations by id; names can improve between taxonomy releases.
POST /api/jobs/feed and POST /api/jobs/feed/managed return the object but take no occupation filters (POST /api/jobs/feed gained them on 2026-11-03). A saved feed can filter on Job category, Job subcategory and Occupation in the Feed Builder.Postgres, Supabase and Bolt outbound feeds get a JSONB occupation column, added on the next sync to tables Jobo created. Rows written before then are filled when the job is delivered again or on a full resync. Elasticsearch outbound feeds get an occupation field. Occupations come from ESCO and O*NET under CC BY 4.0; see Open data licenses and attribution. Responses are otherwise unchanged.Saved feeds
Build a feed once in the dashboard’s Feed Builder and pull it with
POST /api/jobs/feed/{feed_id}. A feed starts from All jobs or your Managed feed and is narrowed by company and job conditions, such as ATS, industry, size, revenue, work model, location and occupation. It can also exclude companies. Before you save, the builder shows the estimated number of jobs, about how many are new each month, and sample jobs.Pages, cursors and billing are the same as POST /api/jobs/feed. Responses also carry feed_revision, and editing a feed mid-scan returns 409 feed_changed. Available to every account with a customer API key. POST /api/jobs/feed and POST /api/jobs/feed/managed are unchanged.GET search flags unknown query parameters
GET /api/jobs now reports query parameters it does not recognize, as POST /api/jobs/search already does for unknown body fields. It adds an unknown_request_fields entry to warnings[] and to the X-Jobo-Warnings header. Before, a parameter such as created_after was silently ignored, and the response looked like a filtered result. To filter by first-indexed time, use discovered_after / discovered_before, which compare against each job’s created_at. Responses without unknown parameters are unchanged.Stable search pagination over a discovered window
POST /api/jobs/search and GET /api/jobs accept a new sort field: relevance (the default, unchanged), discovered_asc or discovered_desc. The discovered orders sort by created_at, the time Jobo first indexed a job, which never changes. With discovered_after / discovered_before, a page-by-page pull no longer skips or repeats jobs that are updated while you page. See Stable pages over a fixed window.Managed feed pages like the main feed
Breaking (managed feed only):
POST /api/jobs/feed/managed now returns jobs oldest first by created_at, the same order as POST /api/jobs/feed with stable_scan. Its first page carries estimated_total. Cursors issued before this change return 400; start a new scan without a cursor.updated_after on the managed feed now also returns every job of a company added to your managed job sources on or after that time. Before, an incremental sync never received a newly added company’s existing jobs, because those jobs had not changed.HTML job descriptions on request
Set
description_format to html to get job descriptions as HTML. The default stays markdown, so existing responses are unchanged.- Query parameter on
GET /api/jobs,GET /api/jobs/{id}andGET /api/companies/{id}/jobs. - Body field on
POST /api/jobs/search,POST /api/jobs/feedandPOST /api/jobs/feed/managed. On the feed, the value is read from each request, including cursor continuations.
http(s), mailto and relative URLs are kept. See Description format.Sandbox keys and Auto Apply live access
Create a Sandbox key (
jbe_test_…) in the dashboard under Developers → API Keys to build against fictional jobs and companies in the real response shapes. It uses the same host, https://connect.jobo.world. Sandbox calls are never billed. Auto Apply applications made with a sandbox key reach only Jobo sandbox forms and need no approval. See Sandbox.Live Auto Apply now needs acceptance of the Auto Apply Terms and DPA plus an approved business review. Request it in the dashboard under Auto Apply → Getting started → Request live access. Without them, a live create returns 403 auto_apply_agreement_required or 403 auto_apply_review_required.Multi-term search pagination fixed
Fixed: between 2026-08-17 and 2026-09-02,
POST /api/jobs/search and GET /api/jobs requests with two or more search terms ignored page and page_size. Every page returned the same first 10 results, while total, total_pages, X-Page and X-Page-Size still reflected the request. A client walking total_pages re-fetched, and was billed for, the same 10 jobs on every page. Single-term searches and the feed were unaffected.Delivered jobs on affected multi-page fetches in that window were billed as normal deliveries. Contact support for an adjustment.Auto Apply fields become structural
Breaking (beta surface): Auto Apply moves to
api_version 2026-08-31 and @jobo-ai/autoapply 2.0.- Field responses always contain
field_id,type,label,requiredandrequires_answer. Options, constraints, format, sensitivity and repeating-group metadata appear only when they apply. - The unreliable
semantic_keyandcategoryhints and the ATScurrent_valueare removed from customer responses. - Answer each field from its provider ID, visible label, type, advertised options, constraints and group shape.
- Sensitive fields stay explicitly marked. Never infer them.
Company name filters explain themselves
The
companies include/exclude filter on POST /api/jobs/search now resolves names more forgivingly, and says so when it cannot.Names still match on the whole name, but accents, punctuation and common trailing legal suffixes are normalized first. FedEx Corporation, fedex and FedEx, Inc. now resolve to the same companies; before, only the exact stored spelling did. It is still not a partial match: Glenmark does not find Glenmark Pharmaceuticals, and Fidelity finds the company called Fidelity, not every Fidelity* employer. Widening the match silently would bill you for companies you never asked for.Responses may now carry warnings[] and filters.companies (matched / unmatched / exclude_unmatched), and the warning codes repeat on the X-Jobo-Warnings header. Both fields are omitted when there is nothing to report, so an ordinary successful search is byte-identical to before.An unmatched include name still returns
200 with zero jobs, not 400. Two fixes ship alongside:- The zero-result response now sends the
X-Total-*pagination headers it used to omit. - MCP search now resolves company names. It used to pass raw names into an id filter, so every name-based company query there returned nothing.
Auto Apply goes synchronous
Breaking (beta surface): at
api_version 2026-08-12, the Auto Apply webhook mechanism is removed and the whole flow is synchronous.POST /api/auto-apply/applicationsblocks until the first step’s fields come back incurrent_step.- The new
POST /api/auto-apply/applications/{id}/answersvalidates answers synchronously; a bad value is a free400with per-field errors. It then fills the form, auto-continues or auto-submits, and blocks until the next step or the terminal result. GET /applications/{id}gainswait_secondsfor long-poll re-attach.
callback_url, webhook settings and secrets, application.fields_requested and terminal events, HMAC signing, the webhook activity log, and all callback_* error codes. Added: the failure codes answers_timeout and verification_timeout, answers_expire_at on steps, and current_step on applications. Local development no longer needs a tunnel.The @jobo-ai/autoapply SDK is rewritten as 1.0.0 around the new loop, including an applications.run() helper. See The application loop.Auto Apply beta
Auto Apply opens as an invite-only beta at
api_version 2026-07-21. POST /api/auto-apply/applications now returns 202 for granted accounts. Accounts without access get 403 auto_apply_not_enabled instead of the previous 503 auto_apply_coming_soon. Webhook settings, secret rotation, verification and the sandbox are live in the dashboard.The callback docs now state two missing rules. application.fields_requested is a synchronous request whose response body carries the answers. A non-2xx or non-JSON reply fails the application permanently, with no retry. A new local development guide explains that a callback must be a public HTTPS address, so localhost needs a tunnel.Documentation
The documentation now covers the API only; the Data Platform section moved to jobo.world. Filter enum values are corrected throughout:
experience_level is intern | entry | mid | senior | lead | executive, and employment_type includes freelance. New pages cover idempotency, pagination, response headers and the 7-day expiry lookback window. The source catalog reference was updated to 106 providers.Auto Apply preview
Published the profileless, callback-driven Auto Apply contract as a preview, at
api_version 2026-07-21. Application creation was not yet enabled: POST /api/auto-apply/applications returned 503 auto_apply_coming_soon until launch.
