Store an edited resume as a job's tailored resume
const url = 'http://127.0.0.1:8765/api/tailored-resumes';const options = { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: '{"actor":"agent","job_url":"https://boards.greenhouse.io/acme/jobs/101","markdown":"## Summary\n\n- Platform engineer with nine years building billing systems.\n\n## Experience\n\n### Northwind Health\nStaff Engineer | Jun 2020 - Present\n\n- Rebuilt the scheduling service on Python and Postgres.\n"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request PUT \ --url http://127.0.0.1:8765/api/tailored-resumes \ --header 'Content-Type: application/json' \ --data '{ "actor": "agent", "job_url": "https://boards.greenhouse.io/acme/jobs/101", "markdown": "## Summary\n\n- Platform engineer with nine years building billing systems.\n\n## Experience\n\n### Northwind Health\nStaff Engineer | Jun 2020 - Present\n\n- Rebuilt the scheduling service on Python and Postgres.\n" }'Store an edited resume markdown as one job’s tailored resume (no model call). Attaches the markdown to that one job (and profile) as its tailored resume; other jobs and the profile’s resume are untouched. A line that is unchanged from the stored tailored resume keeps its sources; a line that is a resume line is a copy; any other body line becomes kind custom (your own text, no source cited). A custom line is checked, with your whole resume and every answer as the sources: a name or contact detail answers 422 personal_info_refused; a number, a skill the posting names, or a Skills item that neither the resume nor an answer states answers 422 edited_resume_unsupported (save an answer that states it with POST /api/answers, then send the markdown again). An entry heading (employer, title, dates) must be a resume line, unchanged. A refusal lists every problem by the markdown’s line number, never a line’s text. Lines above the first ## section are not stored. The response is the stored resume with edited (written_by operator or agent, edited_at, source), changed (false when it already was the stored one) and recheck: the job is queued in the pipeline, whose tailor step keeps an edited resume, so the re-assessment, the Scout ATS score and the Scout label run against it (result enqueued, or not_queued with error_code, e.g. assessment_missing; runner false: run gigai scout pipeline run --once). Background tailoring never replaces an edited resume; POST /api/tailored-resumes does. The markdown also goes to the job’s folder of the jobs folder as resume.md (GET /api/jobs-folder). Markdown that carries the revision comment of the job brief (<!-- gigai-resume: updated_at=... sha256=... -->) is refused with 409 resume_file_stale when the stored resume is another version than the one it names (read the brief again, then edit), and a stored resume file that cannot be read is not written over (409 stored_resume_unreadable). Storing an edit drops a proposed resume that was waiting for the job.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Who writes: recorded as written_by. Also the X-GigAI-Actor header. Without either, a write from the Scout UI (a browser page of this server) is the operator’s and any other loopback write is the agent’s.
The posting’s link: the ONE job this resume is for.
Resume markdown in GigAI’s format (what a tailored resume’s markdown and the resumes folder’s file hold), at most 65536 bytes.
The profile it was tailored for. Default: the selected profile.
Free text, at most 300 characters: where the edit came from. Stored in edited.source; never sent to a model.
Example
{ "actor": "agent", "job_url": "https://boards.greenhouse.io/acme/jobs/101", "markdown": "## Summary\n\n- Platform engineer with nine years building billing systems.\n\n## Experience\n\n### Northwind Health\nStaff Engineer | Jun 2020 - Present\n\n- Rebuilt the scheduling service on Python and Postgres.\n"}Responses
Section titled “Responses”Success.
object
Example
{ "changed": true, "edited": { "edited_at": "2026-10-04T10:05:00Z", "source": null, "written_by": "agent" }, "job": { "job_identity": "https://boards.greenhouse.io/acme/jobs/101" }, "markdown": "## ...", "recheck": { "error_code": null, "result": "enqueued", "runner": true }, "schema_version": "scout-tailor-response:1"}Forbidden / forbidden_origin: loopback peer and Host 127.0.0.1:<port> or localhost:<port> only (Origin too on writes).
object
object
Examplegenerated
{ "error": { "allowed_keys": [ "example" ], "code": "example", "message": "example" }}Profile_not_found, target_unavailable
object
object
Example
{ "error": { "code": "profile_not_found", "message": "..." }}Resume_file_stale, stored_resume_unreadable
object
object
Example
{ "error": { "code": "resume_file_stale", "message": "..." }}Unsupported_media_type: writes need Content-Type: application/json.
object
object
Examplegenerated
{ "error": { "allowed_keys": [ "example" ], "code": "example", "message": "example" }}Unknown_key, wrong_type, invalid_value, resume_markdown_invalid, resume_markdown_too_large, personal_info_refused, edited_resume_unsupported
object
object
Example
{ "error": { "code": "unknown_key", "message": "..." }}Job_fetch_failed
object
object
Example
{ "error": { "code": "job_fetch_failed", "message": "..." }}