Fill REST API v1

← back to app

Versioned under /api/v1. All endpoints (except health) require an API key.

Authentication

Authorization: Bearer fill_k_...        # preferred
X-Api-Key: fill_k_...                   # alternative

Create keys on the API Keys page. Rate limits: 60 req/min per key; AI endpoints 10–20 req/min. 429 responses carry Retry-After.

Errors are JSON: {"error": "message"} with a meaningful HTTP status (402 = no fills available / balance empty).

Account

GET/api/v1/health
Liveness probe (no auth). Returns service + DB status.
GET/api/v1/me
Account summary: email, plan, balance, free-fill quota for today.
GET/api/v1/balance
Quota/balance only: {plan, balance_cents, fill_price_cents, free_fills_per_day, free_fills_used_today, free_fills_remaining_today}
GET/api/v1/profile
The structured candidate profile (identity, location, links, work auth, experience, education, skills) plus the questionnaire answers.

Resumes

GET/api/v1/resumes
List resumes: id, label, filename, mime, is_default, size.
GET/api/v1/resumes/:id/file
Download the original file (binary). X-Filename header carries the name — used by the extension to attach the file to upload fields.
POST/api/v1/select-resume
Body: {jd_text?, title?, company?}{resume_id, label, reasoning}. AI picks the best resume for the job.

Form filling

POST/api/v1/map-fields

The main call. Send the scanned fields and job context; get a value for every field.

{
  "url": "https://boards.greenhouse.io/acme/jobs/123",
  "title": "Senior Engineer",        // optional
  "company": "Acme",                 // optional
  "jd_text": "...",                  // scraped job description
  "resume_id": "uuid",               // optional — skip AI resume selection
  "fields": [{
    "id": "f1",                      // scanner-assigned, echoed back
    "kind": "text|email|tel|url|number|date|textarea|select|combobox|radio|checkbox|checkbox-group|file",
    "label": "First Name",
    "name": "first_name",            // optional attrs that improve matching
    "autocomplete": "given-name", "placeholder": "", "required": true,
    "section": "Personal info",      // optional containing heading
    "options": [{"value": "us", "text": "United States"}]   // choice kinds
  }]
}

Response:

{
  "resume": {"id", "label", "filename", "mime"},
  "resume_reasoning": "…why this resume…",
  "mappings": [{
    "id": "f1", "value": "Jane", "display": "United States",  // display = option text
    "confidence": 0.95,          // 0..1
    "source": "profile|questionnaire|qa|ai|ai_generated|resume|none",
    "needs_review": false,
    "field_type": "standard|open_ended|cover_letter|resume_file|other_file",
    "generate": "cover_letter"   // present when the field wants a generated letter
  }],
  "duplicate": {"id","url","company","title","status","created_at","match":"url|company_title"} | null,
  "quota": { …same as /balance… }
}

Values come from three layers: deterministic profile matching (fast, high confidence), your Q&A knowledge base (semantic match), then LLM mapping/generation for the rest. Returns 402 before doing AI work if no fill is available.

POST/api/v1/cover-letter
Body: {jd_text, title?, company?, resume_id?}{cover_letter} (plain text, 250–350 words).
POST/api/v1/fills/complete

Call after the user approves + the form is filled. Creates the application record and charges one fill (balance first, else free quota; 402 if neither).

{
  "url": "...", "title": "...", "company": "...",
  "jd_text": "full JD copy stored with the record",
  "form_snapshot": [{"label": "First Name", "value": "Jane"}],
  "resume_id": "uuid"
}
→ { "ok": true, "application_id": "uuid", "charged_cents": 100,
    "balance_cents": 400, "quota": {…} }

Q&A knowledge base

POST/api/v1/qa
The learning loop. Body: {question, answer} or {pairs: [{question, answer}]} (≤50). Upserts by question text and re-embeds.
GET/api/v1/qa
List saved pairs.

Applications

GET/api/v1/applications
List (filter: ?status=applied|interview|rejected|offer).
GET/api/v1/applications/check
Duplicate check: ?url=… or ?company=…&title=…{duplicate|null}. URL matching ignores query strings.
POST/api/v1/applications
Record an application manually (no charge).
GET/api/v1/applications/:id
Full record incl. saved JD text and form snapshot.
PATCH/api/v1/applications/:id
Update status / company / title.
DELETE/api/v1/applications/:id
Delete the record.