_private/qwestly-hire-docs/plan/unify-external-api-auth.md

Unify external job search auth with companies

Current gap

Area Today
[src/app/api/external/search/companies/[[...slug]]/route.ts](src/app/api/external/search/companies/[[...slug]]/route.ts) Uses [createExternalApiRouter()](src/lib/external-api-auth.ts) โ†’ **HIRE_APP_API_KEY**
[src/app/api/external/search/jobs/[[...slug]]/route.ts](src/app/api/external/search/jobs/[[...slug]]/route.ts) Done: uses createExternalApiRouter() โ†’ HIRE_APP_API_KEY

[src/proxy.ts](src/proxy.ts) already lists **/api/external** as public (session bypass for API-key-only callers). CORS already allows **X-API-Key**. Route groups (external) were not used; real **/api/external/...** URLs are in placeโ€”no change needed there.

Implementation (hire repo)

  1. Refactor jobs external router โ€” In [src/app/api/external/search/jobs/[[...slug]]/route.ts](src/app/api/external/search/jobs/[[...slug]]/route.ts):
  • Remove direct ApiRouter + isAuthenticatedViaApiKey(..., JOB_SEARCH_API_KEY) construction.
  • Import **createExternalApiRouter** from [src/lib/external-api-auth.ts](src/lib/external-api-auth.ts) and use the same pattern as companies (single line const router = createExternalApiRouter();).
  • Keep all existing router.get handlers and handleError / serialization logic unchanged.
  1. Documentation
  • Update [docs/external-jobs-search-api.md](docs/external-jobs-search-api.md): state authentication uses **HIRE_APP_API_KEY** (same as other /api/external/* routes); replace examples saying JOB_SEARCH_API_KEY / โ€œJob Search API keyโ€ with **HIRE_APP_API_KEY** and note one key for all external integration endpoints on Hire.
  • Optionally add a short cross-link in [docs/features/company-search-external.md](docs/features/company-search-external.md) (or a single โ€œExternal APIsโ€ bullet) that job search under /api/external/search/jobs shares the same key and router helper as company search.
  1. Env template
  • Extend the comment above **HIRE_APP_API_KEY** in [.env.copy](.env.copy) to explicitly include **GET /api/external/search/jobs** (and any sub-routes documented). Do not add JOB_SEARCH_API_KEY as a separate required variable.
  1. Tests
  • If any test or script references JOB_SEARCH_API_KEY, update to HIRE_APP_API_KEY or remove (grep shows none outside jobs route + docs today).

Deploy / consumer migration

  • Vercel / env: Remove **JOB_SEARCH_API_KEY** from Hire project settings (or stop setting it). Ensure **HIRE_APP_API_KEY** is set to the value integrations should use.
  • Any client that was configured with a distinct job-search secret must send **HIRE_APP_API_KEY** instead when calling **/api/external/search/jobs**. Today candidate [hire-app.service.ts] still uses **/api/admin/jobs** with HIRE_APP_API_KEYโ€”no candidate change until product switches job listing to the external jobs API. Document this in the PR description.

Out of scope (unless you expand the ticket)

  • Optional hardening from the separate review (max q length, Zod on Rapid envelope, body size limits, rate limiting); track as follow-up PRs.