{"openapi":"3.1.0","info":{"title":"LinkedIn Jobs Search API","version":"3.0.0","description":"Agent-first HTTP API. All successful responses use the envelope `{ success: true, data: <payload> }`. All errors use RFC 7807 `application/problem+json` with a stable machine `code`. Auth: `Authorization: Bearer lja_<keyId>_<secret>` for agents; Stack-issued cookie for the SPA. Cookies are ignored whenever an Authorization header is present."},"components":{"schemas":{"BillingConfig":{"type":"object","properties":{"mode":{"type":"string","enum":["test","live"]},"publishableKey":{"type":"string","nullable":true},"plans":{"type":"object","properties":{"pro":{"type":"object","properties":{"monthlyPriceId":{"type":"string","nullable":true},"annualPriceId":{"type":"string","nullable":true}},"required":["monthlyPriceId","annualPriceId"]},"team":{"type":"object","properties":{"monthlyPriceId":{"type":"string","nullable":true},"annualPriceId":{"type":"string","nullable":true}},"required":["monthlyPriceId","annualPriceId"]}},"required":["pro","team"]}},"required":["mode","publishableKey","plans"]},"Problem":{"type":"object","properties":{"type":{"type":"string","format":"uri","example":"https://.../problems/unauthorized"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"code":{"type":"string","enum":["unauthorized","forbidden","not_found","validation_error","quota_exceeded","rate_limited","internal_error","invalid_token","expired_token","export_expired"],"example":"unauthorized"},"detail":{"type":"string"},"instance":{"type":"string"},"trace_id":{"type":"string"}},"required":["type","title","status","code"]},"UsageBlock":{"type":"object","properties":{"used":{"type":"number"},"included":{"type":"number"},"remaining":{"type":"number"}},"required":["used","included","remaining"]},"Subscription":{"type":"object","properties":{"plan":{"type":"string","enum":["free","paid"]},"status":{"type":"string"},"currentPeriodStart":{"anyOf":[{"type":"string"},{"type":"string"},{"nullable":true}]},"currentPeriodEnd":{"anyOf":[{"type":"string"},{"type":"string"},{"nullable":true}]},"cancelAtPeriodEnd":{"type":"boolean"},"usage":{"type":"object","properties":{"job_search.success":{"$ref":"#/components/schemas/UsageBlock"},"export.completed":{"$ref":"#/components/schemas/UsageBlock"}},"required":["job_search.success","export.completed"]},"mode":{"type":"string","enum":["test","live"]}},"required":["plan","status","currentPeriodStart","currentPeriodEnd","cancelAtPeriodEnd","usage","mode"]},"StripeUrl":{"type":"object","properties":{"url":{"type":"string","format":"uri"}},"required":["url"]},"CheckoutBody":{"type":"object","properties":{"plan":{"type":"string","enum":["paid"]}},"required":["plan"]},"ExportData":{"type":"object","properties":{"export_id":{"type":"string","example":"01J0000000000000000000ABCD"},"format":{"type":"string","enum":["csv","json"]},"row_count":{"type":"integer"},"url":{"type":"string","format":"uri","example":"https://linkedin-jobs-search.chanmeng-dev.workers.dev/api/export/download/eyJlIjoi..."},"expires_at":{"type":"string","example":"2026-05-10T15:00:00.000Z"}},"required":["export_id","format","row_count","url","expires_at"]},"ExportBody":{"type":"object","properties":{"format":{"type":"string","enum":["csv","json"],"default":"csv"},"jobs":{"type":"array","items":{"type":"object","additionalProperties":{"nullable":true}}}}},"Health":{"type":"object","properties":{"status":{"type":"string","example":"healthy"},"timestamp":{"type":"string","example":"2026-04-28T00:00:00.000Z"},"environment":{"type":"string","example":"production"},"services":{"type":"object","properties":{"kv":{"type":"string","example":"connected"},"database":{"type":"string","example":"configured"}},"required":["kv","database"]},"responseTime":{"type":"string","example":"3ms"}},"required":["status","timestamp","environment","services","responseTime"]},"Country":{"type":"object","properties":{"code":{"type":"string","example":"us"},"name":{"type":"string","example":"United States"},"flag":{"type":"string","example":"🇺🇸"}},"required":["code","name","flag"]},"SearchJobsData":{"type":"object","properties":{"jobs":{"type":"array","items":{"type":"object","additionalProperties":{"nullable":true}}},"count":{"type":"number"},"hasMore":{"type":"boolean"},"page":{"type":"number"}},"additionalProperties":{"nullable":true}},"SearchJobsBody":{"type":"object","properties":{"keyword":{"type":"string"},"location":{"type":"string"},"country":{"type":"string"},"countries":{"type":"array","items":{"type":"string"},"description":"Two-letter country codes for multi-country search. Each country with ≥1 result counts as one billable event."},"jobType":{"type":"string"},"experienceLevel":{"type":"string"},"salary":{"type":"string"},"remoteFilter":{"type":"string"},"dateSincePosted":{"type":"string"},"sortBy":{"type":"string"},"page":{"anyOf":[{"type":"string"},{"type":"number"}]},"limit":{"anyOf":[{"type":"string"},{"type":"number"}]}}},"ApiKeyCreated":{"type":"object","properties":{"id":{"type":"string","example":"01HX1234567890ABCDEFGHJKMN"},"name":{"type":"string","example":"ci-runner"},"prefix":{"type":"string","example":"lja_01HX1234"},"plaintext":{"type":"string","description":"Full Bearer token (`lja_<id>_<secret>`). Returned ONCE at creation; cannot be retrieved later.","example":"lja_01HX1234567890ABCDEFGHJKMN_aBcDeFgH..."},"scopes":{"type":"array","items":{"type":"string"},"example":["*"]},"created_at":{"anyOf":[{"type":"string"},{"type":"string"}],"example":"2026-04-28T00:00:00.000Z"}},"required":["id","name","prefix","plaintext","scopes","created_at"]},"CreateKeyBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"description":"Human label for the key","example":"ci-runner"}},"required":["name"]},"ApiKeySummary":{"type":"object","properties":{"id":{"type":"string","example":"01HX1234567890ABCDEFGHJKMN"},"name":{"type":"string","example":"ci-runner"},"prefix":{"type":"string","example":"lja_01HX1234"},"scopes":{"type":"array","items":{"type":"string"},"example":["*"]},"created_at":{"anyOf":[{"type":"string"},{"type":"string"}],"example":"2026-04-28T00:00:00.000Z"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"string"},{"nullable":true}],"example":null}},"required":["id","name","prefix","scopes","created_at","last_used_at"]},"SavedJob":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"jobId":{"type":"string"},"position":{"type":"string"},"company":{"type":"string"},"location":{"type":"string","nullable":true},"salary":{"type":"string","nullable":true},"jobUrl":{"type":"string"},"companyLogo":{"type":"string","nullable":true},"jobType":{"type":"string","nullable":true},"agoTime":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"appliedAt":{"anyOf":[{"type":"string"},{"type":"string"},{"nullable":true}]},"createdAt":{"anyOf":[{"type":"string"},{"type":"string"}]},"updatedAt":{"anyOf":[{"type":"string"},{"type":"string"}]}},"required":["id","userId","jobId","position","company","location","salary","jobUrl","companyLogo","jobType","agoTime","notes","status","appliedAt","createdAt","updatedAt"]},"SaveJobBody":{"type":"object","properties":{"jobId":{"type":"string"},"jobUrl":{"type":"string"},"position":{"type":"string"},"company":{"type":"string"},"location":{"type":"string"},"salary":{"type":"string"},"companyLogo":{"type":"string"},"jobType":{"type":"string"},"agoTime":{"type":"string"},"notes":{"type":"string"}}},"UpdateSavedJobBody":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["saved","applied","interviewing","offered","rejected"]},"notes":{"type":"string"}},"required":["id"]},"Preset":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"keyword":{"type":"string","nullable":true},"location":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"jobType":{"type":"string","nullable":true},"experienceLevel":{"type":"string","nullable":true},"salary":{"type":"string","nullable":true},"remoteFilter":{"type":"string","nullable":true},"dateSincePosted":{"type":"string","nullable":true},"sortBy":{"type":"string","nullable":true},"isDefault":{"type":"boolean","nullable":true},"createdAt":{"anyOf":[{"type":"string"},{"type":"string"}]},"updatedAt":{"anyOf":[{"type":"string"},{"type":"string"}]}},"required":["id","userId","name","description","keyword","location","country","jobType","experienceLevel","salary","remoteFilter","dateSincePosted","sortBy","isDefault","createdAt","updatedAt"]},"SavePresetBody":{"type":"object","properties":{"name":{"type":"string","minLength":1},"description":{"type":"string"},"keyword":{"type":"string"},"location":{"type":"string"},"country":{"type":"string"},"jobType":{"type":"string"},"experienceLevel":{"type":"string"},"salary":{"type":"string"},"remoteFilter":{"type":"string"},"dateSincePosted":{"type":"string"},"sortBy":{"type":"string"},"isDefault":{"type":"boolean"}},"required":["name"]},"UpdatePresetBody":{"type":"object","properties":{"name":{"type":"string","minLength":1},"description":{"type":"string"},"keyword":{"type":"string"},"location":{"type":"string"},"country":{"type":"string"},"jobType":{"type":"string"},"experienceLevel":{"type":"string"},"salary":{"type":"string"},"remoteFilter":{"type":"string"},"dateSincePosted":{"type":"string"},"sortBy":{"type":"string"},"isDefault":{"type":"boolean"},"id":{"type":"string"}},"required":["id"]},"SearchHistoryItem":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"keyword":{"type":"string","nullable":true},"location":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"jobType":{"type":"string","nullable":true},"experienceLevel":{"type":"string","nullable":true},"salary":{"type":"string","nullable":true},"remoteFilter":{"type":"string","nullable":true},"dateSincePosted":{"type":"string","nullable":true},"sortBy":{"type":"string","nullable":true},"resultsCount":{"type":"number","nullable":true},"searchParams":{"nullable":true},"createdAt":{"anyOf":[{"type":"string"},{"type":"string"}]}},"required":["id","userId","keyword","location","country","jobType","experienceLevel","salary","remoteFilter","dateSincePosted","sortBy","resultsCount","createdAt"]},"SaveSearchHistoryBody":{"type":"object","properties":{"keyword":{"type":"string"},"location":{"type":"string"},"country":{"type":"string"},"jobType":{"type":"string"},"experienceLevel":{"type":"string"},"salary":{"type":"string"},"remoteFilter":{"type":"string"},"dateSincePosted":{"type":"string"},"sortBy":{"type":"string"},"resultsCount":{"type":"number"}},"additionalProperties":{"nullable":true}},"UsageEventRow":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"eventType":{"type":"string","enum":["job_search.success","export.completed"]},"occurredAt":{"anyOf":[{"type":"string"},{"type":"string"}]},"metadata":{"type":"object","nullable":true,"additionalProperties":{"nullable":true}},"apiKeyId":{"type":"string","nullable":true}},"required":["id","eventType","occurredAt","metadata","apiKeyId"]}},"parameters":{}},"paths":{"/api/billing/config":{"get":{"tags":["billing"],"summary":"Public Stripe config","description":"Returns Stripe mode (test|live) + publishable key + price IDs. Public — the pricing page calls it before login.","responses":{"200":{"description":"Stripe config","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/BillingConfig"}},"required":["success","data"]}}}},"500":{"description":"Stripe not configured","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/billing/subscription":{"get":{"tags":["billing"],"summary":"Current user's subscription","responses":{"200":{"description":"Subscription state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Subscription"}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/billing/checkout":{"post":{"tags":["billing"],"summary":"Create a Stripe Checkout Session","description":"Cookie-auth only — Checkout is a human flow, agents don't subscribe themselves. Returns the Checkout Session URL.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutBody"}}}},"responses":{"200":{"description":"Checkout URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/StripeUrl"}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Forbidden — Checkout is cookie-only","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Stripe error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/billing/portal":{"post":{"tags":["billing"],"summary":"Open the Stripe Customer Portal","responses":{"200":{"description":"Portal URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/StripeUrl"}},"required":["success","data"]}}}},"400":{"description":"No customer on file","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Forbidden — portal is cookie-only","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Stripe error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/export":{"post":{"tags":["export"],"summary":"Export saved jobs (R2-backed)","description":"Writes the caller's saved jobs (or the supplied subset) to R2 as CSV or JSON, returns a 1h short-lived signed URL pointing at `/api/export/download/:token`. Cookie or API-key auth. Each successful export counts as one billable `export.completed` event.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportBody"}}}},"responses":{"200":{"description":"Export ready","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ExportData"}},"required":["success","data"]}}}},"400":{"description":"Validation failed or no data to export","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Quota exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/export/download/:token":{"get":{"tags":["export"],"summary":"Download a previously-generated export","description":"Streams an export object from R2. The token is the auth — no Bearer or cookie required. Tokens expire 1h after issue; objects are lifecycle-deleted after 1 day.","parameters":[{"schema":{"type":"string","description":"HMAC-signed download token from POST /api/export."},"required":true,"name":"token","in":"path"}],"responses":{"200":{"description":"Export bytes (binary stream)","content":{"text/csv":{"schema":{"nullable":true}},"application/json":{"schema":{"nullable":true}}}},"401":{"description":"Token signature invalid, malformed, or expired","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"Export object no longer available","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/health":{"get":{"tags":["health"],"summary":"Health check","description":"Liveness probe. Reports KV + DB binding status. No auth.","responses":{"200":{"description":"Service healthy","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Health"}},"required":["success","data"]}}}},"500":{"description":"Internal server error (problem+json)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/image-proxy":{"get":{"tags":["image-proxy"],"summary":"Proxy LinkedIn-hosted images","description":"Server-side proxy that bypasses LinkedIn's hot-link/CORS protection for company-logo images shown in the SPA. Whitelisted to LinkedIn CDN hosts only. Returns the upstream binary verbatim — does NOT use the standard JSON envelope (binary route).","parameters":[{"schema":{"type":"string","format":"uri","description":"Image URL on a whitelisted LinkedIn-affiliated domain.","example":"https://media.licdn.com/dms/image/..."},"required":true,"name":"url","in":"query"}],"responses":{"200":{"description":"Image binary","content":{"image/*":{"schema":{"nullable":true}}}},"400":{"description":"Missing or malformed url query param","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Source domain is not whitelisted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Upstream image fetch failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/jobs":{"get":{"tags":["jobs"],"summary":"List supported countries","description":"Returns the country code → display-info map used by job search.","responses":{"200":{"description":"Country list","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"countries":{"type":"array","items":{"$ref":"#/components/schemas/Country"}}},"required":["countries"]}},"required":["success","data"]}}}},"500":{"description":"Internal error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["jobs"],"summary":"Search LinkedIn jobs","description":"Search LinkedIn jobs across one or more countries. Authentication required (cookie or API key). Each country with ≥1 result counts as one billable `job_search.success` event against your monthly quota.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchJobsBody"}}}},"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/SearchJobsData"}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Authentication required","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Quota exceeded","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/keys":{"post":{"tags":["api-keys"],"summary":"Mint a new API key","description":"Cookie-auth only. Returns the full plaintext token in the `data.plaintext` field exactly once — the secret cannot be retrieved later.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyBody"}}}},"responses":{"200":{"description":"Key minted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/ApiKeyCreated"}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized (cookie required)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Forbidden (API key used; cookie required for key management)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Internal error","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"get":{"tags":["api-keys"],"summary":"List API keys","description":"Cookie-auth only. Returns only id/name/prefix/scopes/created_at/last_used_at — the secret hash is never selected.","responses":{"200":{"description":"List of non-revoked keys","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"count":{"type":"integer"},"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeySummary"}}},"required":["count","keys"]}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/keys/{id}":{"delete":{"tags":["api-keys"],"summary":"Revoke an API key","description":"Soft-delete: sets `revoked_at = now()` so the key is preserved for audit. The WHERE clause pins `user_id` to the caller, defeating cross-user IDOR.","parameters":[{"schema":{"type":"string","example":"01HX1234567890ABCDEFGHJKMN"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Key revoked (or no-op)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{}}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Forbidden","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/user/saved-jobs":{"get":{"tags":["saved-jobs"],"summary":"List the caller's saved jobs","responses":{"200":{"description":"Saved jobs","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"count":{"type":"number"},"jobs":{"type":"array","items":{"$ref":"#/components/schemas/SavedJob"}}},"required":["count","jobs"]}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["saved-jobs"],"summary":"Save a job","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveJobBody"}}}},"responses":{"200":{"description":"Saved (or already-saved) job","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"},"job":{"$ref":"#/components/schemas/SavedJob"}},"required":["message","job"]}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Plan limit reached","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["saved-jobs"],"summary":"Update a saved job","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSavedJobBody"}}}},"responses":{"200":{"description":"Updated job","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"},"job":{"$ref":"#/components/schemas/SavedJob"}},"required":["message","job"]}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Job not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["saved-jobs"],"summary":"Delete a saved job","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"query"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/user/presets":{"get":{"tags":["presets"],"summary":"List the caller's search presets","responses":{"200":{"description":"Presets","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"count":{"type":"number"},"presets":{"type":"array","items":{"$ref":"#/components/schemas/Preset"}}},"required":["count","presets"]}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["presets"],"summary":"Create a search preset","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavePresetBody"}}}},"responses":{"200":{"description":"Created preset","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"},"preset":{"$ref":"#/components/schemas/Preset"}},"required":["message","preset"]}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"put":{"tags":["presets"],"summary":"Update a search preset","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePresetBody"}}}},"responses":{"200":{"description":"Updated preset","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"},"preset":{"$ref":"#/components/schemas/Preset"}},"required":["message","preset"]}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Preset not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["presets"],"summary":"Delete a search preset","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"query"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/user/search-history":{"get":{"tags":["search-history"],"summary":"List the caller's search history","parameters":[{"schema":{"type":"string","example":"10"},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Search history","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"count":{"type":"number"},"history":{"type":"array","items":{"$ref":"#/components/schemas/SearchHistoryItem"}},"windowedDays":{"type":"number","nullable":true}},"required":["count","history","windowedDays"]}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"tags":["search-history"],"summary":"Append to the caller's search history","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveSearchHistoryBody"}}}},"responses":{"200":{"description":"Recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"},"history":{"$ref":"#/components/schemas/SearchHistoryItem"}},"required":["message","history"]}},"required":["success","data"]}}}},"400":{"description":"Validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"tags":["search-history"],"summary":"Delete an entry or clear all history","parameters":[{"schema":{"type":"string"},"required":false,"name":"id","in":"query"}],"responses":{"200":{"description":"Cleared","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/user/usage":{"get":{"tags":["user"],"summary":"List the caller's recent usage events","description":"Returns the caller's recent billable usage events (paginated). Sorted by occurredAt DESC.","parameters":[{"schema":{"type":"string"},"required":false,"name":"page","in":"query"},{"schema":{"type":"string"},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Usage events","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","enum":[true]},"data":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/UsageEventRow"}},"page":{"type":"number"},"limit":{"type":"number"}},"required":["events","page","limit"]}},"required":["success","data"]}}}},"401":{"description":"Unauthorized","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}}}