Skip to content

WW-78 (WING-75) — USAJOBS API surface + auth/rate-limit recon

Spike research. Produced by fleet worker (grok kind, DESKTOP-D249KD4), 2026-07-24. Reviewed and committed by WT-2f62.

Verdict: GO for pure-JSON USAJOBS integration

Three official REST JSON APIs exist (Search, Historic JOA, Announcement Text) plus unauthenticated codelists — no Playwright/scraping needed for the core discovery pipeline. Search requires a free, self-serve API key (BYO-key model); Historic JOA and Announcement Text need no key at all.

Business-vs-technical constraint

The ToS is the harder constraint, not the tech:

Model ToS fit Notes
WorkWingman company key, server-side search for all users Risky / needs counsel + possibly written OPM approval "Requesting company" = WorkWingman; redistributing results may be "other use" / derivative-works territory
BYO-key — user registers, pastes key into app, calls Search as themselves Better alignment User is the "requesting company"; app is a client that never shares keys across users
Historic + Announcement Text only (no Search key) Strong technical/ToS simplicity for closed/corpus work Insufficient alone for a live apply pipeline

Recommendation: BYO-key for live Search + no-key Historic/Text/codelists for bulk/corpus. Company-wide key only after a legal/OPM path (access@usajobs.gov) — do not hard-promise multi-tenant server-side USAJOBS search under one key without that clearance.

Go/no-go: pure JSON ingestion (no Playwright)

GO. Official REST JSON APIs cover live jobs (Search), free bulk data (Historic + Announcement Text), and reference data (codelists, no auth). Playwright is not needed for the core search/match pipeline.

Caveats (not no-gos): 1. Historic/Text operational flakiness (503s observed) — need retries/backoff or Search-primary design. 2. Search requires a key; unauthenticated calls return 401. 3. Status/internal jobs need help-desk / special authorization. 4. ToS is the business constraint more than the technical constraint. 5. Docs inconsistencies (default page size 25 vs 250; multi-value delimiters differ; Historic paging style).

BYO-key onboarding friction estimate

Step User action Est. time
1 Open the API Request form 30s
2 Fill name, email, phone, company/agency, use description (2–255 chars) 3–7 min
3 Accept USAJOBS API Terms of Service 1–2 min (if actually read)
4 Submit request 10s
5 Receive API key by email Unknown — not documented
6 Paste email + key into WorkWingman (User-Agent = email; Authorization-Key = key) 1 min

Form friction: ~5–10 minutes of user work. Wall-clock to first successful Search: form time + email approval lag (unknown, not documented anywhere in the developer portal).

Pitch-safe language: "Users can connect USAJOBS with a free OPM API key (self-serve form)." Pitch-unsafe until measured: "Instant," "same-day," or "always auto-approved."

Suggested architecture mapping (for WW-79+)

Live match / apply pipeline  ->  Search API (BYO key)  ->  PositionURI / ApplyURI
Bulk / training / history    ->  Historic JOA (no key) + Announcement Text (no key)
Enums / filters UI           ->  codelist/* (no key, cache)
Do not scrape usajobs.gov HTML for core features

Open questions

  1. API key turnaround: Instant email vs human review? Denial rate for individuals/startups?
  2. Default ResultsPerPage: 25 (tutorial) vs 250 (overview) — which is live?
  3. Historic reliability: Is the 503 rate systemic or transient? SLA / status page?
  4. Historic "current" jobs: Can rows with status "Accepting applications" be safely treated as live-applyable, or must product always use Search for open roles?
  5. Apply URI completeness: Always present on Search items? Any "apply on agency site only" gaps (disableApplyOnline exists on Historic)?
  6. WhoMayApply Status/All authorization: What form/process unlocks non-Public?
  7. ToS written approval: Will OPM approve a multi-tenant job-coach app using a company key? Response path via access@usajobs.gov?
  8. "Derivative works": Does match scoring / ranked shortlists / stored JOA copies violate ToS, or is in-app display + link-out fine under BYO-key?
  9. Freshness lag: Historic vs Search lag for new posts; how often to poll.
  10. Rate limits beyond 10k/500: Undocumented soft limits? 429 behavior?
  11. Multi-value delimiters: semicolon (Search reference) vs colon (Search tutorial) vs comma (Historic tutorial) — inconsistent across docs.
  12. Announcement Text join cardinality: always 1:1 with Historic control numbers?
  13. Cost: all free found; no paid tier / commercial license discovered.

Sources fetched

URL Role
https://developer.usajobs.gov/api-reference/ API overview: 3 REST APIs, auth summary, paging 250/500
https://developer.usajobs.gov/api-reference/get-api-search Search API params, response schema, live open JOAs
https://developer.usajobs.gov/api-reference/get-api-historicjoa Historic JOA no-auth, fields, continuation tokens
https://developer.usajobs.gov/api-reference/get-api-AnnouncementText Announcement Text no-auth, long-text fields
https://developer.usajobs.gov/guides/authentication Header requirements; codelists no auth; API Request page
https://developer.usajobs.gov/guides/rate-limiting Search 10k/query, 500/page; Public default; Code List no limits
https://developer.usajobs.gov/guides/terms-of-use System ToU + embedded API ToS
https://developer.usajobs.gov/apirequest/ Self-serve API key form fields
https://developer.usajobs.gov/general/quick-start Key emailed; curl example
https://developer.usajobs.gov/Tutorials/Search-Jobs Search headers, defaults, multi-param examples
https://developer.usajobs.gov/tutorials/past-job-announcements Historic no-auth tutorial, filters, multi-value commas
https://data.usajobs.gov/api/codelist/payplans Live no-auth codelist check (200)
https://data.usajobs.gov/api/codelist/occupationalseries Live no-auth codelist check (200)
https://data.usajobs.gov/api/codelist/positionopeningstatuses Opening status enum (no auth)
https://data.usajobs.gov/api/Search Live unauthenticated probe -> 401 (auth confirmed)
https://data.usajobs.gov/api/historicjoa Live probes -> 503 / 204 / 400 observed (ops note)
https://data.usajobs.gov/api/historicjoa/announcementtext Live probes -> 503 / 400 observed (ops note)

Also noted (broken/alternate path): .../get-api-historicjoa-announcementtext returns an error page; the correct path is get-api-AnnouncementText.

Verdict for pitch / WW-79–WW-83

Claim Status
"USAJOBS as a pipeline is real" Supported — official Search JSON API for open jobs + apply URIs
"Cheap / no scraping" Supported — pure REST JSON; free keys; free bulk historic/text
"We can ingest without Playwright" GO
"Historic bulk is free and closed-only" Partially wrong — free yes; closed-only no (current + past rows both present)
"Users get a key in N minutes" Form yes; approval time unknown — do not over-promise
"WorkWingman can proxy all users on one company key" ToS risk — prefer BYO-key until written OPM/legal OK

Spike outcome: GO for pure-JSON USAJOBS integration, with BYO-key Search as the live path and no-key Historic + Announcement Text as bulk/corpus — pending legal review of ToS §2 for any non-BYO deployment. Unblocks WW-79 through WW-83.

Jira WING-75 · Vikunja · BOARD.md