Repository navigation
feat: sync the SDK to the 2026-09-28 → 2026-10-15 API releases - #33
Merged
Merged
Conversation
Audited every request and response key on all 20 JSON routes against the live spec (`/openapi`, plus `GET /changelog/` for the narrative); the SDK had last synced on 2026-09-22 and was missing nine changelog entries. - `is_recent_hire` is a top-level argument on `search.people`, `company.tam_by_people`, `search.employee_finder`, `search.waterfall_icp` and both `enrichment.company_distribution_by_*` methods. It is deliberately not a key of `PeopleFilter`/`TamPeopleFilter`: since 2026-09-28 the API rejects unknown fields at any depth with a 422. - `include_active_jobs` / `include_past_jobs` on `search.people` and `search.waterfall_icp`. Unset flags are omitted so the server defaults apply; `False` is sent. - `company.mx.provider` on the shared `CompanyFilter` (`MxFilter`, `MxProviderFilter`) with a generated 66-value `MxProvider` enum, mapped in `gen_enums.py` by its owning property `provider`. `MxProvider.ANY` is a sentinel meaning every provider, documented on `MxProviderFilter`. - `people.education.start_year` / `end_year` via `PeopleEducationFilter`, which extends `KeywordFilter`. - `found` on both distribution responses (`false`, at no cost, when the company is unknown or has no current employees); the country distribution now counts people, not jobs. - Documented the `[brackets]` exact-match syntax on `job.title` and the jobs-side company HQ `city`/`state`. No schema change, so the filters stay `KeywordFilter`; a test pins that brackets pass through verbatim. No change needed for the 2026-09-28 error-body reshape: `_make_status_error` reads only `message`/`error`. Tests 186 -> 210; mypy and pyright strict, both drift guards clean.
The re-audit against the live spec found SDK-only response keys. All of them are deliberate, so say so where the next audit will look instead of leaving them to be rediscovered and "fixed": - Location is one superset model over three spec sites: a person's location has all six keys, Experience.job_location only city/state_code/country_code, and a job's location only city/country_code. The unlisted keys read None. - HQ.postcode and HQ.street appear nowhere in the spec but are kept by maintainer decision. This reverses the earlier plan to drop them in the next major; removal would turn a silent None into an AttributeError. - total_results on the two TAM builders comes from the shared CursorPage. Adds the list of expected SDK-only keys to the audit playbook, and rewords the Location and HQ docstrings, which still called the HQ fields "unverified".
Widening PeopleFilter.education from KeywordFilter to PeopleEducationFilter
(to add start_year / end_year) broke callers that reuse a typed variable:
education: KeywordFilter = {"include": ["Stanford"]}
people: PeopleFilter = {"education": education}
A KeywordFilter variable is not assignable to a TypedDict that adds optional
keys -- TypedDict assignability is structural, and those keys could hold any
type -- so both mypy and pyright rejected it. The field, and the TamPeopleFilter
that inherits it, is now `PeopleEducationFilter | KeywordFilter`. Dict literals
and PeopleEducationFilter variables are unaffected, and typos and wrong types
inside `education` are still rejected.
The new regression case lives in tests/test_filters.py as plain typed
variables, so mypy and pyright fail the suite if it regresses; confirmed it
fails on both before this change.
Found in review of #33.
…cade tiers The API owner moved is_recent_hire, include_active_jobs and include_past_jobs: - search.people: all three are now keys of `people`. - company.tam_by_people: is_recent_hire is a key of `people`. - search.waterfall_icp: all three are keys of each `cascade` tier, so one cascade can try recent hires first and fall back to everyone. - search.employee_finder and both enrichment.company_distribution_by_* keep is_recent_hire as a top-level argument. PeopleFilter (and TamPeopleFilter, which extends it) and CascadeTier gain the keys; search.people, tam_by_people and waterfall_icp lose the top-level keyword arguments. Since 2026-09-28 the API rejects unknown fields at any depth with a 422, so placement is load-bearing: tests pin it from both sides (the keys exist on the filter types, and are not parameters of the methods that nest them), and the auto-pagination test checks the flags in `people` survive onto page 2. The flags have never been in a release, so this is not a breaking change against 4.0.0, and it supersedes the top-level placement described in the body of 200f4cc. The live spec still showed the previous placement when this landed; docs/CONTEXT.md §12 records that the audit will flag it until republished.
The republished spec gives company.tam_by_people's `people` object is_recent_hire but not include_active_jobs / include_past_jobs; only search.people takes those. Because TamPeopleFilter extends PeopleFilter, putting them on PeopleFilter made TamPeopleFilter offer two keys the endpoint rejects with a 422. The two flags move to a new SearchPeopleFilter(PeopleFilter); PeopleFilter stays the persona criteria both routes share (with is_recent_hire), and TamPeopleFilter is unchanged. search.people takes `SearchPeopleFilter | PeopleFilter` rather than the bare child, for the same reason as people.education in 5353a05: a declared PeopleFilter variable is not assignable to a TypedDict that adds optional keys, so narrowing the parameter would break every caller that reuses one. A call-site test passes a PeopleFilter, a TamPeopleFilter and a SearchPeopleFilter variable to search.people; mypy and pyright fail the suite if that regresses. Re-audited all 20 routes against the republished spec and changelog: zero request differences, and the only response differences are the documented deliberate extras.
Re-pulled the live spec and changelog (one new entry, 40 -> 41). The audit found
two response changes where the changelog names one:
- enrichment.company: `company.mx`, the MX records of the company's domain
(documented in the 2026-10-15 changelog entry).
- enrichment.email: `mx` at the top level and on every `all_emails[]` item, all
required and nullable. This one is in the spec only.
Adds MxRecord (`priority`, `host`) and `mx: list[MxRecord] | None` on Company,
EmailEnrichmentResponse and EmailMatch. Company is the shared superset model, so
`mx` reads None on search.companies and the TAM builders, whose schemas don't
list it.
Deliberately NOT a BlitzList: the API gives null ("no MX data yet" / "not
resolved") and [] ("the domain takes no mail") different meanings, and coercing
one into the other would silently turn "unknown" into "has no mail server". This
is the one exception to the rule that every nullable array is a BlitzList; it is
recorded on the alias, in CONTEXT.md section 5, and pinned by a test that keeps
null and [] distinct.
Fixtures use the spec's own example (priority 1, aspmx.l.google.com) and leave
the shared _COMPANY fixture mx-free. Re-audited all 20 routes: zero request
differences, and the only response differences are the documented deliberate
extras (now including Company.mx at the three non-enrichment company sites).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brings the SDK level with the live spec (
/openapi) andGET /changelog/. The last sync was 2026-09-22 and nine changelog entries had landed since. I re-audited every request and response key on all 20 JSON routes against a fresh spec: zero request differences.Added (non-breaking)
is_recent_hire,include_active_jobs,include_past_jobsare keys of thepeopleobject onsearch.people, and keys of each cascade tier onsearch.waterfall_icp(so one cascade can try recent hires first and fall back to everyone). Oncompany.tam_by_peopleonlyis_recent_hireis a key ofpeople: the past-job flags aresearch.people-only, so they live on a newSearchPeopleFilter(PeopleFilter)andTamPeopleFilterdoes not offer them.search.peopletakesSearchPeopleFilter | PeopleFilterso an existingPeopleFiltervariable keeps type-checking.search.employee_finderand bothenrichment.company_distribution_by_*methods takeis_recent_hireas a top-level argument. The API rejects unknown fields at any depth with a422(since 2026-09-28), so placement is load-bearing; tests pin it from both sides. Unset flags are omitted so server defaults apply,Falseis sent, and bothinclude_*_jobsFalseis a422(documented, not pre-validated).company.mx.provideron the sharedCompanyFilter(MxFilter,MxProviderFilter), backed by a generated 66-valueMxProviderenum (gen_enums.py --fetch; every existing enum byte-identical).MxProvider.ANYmeans every provider.people.education.start_year/end_yearviaPeopleEducationFilter, which extendsKeywordFilter.mxonenrichment.company(company.mx) andenrichment.email(top level and eachall_emails[]item), aslist[MxRecord] | None. The changelog documents only the company one; the email one is in the spec only. Deliberately not aBlitzList:null("no MX data yet") and[]("takes no mail") are different answers, so coercing would turn "unknown" into "has no mail server".foundon both distribution responses; the country distribution now counts people, not jobs.[brackets]exact-match documented onjob.titleand the jobs-side company HQcity/state. No schema change, and a test pins that brackets pass through verbatim.Documented, not changed
LocationandHQ.postcode/HQ.streetare SDK-only keys the spec doesn't list. They are deliberate and kept;HQ.postcode/streetwere weighed for removal as afeat!:and kept, so this stays a minor release.docs/CONTEXT.md§7 and §10 now record why and list the expected SDK-only keys, so the next audit doesn't "fix" them.422body reshape:_make_status_errorreads onlymessage/error.Test plan
ruff check/ruff format --checkmypyandpyright(strict)pytest— 217 passed (186 before), sync and asyncgen_enums.py --checkandgen_sync.py --checkgen_enums.py --fetchagainst the live spec produces no extra diffsearch.companies(is_recent_hire=True)and a flag passed as a top-level argument tosearch.peopleFollow-up
blitz-api-jsneeds the same changes (asdk-is_recent_hire-updateworkspace exists for it).

Confidence Score: 5/5
The PR appears safe to merge; no blocking issues remain.Summary
Adds hiring filters, mail-provider filters, education-year filters, distribution
foundfields, and typed MX records. Since the last review, the changes add MX response fields and preserve the difference between unknown mail data and an empty record list.Reviews (5) · Last reviewed commit: "feat: return the MX records on enrichmen..." · Reviewed by Greptile