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.
199 lines
No EOL
4.5 KiB
Markdown
199 lines
No EOL
4.5 KiB
Markdown
# 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:
|
|
```json
|
|
{
|
|
"filename": "my_cv.pdf",
|
|
"content_base64": "JVBERi0xLjQK..."
|
|
}
|
|
```
|
|
|
|
Response 200:
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"drafts": [
|
|
{
|
|
"kind": "experience",
|
|
"title": "Software Engineer",
|
|
"org": "TechCorp",
|
|
"bullets": ["Built feature X"],
|
|
"tags": ["python"]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Response 201:
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"query": "python developer",
|
|
"region": "Skane lan"
|
|
}
|
|
```
|
|
|
|
Response 200:
|
|
```json
|
|
{
|
|
"new": 12,
|
|
"dupes": 3
|
|
}
|
|
```
|
|
|
|
Errors:
|
|
- 503 `{error: {code: "connectors_disabled", message: "Connectors are not enabled. Set CONNECTORS_ENABLED=true to enable."}}` when `CONNECTORS_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:
|
|
```json
|
|
{
|
|
"application_ids": ["uuid1", "uuid2"]
|
|
}
|
|
```
|
|
|
|
Response 200:
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"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:
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
1. If `SMTP_HOST` is set: `SmtpTransport` (ssl on port 465, starttls otherwise, auth with `SMTP_USER`/`SMTP_PASS`, from `SMTP_FROM`).
|
|
2. 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. |