Implements docs/api-contract-v2.md. Migration 002 adds follow-up fields. EchoTransport replaced by SMTP->Clipboard selection behind unchanged approval gate. Scheduler (APScheduler) daily 07:00 fetch+score, env-gated, default off. Test image installs workspace packages; build context moved to repo root. Recovered and committed by integration lead after W2 worker hit iteration limit.
4.5 KiB
API contract v2 (v1.0 additions)
Base: /api. JSON everywhere. Errors as {error: {code, message}} with proper HTTP status.
This file documents the new endpoints added on top of docs/api-contract.md (POC).
All existing POC endpoints remain unchanged.
CV Import
POST /cv/import
Extract text from an uploaded file and generate draft CV sections via the LLM gateway (cheap class).
Does NOT write to cv_section -- returns drafts for user review.
Request body:
{
"filename": "my_cv.pdf",
"content_base64": "JVBERi0xLjQK..."
}
Response 200:
{
"drafts": [
{
"kind": "experience",
"title": "Software Engineer",
"org": "TechCorp",
"location": "Malmo",
"start_date": "2022-01",
"end_date": null,
"bullets": ["Built feature X", "Improved performance by 20%"],
"tags": ["python", "fastapi"]
}
]
}
Errors:
- 422
{error: {code: "empty_file", message: "..."}}when the file is empty or contains no extractable text. - 422
{error: {code: "unsupported_format", message: "..."}}when the file type is not recognized.
POST /cv/import/confirm
Create cv_section rows from the drafts returned by /cv/import.
Request body:
{
"drafts": [
{
"kind": "experience",
"title": "Software Engineer",
"org": "TechCorp",
"bullets": ["Built feature X"],
"tags": ["python"]
}
]
}
Response 201:
{
"created": 3,
"sections": [/* CvSectionOut[] */]
}
Postings Fetch (Arbetsformedlingen connector)
POST /postings/fetch
Fetch job postings from the Arbetsformedlingen connector, create job_posting + application(discovered) for new postings, skip duplicates.
Request body:
{
"query": "python developer",
"region": "Skane lan"
}
Response 200:
{
"new": 12,
"dupes": 3
}
Errors:
- 503
{error: {code: "connectors_disabled", message: "Connectors are not enabled. Set CONNECTORS_ENABLED=true to enable."}}whenCONNECTORS_ENABLED=false.
Batch Scoring
POST /scoring/batch
Score multiple applications in one call (cheap class). Each response includes red_flags (scam/shield checks).
Request body:
{
"application_ids": ["uuid1", "uuid2"]
}
Response 200:
{
"results": [
{
"application_id": "uuid1",
"score": 72,
"rationale": {"match": 0.72, "factors": {"skills": 0.8}},
"red_flags": ["unpaid trial period mentioned"]
}
]
}
Today Digest
GET /today
Returns the daily digest: ranked postings, follow-up nudges, and pending approval count.
Response 200:
{
"digest": [
{
"application_id": "uuid",
"title": "Backend Developer",
"company": "TechCorp",
"score": 85
}
],
"nudges": [
{
"application_id": "uuid",
"days_since_sent": 9,
"suggestion": "Consider sending a follow-up email asking about the status of your application."
}
],
"pending_approvals": 2
}
Nudge SQL: state = 'sent' AND days_since(last_activity_at) > follow_up_after_days AND (follow_up_snoozed_until IS NULL OR follow_up_snoozed_until < today).
Interview Prep
POST /applications/{id}/interview-prep
Generate interview prep Q&A (strong LLM class), stored as an artifact of kind other.
Sets interview_prep_artifact_id on the application.
Response 200:
{
"artifact_id": "uuid",
"content": "# Interview Prep\n\n## Q1: ..."
}
Mock mode returns deterministic 10-question Q&A markdown.
Concierge / Demo Seed
POST /concierge/seed-demo
Idempotent: seeds a demo profile ('Demo Demosson') with Swedish characters (a,a,o), 6 realistic Skane postings, varied application states (one scored high, one sent 8 days ago for nudge demo). Calling twice does not duplicate data.
Response 200:
{
"profile": "Demo Demosson",
"postings": 6,
"applications": 6,
"sections": 4
}
SMTP Transport (no new endpoint)
The outbox POST /outbox/send now selects transport at call time:
- If
SMTP_HOSTis set:SmtpTransport(ssl on port 465, starttls otherwise, auth withSMTP_USER/SMTP_PASS, fromSMTP_FROM). - Else:
ClipboardTransport(marks sent + stores payload for UI copy/paste).
The approval gate checks (confirmed, unexpired, hash match) are UNCHANGED.
Scheduler (no new endpoint)
APScheduler AsyncIOScheduler starts during app lifespan when SCHEDULER_ENABLED=true (default false).
Runs a daily job at 07:00 that fetches postings and batch-scores pending applications.