# Changelog


All notable changes to the Fantastic.jobs API will be documented here.

---

## v0.9 (beta) - Migrating from Rapid and api.fantastic.jobs
This is our first major refactor of the Fantastic.jobs API and is currently shipping under the **v0.9 beta** namespace ahead of the v1.0 stable release. Below we will describe all major changes.

### New Features
- **`active-ats-count`** and **`active-jb-count`** (beta) - Return the total count of ATS / job board listings matching your filters in the selected time window. No job rows are returned. Intended for selecting a subscription tier that matches your job volume - call with the new `time_frame=1m` (jobs indexed during the last 31 days, the default) for a monthly estimate. Also accepts `1h`, `24h`, and `6m`. The endpoints accept the same parameters as `active-ats` / `active-jb` except those that shape the response or paginate (`description_format`, `include_basic_organization_details`, `exclude_recruiter_fields`, `id`, `limit`, `offset`, `cursor`). `description` / `description_advanced` are not available on `time_frame=6m`. Each call consumes 1 API Request and 0 Jobs credits.
- Expanded `include_basic_organization_details=true` on `active-ats` and `modified-ats` with four additional company fields: `org_linkedin_name`, `org_crunchbase_categories`, `org_crunchbase_total_investment`, and `org_logo_permalink` (Crustdata-hosted logo URL, aliased to match `organizations-advanced`). These four fields are populated for **newly indexed jobs only**; backfilling them across the `time_frame=6m` window is in progress.
- Added `organizations-advanced` - returns advanced organization data including LinkedIn profile, Crunchbase funding & investment, Glassdoor ratings & reviews, headcount/follower/revenue timeseries, and news articles. Fixed at 100 results per page. Heavy nested fields are excluded by default and must be opted into via `include_crunchbase_funding_and_investment`, `include_linkedin_headcount_timeseries`, `include_linkedin_followers_timeseries`, `include_linkedin_estimated_revenue_timeseries`, `include_news_articles`, and `include_glassdoor_reviews`.

### Breaking Changes
- Response field `id` is now returned as a numeric `bigint` instead of a string.
- **`active-ats`** - Unified ATS endpoint replacing the separate `active-ats-1h`, `active-ats-24h`, `active-ats-7d`, and `active-ats-6m` endpoints. Use the `time_frame` parameter to select the time window (`1h`, `24h`, `7d`, `6m`).
- **Expired feeds** - Unified expired endpoints replacing the separate `ats-expired-1h`, `ats-expired-6m`, `jb-expired-1h`, and `jb-expired-6m` endpoints. Use `expired-ats` or `expired-jb` with `time_frame` set to `1h` (rolling), `1d` (daily snapshot, refreshed at 01:00 UTC), or `6m`.
- **Expired feeds** - The previous daily snapshot value `time_frame=24h` has been renamed to `time_frame=1d` to indicate the cadence rather than imply a rolling 24-hour window. `1d` is a once-per-day snapshot of the previous UTC day (refreshed at 01:00 UTC). The `1h` rolling window and `6m` backfill are unchanged.
- Default limit unified to 100, max increased to 1000.
- Added `organizations` - returns the full list of organizations currently tracked across ATS sources, including basic LinkedIn company details.
- Removed `date_filter` parameter. Use `date_posted_gte` and `date_posted_lt` instead.
- Removed `remote` parameter and `remote_derived` response field. Use `ai_work_arrangement` instead, which provides a more comprehensive analysis (`Remote Solely`, `Remote OK`, `Hybrid`, `On-site`) than the previous keyword-matching heuristic.
- Removed `organization_description` parameter.
- Removed `organization_specialties` parameter.
- Removed `include_ai` parameter. AI-enriched fields are now included by default.
- Renamed `include_li` to `include_basic_organization_details`
- Renamed query parameters for consistency:
  - `title_filter` -> `title`
  - `description_filter` -> `description`
  - `location_filter` -> `location`
  - `advanced_title_filter` -> `title_advanced`
  - `advanced_description_filter` -> `description_advanced`
  - `advanced_location_filter` -> `location_advanced`
  - `advanced_organization_filter` -> `organization_advanced`
  - `organization_filter` -> `organization`
  - `organization_exclusion_filter` -> `exclude_organization`
  - `domain_filter` -> `domain`
  - `domain_exclusion_filter` -> `exclude_domain`

  - `li_industry_filter` -> `organization_industry`
  - `li_organization_slug_filter` -> `organization_slug`
  - `li_organization_slug_exclusion_filter` -> `exclude_organization_slug`
  - `li_organization_employees_gte` -> `organization_headcount_gte`
  - `li_organization_employees_lte` -> `organization_headcount_lt` (note: bound is now exclusive - see below)
  - `ai_experience_level_filter` -> `ai_experience_level`
  - `ai_work_arrangement_filter` -> `ai_work_arrangement`
  - `ai_employment_type_filter` -> `ai_employment_type`
  - `ai_education_requirements_filter` -> `ai_education`
  - `ai_taxonomies_a_filter` -> `ai_taxonomies_a`
  - `ai_taxonomies_a_exclusion_filter` -> `exclude_ai_taxonomies_a`
  - `ai_taxonomies_a_primary_filter` -> `ai_taxonomies_a_primary`
  - `ai_visa_sponsorship_filter` -> `ai_visa_sponsorship`
  - `ai_has_salary` -> `has_salary`
  - `agency` -> `organization_agency`
  - `description_type` -> `description_format`
- Exclusion filters now use the `exclude_` prefix instead of the `_exclusion` suffix (e.g. `organization_exclusion` -> `exclude_organization`).
- Tri-state filters `organization_agency`, `direct_apply`, and `ai_visa_sponsorship` no longer accept `true`/`false`. They now take an enum of `only` or `exclude`; omit the parameter to include both. (e.g. `?organization_agency=only` returns only agency listings, `?organization_agency=exclude` returns only non-agency listings, omitting it returns both.)
- Renamed `directapply` to `direct_apply` for snake_case consistency. This applies to both the query parameter on `active-jb` and the response field on `ActiveJbJob`. The wire format is otherwise unchanged.
- Renamed response field `org_linkedin_url` to `org_linkedin_website` on `active-ats`, `active-jb`, and `modified-ats`. The value is the company's website (e.g. `https://example.com`), not the LinkedIn profile URL - the new name reflects the actual data and matches the field name on `organizations-advanced`. On `organizations-advanced`, `org_linkedin_url` (the LinkedIn profile URL) and `org_linkedin_website` (the company website) remain as two distinct fields.
- Deprecated response field `organization_logo` on `active-ats` and `modified-ats`. The ATS-supplied URL can break or expire over time. Use `org_logo_permalink` instead - a Crustdata-hosted permanent URL returned when `include_basic_organization_details=true` (or always present on `organizations-advanced`). `organization_logo` continues to be returned without a flag in the meantime. `active-jb` keeps `organization_logo` as the primary logo field.
- Dropped the `_raw` suffix from source-supplied response fields. `locations_raw` -> `locations`, `locations_alt_raw` -> `locations_alt`, `location_requirements_raw` -> `location_requirements`, `salary_raw` -> `salary`. The `_raw` suffix was redundant given the existing convention (`_derived` for normalized values, `ai_` for AI-extracted, `org_` for organization context, bare name for source-of-truth). Applies to `active-ats`, `active-jb`, and `modified-ats`.
- Multi-value free-text filters (`organization`, `exclude_organization`, `domain`, `exclude_domain`, `source`, `exclude_source`, `organization_industry`, `exclude_organization_industry`, `organization_slug`, `exclude_organization_slug`, `seniority`, `id`, `linkedin_id`) are now typed as `array<string>` (`style: form`, `explode: false`) in the OpenAPI spec for proper SDK generation. The wire format is unchanged (`?param=val1,val2`). Whitespace around commas in raw URL inputs (e.g. `?source=greenhouse, lever`) is now trimmed automatically rather than treated as part of the value.
- Renamed response fields to proper snake_case:
  - `date_validthrough` → `date_valid_through`
  - `ai_salary_minvalue` → `ai_salary_min_value`
  - `ai_salary_maxvalue` → `ai_salary_max_value`
  - `ai_salary_unittext` → `ai_salary_unit_text`
  - `ai_education_requirements` → `ai_education`
  - `linkedin_org_foundeddate` → `org_linkedin_founded_date`
- Renamed all `linkedin_org_` response field prefixes to `org_linkedin_` to group organization fields by entity. This applies to all LinkedIn organization fields (e.g., `linkedin_org_slug` → `org_linkedin_slug`, `linkedin_org_employees` → `org_linkedin_headcount`, etc.).
- Renamed `org_linkedin_employees` response field and `organization_employees_gte` / `organization_employees_lte` filter parameters to `org_linkedin_headcount`, `organization_headcount_gte`, and `organization_headcount_lt`.
- All range filters now follow a consistent `_gte` (inclusive lower) + `_lt` (exclusive upper) convention. The previous `organization_headcount_lte` (inclusive upper) has been replaced by `organization_headcount_lt` (exclusive upper); to keep the same result set, increment the value by 1 (e.g. `organization_headcount_lte=100` → `organization_headcount_lt=101`).
- Added `exclude_organization_industry` filter to `active-ats`, `active-jb`, and `modified-ats` for parity with `organization_industry` (matches the existing `organization`/`exclude_organization`, `domain`/`exclude_domain`, `source`/`exclude_source`, `organization_slug`/`exclude_organization_slug` pattern).
- **`active-jb`** - Unified job board endpoint replacing the separate `active-jb-hourly`, `active-jb-24h`, `active-jb-7d`, and `active-jb-6m` endpoints. Use the `time_frame` parameter to select the time window (`1h`, `24h`, `7d`, `6m`).
  - Organization details are now always included (no toggle).
  - AI-enriched fields are now included by default (removed `include_ai`).
  - `source` filter now supports multiple values (comma-separated) instead of single value.
  - Description search (`description`, `description_advanced`) returns a 400 error when used with `time_frame=6m`.
  - Removed `li_startup`, `remote`, `date_filter`, `organization_description_filter`, `organization_specialties_filter`, `advanced_organization_description_filter`.
  - Removed `has_external_apply` query parameter and `external_apply_url` response field. The LinkedIn job `url` remains the canonical apply entry point. Note that `direct_apply` is a separate signal from LinkedIn (whether the job offers in-platform "Easy Apply") and is not an inverse of having an external apply URL - it remains available as both a filter and a response field.
  - Renamed `recruiter` toggle to `exclude_recruiter_fields` (polarity flipped: default is still to include the three recruiter fields; pass `exclude_recruiter_fields=true` to drop them).
  - Renamed `type_filter` to `employment_type`.
  - Renamed `seniority_filter` to `seniority`.
  - Added `exclude_organization`, `exclude_source`, `ai_education`, `ai_employment_type` filters.
  - Same parameter naming conventions as ATS (dropped `_filter` suffixes, `organization_` prefix for LinkedIn params, `_advanced` suffix for FTS).
  - Same limit/offset, snake_case renames, and `org_linkedin_` prefix changes as ATS.
- **`active-jb`** - `employment_type` filter is deprecated and will be removed in a future version. Use `ai_employment_type` instead, which provides normalized, market-independent values now that every job is AI-analysed.

### Minor Changes
- For `active-ats`, `date_modified` and `modified_fields` are only available when `time_frame=6m`.
- `org_linkedin_slug` is now always included so jobs can be matched to advanced company details, even when `include_basic_organization_details` is not set.
- `exclude_ats_duplicate` (LinkedIn ↔ ATS deduplication) now uses two checks instead of three - the cleaned-URL match has been removed. Title + organization name and title + LinkedIn company-profile matches remain. `ats_duplicate=null` semantics for agency and direct-apply listings are unchanged.
