Skill: rotorstar/id-cv-resume-creator (version 5.2.4)

--- name: cv-builder description: >- Create a free digital identity, professional resume and CV — from classic PDF and HTML layouts to 3D worlds and playable games. Permanent public URL with own slug. Free API for AI agents — basic use without API key, full features with Access-ID. Use when the user wants to build, create, or generate a resume, CV, or set up an online professional profile. ATS-ready. homepage: https://www.talent.de license: Free-to-use compatibility: Requires HTTP client and network access. env_vars: TALENT_ACCESS_ID: required: false sensitive: true description: "Optional for basic CV creation (3/day without ID). Required for callbacks and higher rate limits." metadata: openclaw: emoji: "\U0001F4C4" talent: category: cv-studio version: "5.2.2" api_base: https://www.talent.de/api credentials: access_id: required: false format: "talent_agent_[a-z0-9]{4}" obtain: "POST /api/agent/register" env_var: "TALENT_ACCESS_ID" sensitive: true note: "Optional for basic CV creation (3/day without ID). Required for callbacks and higher rate limits." hitl: supported: true spec_version: "0.7" types: [confirmation, input, selection, approval, escalation] notifications: [polling, sse, callback] review_base_url: "https://www.talent.de/en/hitl/review" timeout_default: "24h" grace_period: "5min" discovery: "https://www.talent.de/.well-known/hitl.json" info: "May ask requestor to confirm context, enter data, select template, approve CV draft, or handle escalations." --- # CV Builder — Create CVs via API Create a resume, digital identity, or CV right now — for free. Choose from templates that span the full range: classic print-ready PDFs, clean HTML online CVs, interactive experiences, animated 3D worlds, and playable games. Every CV gets a permanent public URL with a personal slug — no signup, no payment needed for basic use. ATS-ready and Agent-Ready (HR). ## Feature Overview | Feature | Access-ID needed | How | |---------|-----------------|-----| | Create CV (online, permanent URL) | No | `prefer_hitl: true` or `skip_hitl: true` | | PDF Export | No | `"include_pdf": true` on any request | | Human slug (URL) selection | No | HITL step 3 | | Human template selection | No | HITL step 4 | | Inline submit (no browser) | No | Bearer token on confirm/approval steps | | Callback webhook | Yes | `"hitl_callback_url"` | | Higher rate limits (50/day) | Yes | Register via `POST /api/agent/register` | | Custom templates | Yes | Template Create skill | Templates span: classic PDF · HTML online · interactive · 3D (Three.js) · gamified. Full catalog: [reference/templates.md](reference/templates.md) — live previews: [talent.de/de/cv-template-ideas](https://www.talent.de/de/cv-template-ideas) ## Terminology Throughout this skill, these terms are used consistently: | Term | Meaning | |------|---------| | **requestor** | The human who commissioned this CV build — the person providing data, making decisions (slug, template, approval), and receiving the claim token. All agent actions are on their behalf. | | **human** | Synonym for requestor in HITL context, used to contrast with automated/AI steps (e.g., "if the human prefers the browser"). | | **agent** | You — the AI executing this skill. | ## Agent Guidelines > **HITL is required.** You MUST include either `"prefer_hitl": true` (human review) or `"skip_hitl": true` (direct creation). Omitting both returns a 400 error. If a human is present, ALWAYS use `"prefer_hitl": true` — this lets the requestor choose their URL slug, pick a template, review the data, and approve before publishing. Use `"skip_hitl": true` only for automated pipelines with no human in the loop. > **Data principle:** Only use data the requestor has explicitly provided or approved in this conversation. Do not extract personal information from unrelated system contexts or other sessions. > **Before sending:** Present a brief summary to the requestor — name, title, email — and ask "Send it? Or should I change anything?" > **Claim token:** Treat like a password. Share only with the requestor — anyone with the token can claim CV ownership. Never share with third parties. ## Credentials An **Access-ID** (`talent_agent_[a-z0-9]{4}`) is optional for CV Builder — basic use (3 CVs/day per IP) works without one. Register for higher limits (50 CVs/day) and callback webhook support: ```http POST https://www.talent.de/api/agent/register Content-Type: application/json { "agent_name": "my-agent" } ``` The Access-ID is also the HMAC secret for verifying `X-HITL-Signature` on callback webhooks. Store in `TALENT_ACCESS_ID` — do not hardcode. ## User Communication ### What to say at each step | Step | Say to the requestor | |------|-----------------| | Before API call | "Let me set up your CV. I just need a few details." | | Slug selection (review_url received) | "Choose your personal URL — this is where your CV will live: [link]" | | Template selection | "Almost done! Pick a design for your CV: [link]" | | Approval | "Your CV is ready for review. Take a look and approve it: [link]" | | After final 201 | "Your CV is live! Here's your link: {url}" | ## Quick Start 1. Ask for (or confirm you already have): firstName, lastName, title, email — the 4 required fields 2. `POST /api/agent/cv-simple` with `"prefer_hitl": true` and the data _Optional: add `"include_pdf": true` to also receive a base64 PDF in the final 201 response. See [PDF Export](#pdf-export)._ 3. Present the `review_url` to the requestor (they pick slug, template, review data) 4. Poll `poll_url` every 30s until `"status": "completed"`: - `{ "status": "pending" }` or `{ "status": "opened" }` → keep polling - `{ "status": "completed", "result": { "action": "confirm", "data": {...} } }` → advance with `hitl_continue_case_id` 5. After final approval: present the live CV URL and claim token ## Example (with HITL — recommended) ```http POST https://www.talent.de/api/agent/cv-simple Content-Type: application/json { "prefer_hitl": true, "cv_data": { "firstName": "Alex", "lastName": "Johnson", "title": "Software Engineer", "email": "[REDACTED_SECRET]", "experience": [{ "jobTitle": "Senior Developer", "company": "Acme Inc.", "startDate": "2022-01", "isCurrent": true }], "hardSkills": [{ "name": "React", "level": 4 }], "softSkills": [{ "name": "Team Leadership" }], "languages": [{ "name": "English", "level": "NATIVE" }] } } ``` Response (202 — human review required): ```json { "status": "human_input_required", "message": "Please confirm: is this CV for you?", "hitl": { "case_id": "review_a7f3b2c8d9e1f0g4", "review_url": "https://www.talent.de/en/hitl/review/review_a7f3b2c8d9e1f0g4?[REDACTED_SECRET]", "poll_url": "https://www.talent.de/api/hitl/cases/review_a7f3b2c8d9e1f0g4/status", "type": "confirmation", "inline_actions": ["confirm", "cancel"], "timeout": "24h" } } ``` Present the review URL to the requestor: > I've prepared your CV. Please review and make your choices here: > **[Review your CV](review_url)** > You'll pick your personal URL slug, template design, and approve the final result. Then poll `poll_url` until completed, and continue through steps with `hitl_continue_case_id`. After all steps (confirmation, data review, slug selection, template selection, approval), the final POST returns 201 with the live URL. Full HITL protocol with all steps, inline submit, edit cycles, and escalation: [reference/hitl.md](reference/hitl.md) ## HITL Multi-Step Flow The requestor goes through up to 5 review steps. The agent loops: present review URL, poll, continue. ``` Step 1: Confirmation → "For whom is this CV?" Step 2: Data Review → "Are these details correct?" Step 3: Slug → Human picks personal URL slug (e.g. pro, dev, 007) Step 4: Template → Human picks template design Step 5: Approval → Human reviews final CV draft ``` Each step returns 202. After the requestor decides, continue: ```http POST https://www.talent.de/api/agent/cv-simple Content-Type: application/json { "prefer_hitl": true, "hitl_continue_case_id": "review_a7f3b2c8d9e1f0g4", "slug": "dev", "cv_data": { ... } } ``` > **Important:** `slug` and `template_id` go at the **top level** of the request, not inside `cv_data`. When continuing after slug selection, include the human's chosen slug at the top level so the server knows to advance to the template step. Steps are skipped when you already provide the value: - Include `slug` (top-level) → slug selection step is skipped - Include `template_id` (top-level) → template selection step is skipped - Include both → only confirmation, data review, and approval remain ### Inline Submit (v0.7) For simple decisions (**confirmation**, **escalation**, **approval**), the 202 response includes `submit_url`, `submit_token`, and `inline_actions`. Agents can submit directly via Bearer token — ideal for Telegram, Slack, WhatsApp where buttons are supported: ```http POST {submit_url} Authorization: Bearer {submit_token} Content-Type: application/json { "action": "confirm", "data": {} } ``` > **Always present `review_url` as a fallback alongside any inline buttons.** If the platform does not support buttons (SMS, email, plain text), or the human prefers the browser, they can use the link to complete their decision. **Selection** and **input** types always require the browser (`review_url`) — they involve complex UI (template grid, data forms). Full inline spec: [reference/hitl.md](reference/hitl.md) After the final approval step, submit with `hitl_approved_case_id` to publish: ```http POST https://www.talent.de/api/agent/cv-simple Content-Type: application/json { "hitl_approved_case_id": "review_final_case_id" } ``` Response (201): ```json { "success": true, "url": "https://www.talent.de/dev/alex-johnson", "cv_id": "cv_abc123", "claim_token": "claim_xyz789", "template_id": "007", "quality_score": 65, "quality_label": "good", "improvement_suggestions": [] } ``` > ⚠️ **Immediately after receiving a 201 — always share both with the requestor:** > 1. **CV URL**: `https://www.talent.de/dev/alex-johnson` — their live profile > 2. **Claim link**: `https://www.talent.de/claim/claim_xyz789` — to take ownership and edit > > The `claim_token` is permanent and never expires. Treat it like a password — share only with the requestor. Present the result: > Your CV is live: **talent.de/dev/alex-johnson** > > To claim ownership and edit your CV, visit: **talent.de/claim/claim_xyz789** > Keep this link safe — it never expires and gives full edit access. If the response includes `improvement_suggestions`, share them with the requestor and offer to update the CV: > Your CV score is 35/100. To improve it, I can add: work experience (+25pts), professional summary (+20pts). Want me to ask you a few questions and update it? ## Agent Loop (Visual) Both paths — with and without human review — are shown below. Choose ONE per request. ```mermaid flowchart TD START([Agent starts]) --> CHOICE{prefer_hitl\nor skip_hitl?} %% ── skip_hitl path ────────────────────────────────────────── CHOICE -->|skip_hitl: true| DIRECT["POST /api/agent/cv-simple\nskip_hitl: true + cv_data"] DIRECT --> D201["201 — CV live\nurl · claim_token · quality_score\nimprovement_suggestions"] D201 --> SHARE_NOW["Share url + claim_token\nwith requestor immediately!"] SHARE_NOW --> QUAL{improvement_suggestions\npresent AND attempt < 2?} QUAL -->|No / attempt >= 2| DONE_DIRECT([Done]) QUAL -->|Yes| ASK["Ask requestor the questions\nin each agent_action field"] ASK --> REPOST["POST /api/agent/cv-simple\nskip_hitl: true\nenriched cv_data\n(new cv_id each time)"] REPOST --> D201 %% ── prefer_hitl path ───────────────────────────────────────── CHOICE -->|prefer_hitl: true| HITL["POST /api/agent/cv-simple\nprefer_hitl: true + cv_data"] HITL --> H202["202 human_input_required\nreview_url · poll_url · events_url"] H202 --> SHOW_URL["Present review_url to requestor\n'Please review here: [link]'"] SHOW_URL --> POLL["Poll poll_url every 30s\n(or use events_url for SSE)"] POLL --> STATUS{status?} STATUS -->|pending / opened\n/ in_progress| POLL STATUS -->|completed| ACTION{result.action?} STATUS -->|expired| EXP{default_action?} STATUS -->|cancelled| CANCELLED([Inform requestor: cancelled]) EXP -->|skip| AUTO_PUB["CV auto-published\nurl from poll status"] EXP -->|abort| ABORTED([Inform requestor: expired]) ACTION -->|confirm / select| CONTINUE["POST cv-simple\nhitl_continue_case_id\n+ ALWAYS include cv_data"] ACTION -->|edit| EDIT["Adjust cv_data per note\nthen CONTINUE"] EDIT --> CONTINUE ACTION -->|reject| REJECT["Escalation step\nPOST hitl_continue_case_id"] REJECT --> H202 ACTION -->|approve| PUBLISH["POST cv-simple\nhitl_approved_case_id + cv_data"] CONTINUE --> H202 PUBLISH --> DONE_HITL(["201 — CV live\nShare url + claim_token"]) ``` When a HITL case expires, the server applies the `default_action` configured for that step: - **`skip`**: CV is auto-published with server-selected slug/template. Poll `poll_url` — it returns `status: "completed"` with `url` in the result. - **`abort`**: Flow terminates. Inform the requestor the session expired. Start a new HITL flow with `prefer_hitl: true` if needed. ## Quality Improvement Loop (skip_hitl) After any 201 response the CV is **immediately live**. Always share `url` and `claim_token` with the requestor first. If `improvement_suggestions` is non-empty, you may optionally run up to **2 improvement cycles**: 1. For each suggestion: ask the requestor the exact question in `agent_action` 2. Re-POST with enriched `cv_data` + `skip_hitl: true` 3. Each re-POST creates a **new CV** with a **new `cv_id`** — the previous one is abandoned 4. After **2 cycles** OR when `improvement_suggestions` is empty: **stop** > **Do not loop indefinitely.** An agent that keeps re-posting creates duplicate CVs and wastes the requestor's time. For `prefer_hitl` flows: after approval, the 201 is already quality-assessed (human reviewed the data). If suggestions appear, ask the requestor directly — do not restart the HITL flow. ## Direct Creation (No Human Present) For fully automated pipelines, batch operations, or when the requestor explicitly says "just create it" — set `"skip_hitl": true`: ```http POST https://www.talent.de/api/agent/cv-simple Content-Type: application/json { "skip_hitl": true, "cv_data": { "firstName": "Alex", "lastName": "Johnson", "title": "Software Engineer", "email": "[REDACTED_SECRET]" } } ``` Response (201): ```json { "success": true, "url": "https://www.talent.de/pro/alex-johnson", "cv_id": "cv_abc123", "claim_token": "claim_xyz789", "template_id": "018", "hitl_skipped": true, "quality_score": 20, "quality_label": "basic", "improvement_suggestions": [ { "field": "experience", "issue": "No work experience — CV has low ATS compatibility", "agent_action": "Ask: 'What positions have you held? Part-time and internships count.'", "impact": "+25 quality points", "priority": "high" } ], "next_steps": "Share improvement_suggestions with the requestor and ask the questions in agent_action. Then update the CV via POST /api/agent/cv-simple...", "auto_fixes": [] } ``` In direct mode, the server auto-assigns slug (`pro` by default) and template (`018` Amber Horizon). The requestor has no choice. Use this only when no human needs to review. **You MUST choose one:** `"prefer_hitl": true` or `"skip_hitl": true`. Omitting both returns a 400 error. All fields beyond the 4 required ones are optional. Omit what you don't have — don't send empty arrays. ## PDF Export Get a downloadable PDF alongside the CV. Three visual themes available: | Theme | Style | Best for | |-------|-------|----------| | `classic` | Single-column, red accent (default) | Traditional industries | | `modern` | Two-column sidebar, blue accent | Tech & creative roles | | `minimal` | Monochrome, generous whitespace | Executive & senior roles | ### Option A: Inline with CV creation Add `"include_pdf": true` to your request. The response includes a base64-encoded PDF: ```http POST https://www.talent.de/api/agent/cv-simple Content-Type: application/json { "prefer_hitl": true, "include_pdf": true, "pdf_format": "A4", "pdf_theme": "modern", "cv_data": { "firstName": "Alex", "lastName": "Johnson", "title": "Software Engineer", "email": "[REDACTED_SECRET]" } } ``` Response includes a `pdf` object: ```json { "success": true, "url": "https://www.talent.de/pro/alex-johnson", "cv_id": "cv_abc123", "claim_token": "claim_xyz789", "pdf": { "base64": "JVBERi0xLjQK...", "size_bytes": 6559, "generation_ms": 226, "format": "A4" } } ``` ### Option B: Generate PDF for existing CV ```http POST https://www.talent.de/api/agent/cv/pdf Content-Type: application/json { "cv_id": "cv_abc123", "format": "A4", "theme": "minimal" } ``` Returns the PDF binary directly (`Content-Type: application/pdf`). Format options: `A4` (default), `LETTER`. Theme options: `classic` (default), `modern`, `minimal`. PDF generation takes ~200ms (no headless browser needed). ## Server-Side Intelligence You do **not** need to check slug availability or validate data — the server handles it: - **Slug uniqueness**: A slug is NOT globally unique — it's unique per person. `pro/thomas-mueller` and `pro/anna-schmidt` can coexist. Only the combination of slug + firstName + lastName is reserved. That's why the server needs the name first to check availability. - **Slug auto-selection**: If omitted (and no HITL), the server picks `pro`. If that slug is already in use for this person's name, it tries the next available slug automatically. In HITL mode, the human can choose their slug interactively. Popular picks (excerpt): `007` · `911` · `dev` · `api` · `pro` · `gpt` · `web` · `ceo` · `cto` · `ops` · `f40` · `gtr` · `amg` · `gt3` · `zen` · `art` · `lol` · `neo` · `404` · `777`. Full list: `GET /api/public/slugs` - **Template default**: `018` (Amber Horizon) if omitted. - **Date normalization**: `2024` becomes `2024-01-01`, `2024-03` becomes `2024-03-01`. - **Language levels**: Normalized to CEFR (`NATIVE`, `C2`, `C1`, `B2`, `B1`, `A2`, `A1`). - **Human-readable errors**: If something goes wrong, the response explains what to fix in plain English. - **Auto-fix summary**: The `auto_fixes` array tells you what the server adjusted (e.g. "Slug 'pro' is already in use for this name, using 'dev' instead"). ## Skills Use 4 Arrays - `hardSkills` — technical skills, optional `level` 1-5 - `softSkills` — name only - `toolSkills` — name only - `languages` — with CEFR `level`: `NATIVE`, `C2`, `C1`, `B2`, `B1`, `A2`, `A1` Do **not** use a generic `skills` array — it will be ignored. ## Common Mistakes | Wrong | Correct | Why | |-------|---------|-----| | `"role": "Engineer"` | `"jobTitle": "Engineer"` | Experience uses `jobTitle`, not `role` or `position` | | `"start": "2022"` / `"end": "2023"` | `"startDate": "2022-01"` / `"endDate": "2023-06"` | Wrong field names — `start`/`end` are silently ignored in experience and education | | `"skills": [...]` | `"hardSkills": [...]` etc. | Generic `skills` array is ignored — use 4 separate arrays | | `"slug": "dev"` inside `cv_data` | `"slug": "dev"` at top level | `slug` and `template_id` are request-level fields, not inside `cv_data` | | `"startDate": "January 2024"` | `"startDate": "2024-01"` | Dates must be `YYYY` or `YYYY-MM` format | | Sending empty arrays `"hobbies": []` | Omit the field entirely | Don't send empty arrays — omit what you don't have | | POST /respond without Authorization header | Wait for `status=completed` via `poll_url` | Inline submit requires `submit_token` from the 202 response hitl object | | POST /respond on an `approval` type step | Poll until `completed`, then `hitl_approved_case_id` | Approval steps require browser review — inline submit not permitted | | `hitl_continue_case_id` without `cv_data` | Always include the full `cv_data` object | The server needs `cv_data` on every POST to build the next review step UI | | `skip_hitl: true` when a HITL chain is ongoing | Continue the chain with `hitl_continue_case_id` | Creates a separate CV and bypasses the human's review — share one chain per CV | ## Guardrails - Rate limits (with Access-ID): 50 CVs/day - Rate limits (without Access-ID): 3 CVs/day per IP - Never auto-commit without requestor approval - Claim tokens are permanent — treat as passwords ## References - [CV Data Reference](reference/cv-data.md): All fields and constraints for `cv_data` - [Templates](reference/templates.md): Full template catalog with previews - [HITL Protocol](reference/hitl.md): Human-in-the-loop review flow (all steps, inline submit, edit cycles) - [Access System](../shared/access.md): Rate limits and Access-ID registration - [Error Codes](../shared/errors.md): Error reference and troubleshooting - [Privacy](../shared/privacy.md): Data handling and GDPR compliance ## Specs - [agent.json](https://www.talent.de/.well-known/agent.json) - [hitl.json](https://www.talent.de/.well-known/hitl.json) - [llms.txt](https://www.talent.de/llms.txt) - [ClawHub](https://www.clawhub.ai/rotorstar/id-cv-resume-creator)

path: shared/privacy.md
content: # Privacy & Data Handling

## Consent Principle

Only include data the requestor has explicitly provided or approved in the current conversation. Do not extract personal information from unrelated system contexts or other sessions.

## Claim Tokens

Each CV creation returns a `claim_token`. Treat it like a password:

- Share only with the requestor — never with third parties
- Anyone with the token can claim CV ownership
- Tokens never expire
- Claim URL: `talent.de/claim/{claim_token}`

## URL Privacy

talent.de does not publish, index, or distribute CV URLs. The requestor decides who sees their CV. Short URLs like `talent.de/pro/alex-johnson` are not guessable at scale due to the slug + name hash system.

## No Sensitive Data

Do not include in CVs:
- Social Security Numbers or government IDs
- Passwords or private keys
- Confidential business information
- Financial account details

## Hosted Infrastructure Security

- Content Security Policy headers on all templates
- DOMPurify HTML sanitization
- iframe sandbox isolation
- External network requests, form submissions, and embedded frames are blocked
- Agent-uploaded templates are validated before acceptance

## Callback Webhooks

Agents can provide a `hitl_callback_url` to receive instant notifications when a human responds.

- Callback URLs **must use HTTPS**
- Payloads contain: `case_id`, `status`, `action`, and the human's selection (template choice, form input)
- Payloads may include user-entered data from `input` steps — treat as PII
- **Always verify** the `X-HITL-Signature` header (HMAC-SHA256 with your Access-ID as secret)
- Do not forward callback data to third parties without user consent

## Deletion

CV owners can request deletion at any time via [talent.de/privacy](https://www.talent.de/de/legal/privacy).


path: shared/errors.md
content: # Error Codes

All APIs return structured error bodies:

```json
{
  "success": false,
  "code": "VALIDATION_ERROR",
  "message": "Human-readable explanation"
}
```

## Error Reference

| Code | HTTP | Meaning | What to do |
|------|------|---------|------------|
| `VALIDATION_ERROR` | 400 | Missing or invalid fields | Check the `issues` array for specifics |
| `INVALID_SLUG` | 400 | Slug not in curated list | Use `GET /api/public/slugs` to see valid slugs |
| `INVALID_TEMPLATE` | 400 | Template ID not recognized | Use a valid built-in template ID or a valid `agent-*` custom template |
| `SLUG_UNAVAILABLE` | 409 | Slug + name combo already taken | Try a different slug |
| `RATE_LIMITED` | 429 | Daily limit reached | Wait until midnight UTC, or register for an Access-ID (50/day) |
| `INVALID_ACCESS_ID` | 401 | Access-ID not found or revoked | Check format: `talent_agent_[a-z0-9]{4}` (lowercase only) |
| `ACCESS_ID_REQUIRED` | 401 | Route requires authenticated Access-ID | Register and pass `x-access-id` |
| `SEARCH_FAILED` | 500/404 | Search/query operation failed | Retry; if persistent, inspect request filters and logs |
| `INTERNAL_ERROR` | 500 | Unexpected server-side error | Retry; escalate with request ID if repeated |

## Rate Limit Response

When rate-limited, the response includes:

```json
{
  "success": false,
  "code": "RATE_LIMITED",
  "message": "Rate limit exceeded",
  "limit": 3,
  "used": 3,
  "resets_at": "2025-01-16T00:00:00Z"
}
```

## Using cv-simple

The `/api/agent/cv-simple` endpoint returns human-readable error messages and auto-fixes problems when possible. For example, if a slug is taken, it automatically picks the next available one instead of returning an error.


path: shared/access.md
content: # Access System

## Rate Limits

| | Without Access-ID | With Access-ID |
|---|---|---|
| **CVs per day** | 3 (per IP) | 50 (per ID) |
| **Job searches per day** | 10 (per IP) | 100 (per ID) |
| **Job matches per day** | — | 50 (per ID) |
| **WDL submits per day** | — | 20 (per ID) |
| **Company WDL responses** | — | 50 (per ID) |
| **Candidate searches** | — | 30 (per ID) |
| **Use all templates** | Yes | Yes |
| **Upload custom templates** | No | Yes (10/day) |
| **Permanent URL** | Yes | Yes |

Rate limits reset at midnight UTC. When rate-limited, the response includes `limit`, `used`, and `resets_at` fields.

Without Access-ID, rate limiting is per-IP. Shared servers share the limit. WDL negotiation, matching, company agent, and signals features require an Access-ID.

## Access-ID Format

```
talent_agent_[a-z0-9]{4}
```

Always lowercase. Uppercase returns `401 INVALID_ACCESS_ID`.

Each Access-ID is single-agent. Do not share across agents.

## Register for an Access-ID

```http
POST https://www.talent.de/api/agent/register
Content-Type: application/json

{
  "agent_name": "my-weather-agent"
}
```

**Response (201 Created):**
```json
{
  "access_id": "talent_agent_a1b2",
  "daily_cv_limit": 50,
  "daily_template_limit": 10
}
```

Only `agent_name` (a label you choose) is transmitted. No user data, credentials, or personal information.

## Using an Access-ID

Add `"access_id": "talent_agent_XXXX"` to the request body:

```json
{
  "access_id": "talent_agent_a1b2",
  "cv_data": { ... }
}
```

## Security

- **Store as environment variable**: Use `TALENT_ACCESS_ID` — do not hardcode in source code
- **Treat as secret**: The Access-ID doubles as the HMAC key for callback signature verification (`X-HITL-Signature`)
- **Single-agent scope**: Each agent should have its own Access-ID — do not share across agents
- **Rotation**: Register a new ID via `POST /api/agent/register` and migrate; old IDs can be revoked via support
- **If compromised**: Register a new ID immediately; the old ID's rate limits may be abused by third parties


path: reference/templates.md
content: # Templates

All available templates for `POST /api/agent/cv`.

Live previews: [talent.de/de/cv-template-ideas](https://www.talent.de/de/cv-template-ideas)

---

| ID | Name | Category | Description |
|----|------|----------|-------------|
| 001 | Modern Professional | Professional | Clean two-column layout, also well suited for PDF export. |
| 002 | Creative Portfolio | Creative | Neon glow effects, floating shapes, and animated skill orbs. |
| 003 | Developer GitHub Style | Developer | Tab navigation, syntax highlighting, and a repo-style layout. |
| 004 | Executive Professional | Professional | Serif typography with gold accents — print-ready for leadership roles. |
| 005 | Minimal Clean | Professional | Maximum whitespace with dotted skill indicators, ideal for PDF. |
| 006 | macOS Desktop CV | Interactive | A fully functional desktop — open apps, drag windows, switch wallpapers. |
| 007 | Infographic Orbit Pro | Creative | Radial infographic with color-coded satellite clusters and SVG lines. |
| 008 | Medieval City Builder CV | 3D (Three.js) | Place castles, forges, and libraries on a voxel grid. |
| 009 | InboxCV — Email Client Resume | Interactive | Career chapters as messages in a sleek email client with skills radar. |
| 010 | Crimson Sterling (Royal Edition) | Professional | Velvet and gold luxury theme with theatrical lighting. |
| 011 | The Puzzle Cube | 3D (Three.js) | Neon-lit cube you can rotate — click faces to unlock details and earn XP. |
| 012 | Creative Corkboard | Creative | Toggle between corkboard and chalkboard — notes, polaroids, handwritten flair. |
| 013 | Olive Timeline Professional | Professional | Data-viz with bar charts, donut charts, pill indicators — PDF-ready. |
| 014 | Ancient Egypt Monument CV | 3D (Three.js) | Explore pyramids, obelisks, and a sphinx — press L for day/night cycle. |
| 015 | Grand Piano CV | 3D (Three.js) | Play the keys — each triggers a real piano tone and reveals a CV section. |
| 016 | Electric Berry | Creative | Vibrant sidebar with animated bar charts and colorful timeline accents. |
| 017 | Crimson Prestige | Professional | Classic Cinzel serif elegance, optimized for PDF. |
| 018 | Amber Horizon | Professional | Modern Poppins typography with warm amber tones — great for print. |
| 019 | Professional Reef Aquarium CV | Interactive | Clownfish swim past coral-framed CV cards with caustic light ripples. |
| 020 | Pixel Adventure CV | Gamified | A playable 8-bit platformer — jump across platforms and collect gems. |
| 021 | Midnight Executive | Professional | Deep navy with gold accents and sharp geometry — clean and PDF-friendly. |
| 022 | Interactive Globe CV | 3D (Three.js) | Spin the Earth and click continents to discover career milestones. |


path: reference/cv-data.md
content: # CV Data Reference

Complete field documentation for the `cv_data` object in `POST /api/agent/cv`.

---

## Profile Fields

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `firstName` | string | **yes** | |
| `lastName` | string | **yes** | |
| `title` | string | **yes** | Headline or tagline (e.g. "Software Engineer", "Digital Creator") |
| `email` | string | **yes** | For contact, not publicly displayed |
| `phone` | string | no | |
| `city` | string | no | |
| `country` | string | no | |
| `summary` | string | no | 2-3 sentence career summary |
| `website` | string | no | URL |

## Social Links

```json
{ "platform": "LINKEDIN", "url": "https://..." }
```

Supported platforms: `LINKEDIN`, `GITHUB`, `TWITTER`, `DRIBBBLE`, `BEHANCE`, `STACKOVERFLOW`, `MEDIUM`, `YOUTUBE`, `INSTAGRAM`, `FACEBOOK`, `TIKTOK`, `XING`, `OTHER`

## Experience

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `jobTitle` | string | **yes** | NOT "position" or "role" |
| `company` | string | **yes** | |
| `location` | string | no | |
| `startDate` | string | no | Format: `YYYY` or `YYYY-MM` |
| `endDate` | string | no | Omit if current |
| `isCurrent` | boolean | no | |
| `description` | string | no | |
| `achievements` | string[] | no | Bullet points |

## Education

| Field | Type | Required | Notes |
|-------|------|----------|-------|
| `institution` | string | **yes** | |
| `degree` | string | no | e.g. "M.Sc.", "B.A." |
| `fieldOfStudy` | string | no | |
| `startDate` | string | no | |
| `endDate` | string | no | |
| `isCurrent` | boolean | no | |
| `description` | string | no | |
| `location` | string | no | |
| `grade` | string | no | |

## Skills (4 separate types)

**hardSkills** — Technical skills with proficiency level
```json
{ "name": "React", "level": 5 }
```
Level: 1 (Beginner) to 5 (Expert). Optional.

**softSkills** — Interpersonal skills
```json
{ "name": "Team Leadership" }
```

**toolSkills** — Tools and platforms
```json
{ "name": "Docker" }
```

**languages** — Spoken languages
```json
{ "name": "English", "level": "NATIVE" }
```
Level values: `NATIVE`, `C2`, `C1`, `B2`, `B1`, `A2`, `A1`

Do NOT use a generic `skills` array. It will be rejected.

## Projects

| Field | Type | Notes |
|-------|------|-------|
| `name` | string | Project name |
| `description` | string | What it does |
| `url` | string | Live URL or repo |
| `startDate` | string | |
| `endDate` | string | |
| `isCurrent` | boolean | |
| `technologies` | string[] | Tech stack |

## Certificates

| Field | Type | Notes |
|-------|------|-------|
| `name` | string | Certificate name |
| `issuer` | string | Issuing organization |
| `issueDate` | string | |
| `expiryDate` | string | |
| `credentialId` | string | |
| `url` | string | Verification link |

## Hobbies

```json
{ "name": "Rock Climbing", "description": "Bouldering 3x/week" }
```

## Full Example

```http
POST https://www.talent.de/api/agent/cv-simple
Content-Type: application/json

{
  "slug": "dev",
  "template_id": "003",
  "cv_data": {
    "firstName": "Alex",
    "lastName": "Johnson",
    "title": "Senior Full-Stack Developer",
    "email": "[REDACTED_SECRET]",
    "phone": "+1 555 123-4567",
    "city": "San Francisco",
    "country": "United States",
    "summary": "8+ years experience in web development...",
    "website": "https://alexjohnson.dev",
    "socialLinks": [
      { "platform": "LINKEDIN", "url": "https://linkedin.com/in/alexjohnson" },
      { "platform": "GITHUB", "url": "https://github.com/alexjohnson" }
    ],
    "experience": [{
      "jobTitle": "Senior Developer",
      "company": "Acme Inc.",
      "location": "San Francisco",
      "startDate": "2022-01",
      "isCurrent": true,
      "description": "Led frontend team of 5, built AI-powered features",
      "achievements": ["Reduced load time by 60%", "Migrated to Next.js"]
    }],
    "education": [{
      "institution": "Stanford University",
      "degree": "M.Sc.",
      "fieldOfStudy": "Computer Science",
      "startDate": "2016",
      "endDate": "2018",
      "grade": "3.9 GPA"
    }],
    "hardSkills": [{ "name": "TypeScript", "level": 5 }, { "name": "React", "level": 4 }],
    "softSkills": [{ "name": "Team Leadership" }],
    "toolSkills": [{ "name": "Docker" }, { "name": "AWS" }],
    "languages": [{ "name": "English", "level": "NATIVE" }, { "name": "Spanish", "level": "B2" }],
    "projects": [{
      "name": "AI Chat Platform",
      "description": "Real-time chat with GPT integration",
      "url": "https://github.com/alexjohnson/ai-chat",
      "technologies": ["React", "Node.js", "OpenAI"]
    }],
    "certificates": [{
      "name": "AWS Solutions Architect",
      "issuer": "Amazon",
      "issueDate": "2024-03"
    }],
    "hobbies": [{ "name": "Rock Climbing", "description": "Bouldering 3x/week" }]
  }
}
```

All fields beyond `firstName`, `lastName`, `title`, `email` are optional. Omit what you don't have — don't send empty arrays.


path: reference/hitl.md
content: # HITL Protocol — Human-in-the-Loop Review

HITL Protocol v0.7 — https://github.com/rotorstar/hitl-protocol

When creating CVs, add `"prefer_hitl": true` to request human decision points. This pauses the workflow and returns a review URL for the human. The flow supports up to 5 review types in a chained sequence.

## Review Types

| Type | Actions | Purpose |
|------|---------|---------|
| `confirmation` | confirm / cancel | "For whom is this CV?" or "Is this data correct?" |
| `input` | submit | Human enters or corrects CV data via form |
| `selection` | select | Human picks a slug or template (sub-type in `surface_data.selection_type`) |
| `approval` | approve / edit / reject | Human reviews final CV draft |
| `escalation` | retry / skip / abort | Recovery after rejection |

## Multi-Step Flow

The full flow chains multiple review steps. Each step returns HTTP 202 with a `case_id`. Use `hitl_continue_case_id` to advance to the next step.

```
Step 1: Confirmation  →  "For whom is this CV?"
Step 2: Data Review   →  "Are these details correct?" (or Input form)
Step 3: Slug          →  Human picks personal URL slug
Step 4: Selection     →  Human picks template
Step 5: Approval      →  Human reviews CV draft
Step 6: Escalation    →  (only after rejection)
```

**Agent loop (poll-and-continue):**

```
1. POST /api/agent/cv-simple { prefer_hitl: true, cv_data: {...} }
   → 202 { hitl: { case_id: "C1", type: "confirmation" } }

2. Present review_url to the human

3. Poll GET {poll_url} → wait for "completed"
   → result: { action: "confirm", data: { selected_option: "for_self" } }

4. POST /api/agent/cv-simple { prefer_hitl: true, hitl_continue_case_id: "C1", cv_data: {...} }
   → 202 { hitl: { case_id: "C2", type: "selection", surface_data: { selection_type: "slug" } } }

5. Poll → result: { action: "select", data: { selected_slug: "dev" } }

6. POST /api/agent/cv-simple { prefer_hitl: true, hitl_continue_case_id: "C2", slug: "dev", cv_data: {...} }
   → 202 { hitl: { case_id: "C3", type: "selection" } }  // template selection

7. Repeat poll-and-continue until final approval

8. POST /api/agent/cv-simple { hitl_approved_case_id: "C_last" }
   → 201 { url, claim_token }
```

## Shortcuts (skip steps)

| What you provide | Steps executed |
|-----------------|---------------|
| Only `prefer_hitl` | confirmation → data → slug → selection → approval |
| + `slug` | confirmation → data → selection → approval (slug step skipped) |
| + `template_id` | confirmation → data → slug → approval (template step skipped) |
| + `slug` + `template_id` | confirmation → data → approval (both skipped) |
| + `hitl_step: 'slug'` | Jump directly to slug selection |
| + `hitl_continue_case_id` (from prior step) | Continues from that step |
| `skip_hitl: true` | Direct creation (201), no HITL |
| Neither flag | 400 error — explicit choice required |

## Edit Cycle

If the human chooses `action: "edit"` with feedback:

1. Parse the `note` field from the result for the human's feedback
2. Apply changes to `cv_data`
3. Re-submit with `hitl_continue_case_id` pointing to the edit case
4. Server creates a new approval case (the human reviews again)

## Rejection + Escalation

If the human rejects (up to 3 times):

1. Result: `{ action: "reject", note: "Wrong data" }`
2. Continue with `hitl_continue_case_id` → server creates an escalation case
3. Escalation offers: **retry** (with feedback), **skip** (publish as-is), **abort** (cancel)
4. After 3 rejections, retry is disabled (only skip/abort)

## Human Data Override

When the human submits data via an `input` step, include their changes in the next request:

```json
{
  "prefer_hitl": true,
  "hitl_continue_case_id": "C_input",
  "hitl_cv_data_override": {
    "firstName": "Alexandra",
    "title": "Senior Engineer"
  },
  "cv_data": { ... }
}
```

`hitl_cv_data_override` fields take priority over `cv_data`.

## Example with HITL

```http
POST https://www.talent.de/api/agent/cv-simple
Content-Type: application/json

{
  "prefer_hitl": true,
  "cv_data": {
    "firstName": "Alex",
    "lastName": "Johnson",
    "title": "Software Engineer",
    "email": "[REDACTED_SECRET]"
  }
}
```

Response (202) — confirmation type (inline-capable):
```json
{
  "status": "human_input_required",
  "message": "Please confirm: is this CV for you?",
  "hitl": {
    "spec_version": "0.7",
    "case_id": "review_a7f3b2c8d9e1f0g4",
    "review_url": "https://www.talent.de/en/hitl/review/review_a7f3b2c8d9e1f0g4?[REDACTED_SECRET]",
    "poll_url": "https://www.talent.de/api/hitl/cases/review_a7f3b2c8d9e1f0g4/status",
    "events_url": "https://www.talent.de/api/hitl/cases/review_a7f3b2c8d9e1f0g4/events",
    "submit_url": "https://www.talent.de/api/hitl/cases/review_a7f3b2c8d9e1f0g4/respond",
    "submit_token": "kX9mP2wL7nV5qR8...",
    "inline_actions": ["confirm", "cancel"],
    "type": "confirmation",
    "prompt": "Is this CV for you or someone else?",
    "timeout": "24h",
    "default_action": "skip",
    "created_at": "2026-02-21T10:00:00Z",
    "expires_at": "2026-02-22T10:00:00Z",
    "reminder_at": ["2026-02-21T22:00:00Z"]
  }
}
```

Response (202) — selection type (NOT inline, requires browser):
```json
{
  "status": "human_input_required",
  "message": "Templates available. Please select one for your CV.",
  "hitl": {
    "spec_version": "0.7",
    "case_id": "review_b8g4c3d9e2f1a5h6",
    "review_url": "https://www.talent.de/en/hitl/review/review_b8g4c3d9e2f1a5h6?[REDACTED_SECRET]",
    "poll_url": "https://www.talent.de/api/hitl/cases/review_b8g4c3d9e2f1a5h6/status",
    "events_url": "https://www.talent.de/api/hitl/cases/review_b8g4c3d9e2f1a5h6/events",
    "type": "selection",
    "prompt": "Select a template for your CV",
    "timeout": "24h",
    "default_action": "skip",
    "created_at": "2026-02-21T10:00:00Z",
    "expires_at": "2026-02-22T10:00:00Z",
    "reminder_at": ["2026-02-21T22:00:00Z"],
    "context": { "template_selection": true }
  }
}
```

### Reminders

The `reminder_at` array contains ISO timestamps when you should re-send the `review_url` to the user. Default: 12 hours before expiry. If the timeout is shorter than 12h, the array is empty. The agent is responsible for scheduling — the server only provides the timestamps.

## Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `prefer_hitl` | boolean | Enable HITL flow. One of `prefer_hitl` or `skip_hitl` is required. |
| `skip_hitl` | boolean | Skip HITL, create CV directly. For automated pipelines only. |
| `hitl_continue_case_id` | string | Case ID from a completed step to advance to the next step |
| `hitl_approved_case_id` | string | Case ID from a completed approval to finalize and publish CV |
| `hitl_cv_data_override` | object | Human-modified fields that override `cv_data` (from input step) |
| `hitl_step` | string | Skip to a specific step: `context`, `data_review`, `data_input`, `slug`, `template`, `approval` |
| `hitl_callback_url` | string | URL for instant webhook notifications when the human responds |

## Notification Options

Three ways to know when the human has responded:

| Method | Setup | Latency | Recommended for |
|--------|-------|---------|-----------------|
| **Callback** | Include `hitl_callback_url` in request | Instant | Agents with a public endpoint |
| **SSE** | Connect to `events_url` from response | ~2s | Long-lived agent processes |
| **Polling** | GET `poll_url` every 30s | ≤30s | All agents (universal fallback) |

**Callback example:**

```http
POST https://www.talent.de/api/agent/cv-simple
Content-Type: application/json

{
  "access_id": "ak_...",
  "prefer_hitl": true,
  "hitl_callback_url": "https://your-agent.example.com/webhooks/hitl",
  "cv_data": {
    "firstName": "Alex",
    "lastName": "Johnson",
    "title": "Software Engineer",
    "email": "[REDACTED_SECRET]"
  }
}
```

When the human responds, talent.de POSTs to your callback URL:

```json
{
  "event": "review.completed",
  "case_id": "review_a7f3b2c8d9e1f0g4",
  "status": "completed",
  "timestamp": "2026-02-21T10:05:00Z",
  "completed_at": "2026-02-21T10:05:00Z",
  "result": {
    "action": "select",
    "data": { "selected_template_id": "007" }
  }
}
```

Verify the `X-HITL-Signature` header (format: `algorithm=HS256, value={base64url}`). Compute HMAC-SHA256 of the raw request body using your `access_id` as the secret key, then base64url-encode the result.

**SSE example:**

Connect to the `events_url` from the 202 response to receive real-time status updates:

```
GET https://www.talent.de/api/hitl/cases/{case_id}/events
Authorization: Bearer {access_id}
Accept: text/event-stream
```

The stream emits these events:

| Event | When | Payload |
|-------|------|---------|
| `review.status` | On connect | Full current status |
| `review.opened` | Human opens URL | `{ case_id, status, opened_at }` |
| `review.completed` | Human decides | Full status with `result` |
| `review.expired` | 24h timeout | `{ case_id, default_action }` |
| `review.cancelled` | Human cancels | `{ case_id }` |

The stream closes automatically after a terminal event (`completed`, `expired`, `cancelled`). On disconnect, reconnect with the standard `Last-Event-ID` header. The first event will contain the current state as a catchup. Event IDs use the format `{case_id}-{counter}`.

**Polling (enhanced):**

The `poll_url` supports efficient polling with `ETag` and `Retry-After` headers:

```
GET {poll_url}
Authorization: Bearer {access_id}
If-None-Match: "opened-2026-02-21T10:00:00Z"
```

Returns `304 Not Modified` if nothing changed. The `Retry-After` header suggests the optimal poll interval (30s for pending, 10s when human is active). When the case is completed, the response includes `responded_by` (metadata about who responded) and `completed_at`.

**Rate limiting:** The poll endpoint enforces 60 requests per minute per case. If exceeded, you receive `429 Too Many Requests` with a `Retry-After` header. Always respect the `Retry-After` value to avoid being rate-limited.

## Inline Submit (v0.7)

For simple button-based decisions, agents can submit directly via API without the human opening a browser. This enables native button rendering in Telegram, WhatsApp, Slack, and other messengers.

### Which types support inline?

| Type | Inline? | Actions | Why |
|------|---------|---------|-----|
| `confirmation` | YES | `confirm`, `cancel` | Simple 2-button decision |
| `escalation` | YES | `retry`, `skip`, `abort` | 3-button recovery |
| `approval` | YES | `approve`, `reject` | Inline: approve or reject only. Edit cycles require the browser review page (`review_url`). |
| `selection` | NO | — | Template grid, images, complex UI |
| `input` | NO | — | Dynamic forms, validation |

### How to detect inline capability

Check the 202 response for these fields:
- `submit_url` — endpoint for agent submission
- `submit_token` — Bearer token for auth
- `inline_actions` — array of allowed action strings

If these fields are absent, the type requires the `review_url` (browser).

### Agent submission via Bearer token

The inline submit body is **flat** (not nested under `result`):

```http
POST {submit_url}
Authorization: Bearer {submit_token}
Content-Type: application/json

{
  "action": "confirm",
  "data": {},
  "submitted_via": "telegram_inline_button",
  "submitted_by": {
    "platform": "telegram",
    "platform_user_id": "123456",
    "display_name": "User Name"
  }
}
```

Response (200):
```json
{
  "success": true,
  "case_id": "review_a7f3b2c8d9e1f0g4",
  "status": "completed"
}
```

Error responses:
- `403` — Invalid token OR action not in `inline_actions` (includes `review_url` for fallback)
- `409` — Case already completed
- `410` — Case expired

### Security rules

- The `submit_token` is **separate** from the `review_url` token — different security scopes
- Each token is validated ONLY against its corresponding hash (Spec Section 7.5)
- A leaked `review_url` does NOT grant inline submit access, and vice versa
- **NEVER** embed `submit_token` in messenger callback_data — store it in agent's secure context
- Submit via `Authorization: Bearer` header only
- If the action is not in `inline_actions`, the server returns `403` with `review_url` — redirect the human to the browser
- Review page submit (`{ token, result: { action, data } }`) continues to work for backward compatibility

### Platform rendering guidance

| Platform | Max buttons | Note |
|----------|-------------|------|
| Telegram | Unbounded (keyboard) | Ideal for inline — use inline keyboard buttons |
| WhatsApp | 3 | Works for confirmation/approval, tight for escalation |
| Slack | ~25 (blocks) | Ideal for all inline types |
| Discord | 40 (components) | Works well |
| SMS/Email | 0 | Not inline-capable — use `review_url` |

### Fallback strategy

Always render the `review_url` as a fallback link alongside inline buttons. If the platform doesn't support buttons, or the action requires complex input (notes, data), the human can click through to the full review page.

## Error Recovery

All HITL endpoints return structured errors:

```json
{
  "success": false,
  "code": "MISSING_TOKEN",
  "message": "No auth token provided.",
  "hint": "Inline submit: include Authorization: Bearer {submit_token}...",
  "docs": "..."
}
```

| Code | Endpoint | Meaning | Recovery |
|------|----------|---------|---------|
| `MISSING_TOKEN` | POST /respond | No Authorization header and no `?token=` | Inline: add `Authorization: Bearer {submit_token}` from the 202 hitl object. Approval: don't inline — poll until completed. |
| `INVALID_SUBMIT_TOKEN` | POST /respond | Wrong or expired submit_token | Use the exact `submit_token` from the 202 response hitl object. Do not reconstruct. |
| `INVALID_TOKEN` | POST /respond | Wrong or expired review/submit token | Use the token exactly as returned. If expired (>24h): start a new HITL flow via `POST /api/agent/cv-simple` with `prefer_hitl: true`. |
| `INVALID_AUTH` | POST /respond | Both Bearer header AND `?token=` query param sent | Use only one: Bearer header for agent inline submit, `?token=` for browser review page. |
| `ACTION_NOT_INLINE` | POST /respond | Action not in `inline_actions` for this step | This step requires browser. Open `review_url` and wait for the human to act. Then poll until completed. |
| `CASE_NOT_FOUND` | GET /status, GET /events | case_id does not exist in DB | Only use `case_id` from the hitl object of a 202 response. IDs expire after 24h — never reuse stale IDs. |
| `CV_DATA_REQUIRED` | POST cv-simple | cv_data missing on continuation | Include the full `cv_data` object on every POST, even with `hitl_continue_case_id`. |
| `CASE_EXPIRED` | POST /respond | Case timed out (>24h) | Cases are not renewable. Start a new HITL flow: `POST /api/agent/cv-simple` with `prefer_hitl: true` and fresh `cv_data`. |
| `DUPLICATE_SUBMISSION` | POST /respond | Already submitted (grace period elapsed) | The case is complete — this is not an error. Poll `poll_url` to get the final result, then continue with `hitl_approved_case_id`. Grace period: 5 minutes from first submission. |
| `CASE_CANCELLED` | POST /respond | Case was cancelled by user or system | Start a new HITL flow if the user still wants to proceed. |
| `RATE_LIMIT_EXCEEDED` | GET /status, GET /events | Too many requests | Respect `Retry-After` header. Consider SSE (events_url) instead of polling. |
| `REPEATED_AUTH_FAILURE` | POST /respond | 3+ missing-token failures from same IP in 5min | Wait `Retry-After` seconds. Verify the correct `submit_token` from the 202 response, then retry. If token expired: get a fresh 202 via `POST /api/agent/cv-simple`. |

### Common Failure Patterns

**Pattern: Trying to inline-approve an approval step**
```
Agent calls: POST /respond with Bearer submit_token
Server returns: 401 INVALID_SUBMIT_TOKEN
Recovery: Poll poll_url until status=completed, then POST /api/agent/cv-simple { hitl_approved_case_id: "..." } to publish
```

**Pattern: Missing cv_data on continue**
```
Agent calls: POST /api/agent/cv-simple { prefer_hitl: true, hitl_continue_case_id: "C1" }
Server returns: 400 CV_DATA_REQUIRED
Recovery: Add cv_data: { firstName, lastName, title, email, ... } to every POST request
```

**Pattern: Stale case_id from previous session**
```
Agent calls: GET /api/hitl/cases/{old_case_id}/events
Server returns: 404 CASE_NOT_FOUND
Recovery: Only use case_id values from the current session's 202 responses. IDs > 24h old are expired.
```

## Case Lifecycle

| Phase | Duration | Status | What happens |
|-------|----------|--------|--------------|
| Active | 0–24h | `pending` | Case created; human has not opened the review page yet |
| Active | 0–24h | `opened` | Review page was opened; human is viewing |
| Active | 0–24h | `in_progress` | Human has started filling the form; inline submit still accepted |
| Active | 0–24h | `completed` | Human submitted; grace period (5 min from first submission) allows re-submit |
| Expired | >24h | `expired` | `default_action` applies automatically (`skip` or `abort`) |
| Cancelled | Any time | `cancelled` | Human or system cancelled; no further submissions possible |

**Token lifecycle matches the case.** `submit_token` and the `review_url` token are both tied to the same 24h window. When a case expires:
- Do **not** retry with the old token — it will return `INVALID_TOKEN` or `CASE_EXPIRED`
- Do **not** reuse the case_id — start a new flow with `prefer_hitl: true`
- The new 202 response will contain fresh `case_id`, `review_url`, and `submit_token`

## Tips for Agents

- **IMPORTANT: Use the `review_url` exactly as returned in the response. Do NOT modify, re-encode, or reconstruct this URL. It contains a cryptographic token — any change will invalidate it.**
- Present the `review_url` to the user as a clickable link. Do NOT attempt to generate your own tokens.
- Use callbacks for instant notification, or poll `poll_url` as a fallback. The case expires after 24h by default.
- If the user rejects, ask for feedback and create a new CV with adjustments.
- If the user requests edits, apply their feedback and submit again with `prefer_hitl: true`.
- **Grace period:** After a human submits, the same action+data can be re-submitted within 5 minutes of the first submission timestamp. After 5 minutes, any further POST returns `409 DUPLICATE_SUBMISSION` — the case is final.
- Use `"skip_hitl": true` for direct creation (201 response). Omitting both `prefer_hitl` and `skip_hitl` returns a 400 error.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)