Using the patch API
Change individual fields of a Hireway with JSON Patch (RFC 6902): add, remove, replace and move items, set dates and avoid overwriting edits.
The patch endpoint lets you make small, targeted changes to a resume's content without sending the whole document. You send a list of JSON Patch (RFC 6902) operations, and the server applies them together and returns the updated resume. This is the same mechanism the MCP server's apply_resume_patch tool uses.
Before you start
- Create an API key and try a first request, as described in Using the API.
- Find the resume's ID with
GET /api/openapi/resumes, then fetch its current content withGET /api/openapi/resumes/{id}. Paths in your operations point into thedataobject of that response. - Keep the JSON resume schema at hand. It lists every field, its type and which fields a new item needs.
Choose PATCH or PUT
| You want to | Use |
|---|---|
| Change one field, such as the headline or a company name | PATCH /resumes/{id} |
| Add, remove or reorder entries in a section | PATCH /resumes/{id} |
| Change the template, colors, fonts or layout | PATCH /resumes/{id} |
| Rename the resume, change its slug or tags, or make it public | PUT /resumes/{id} |
| Replace the whole resume content at once | PUT /resumes/{id} with data |
PATCH only changes the resume's data. The name, slug, tags and public setting sit outside data, so change them with PUT, which updates only the fields you send.
Send a patch
PATCH /api/openapi/resumes/{id}
x-api-key: YOUR_API_KEY
Content-Type: application/jsonThe body holds the operations, and optionally the version of the resume you based them on:
{
"expectedUpdatedAt": "2026-09-30T00:10:08.197Z",
"operations": [{ "op": "replace", "path": "/basics/headline", "value": "Senior Game Developer" }]
}| Field | Required | Description |
|---|---|---|
operations | Yes | At least one JSON Patch operation. They run in order. |
expectedUpdatedAt | No | The resume's updatedAt when you read it. If the resume has changed since, the patch is rejected with 409. See Avoid overwriting other edits. |
Each operation has these properties:
| Property | Required | Description |
|---|---|---|
op | Yes | add, remove, replace, move, copy or test |
path | Yes | A JSON Pointer (RFC 6901) into the resume data, such as /basics/name |
value | For add, replace and test | The value to write or compare |
from | For move and copy | A JSON Pointer to the source |
A successful patch returns 200 with the full updated resume, including its new updatedAt.
Examples
Replace basic fields
curl -X PATCH "https://hireway.dev/api/openapi/resumes/YOUR_RESUME_ID" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{ "op": "replace", "path": "/basics/name", "value": "David Kowalski" },
{ "op": "replace", "path": "/basics/headline", "value": "Senior Game Developer" }
]
}'Add an experience entry
A new entry must be a complete item: include every required field of that item type, a unique id (a UUID) and "hidden": false. Set its dates in dates and leave period empty; the server fills it in.
curl -X PATCH "https://hireway.dev/api/openapi/resumes/YOUR_RESUME_ID" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{
"op": "add",
"path": "/sections/experience/items/0",
"value": {
"id": "019a0000-0000-7000-8000-000000000001",
"hidden": false,
"company": "Northwind Games",
"position": "Senior Gameplay Engineer",
"location": "Remote",
"period": "",
"dates": { "start": "2024-01", "end": null, "present": true },
"website": { "url": "", "label": "" },
"description": "<p>Leading the combat systems team.</p>",
"roles": []
}
}
]
}'A numeric index inserts at that position (0 puts the entry first). The special index - appends to the end, as in /sections/skills/items/-.
Remove an entry
Remove the second skill:
{ "operations": [{ "op": "remove", "path": "/sections/skills/items/1" }] }Move an entry within a section
Move the first experience entry to the third position:
{ "operations": [{ "op": "move", "from": "/sections/experience/items/0", "path": "/sections/experience/items/2" }] }Change the design
Switch the template and the primary color:
{
"operations": [
{ "op": "replace", "path": "/metadata/template", "value": "bronzor" },
{ "op": "replace", "path": "/metadata/design/colors/primary", "value": "rgba(37, 99, 235, 1)" }
]
}Hide a section
{ "operations": [{ "op": "replace", "path": "/sections/interests/hidden", "value": true }] }Dates
Experience (and its roles), education, projects and volunteer entries, and awards, certifications and publications, have a structured dates object:
"dates": { "start": "2022-03", "end": null, "present": true }| Field | Value |
|---|---|
start | A year ("2022") or a year and month ("2022-03"), or null. Single-date entries (awards, certifications, publications) use only start. |
end | A year or year and month, or null while the entry is ongoing or for single dates. |
present | true when the entry is ongoing. It prints as "Present" in the resume's language. |
raw | Optional. Original text that couldn't be read exactly, such as "Summer 2016". It prints as written until the dates are edited. |
Write dates, not the text. Each entry also has a text field, period (ranges) or date (single dates). On every save, the server rewrites that text from dates, in the resume's language and date format (/metadata/page/dateFormat: short, long, numeric or iso). A patch that changes only period or date is overwritten, and the resume keeps its old dates.
To change an entry's dates, replace the whole object:
{
"operations": [
{
"op": "replace",
"path": "/sections/experience/items/0/dates",
"value": { "start": "2021", "end": "2024-06", "present": false }
}
]
}With the long date format, that entry's period becomes "2021 – June 2024". An entry sent without dates (for example from an older export) gets them read from its text.
Avoid overwriting other edits
Someone might edit the resume in the browser between your read and your write. Two tools protect you:
expectedUpdatedAt rejects the whole patch if the resume changed after you read it. Send the updatedAt value from your last read:
{
"expectedUpdatedAt": "2026-09-30T00:10:08.197Z",
"operations": [{ "op": "replace", "path": "/basics/headline", "value": "Lead Game Developer" }]
}If the resume has moved on, you get 409 with the code RESUME_VERSION_CONFLICT and the current updatedAt in data. Read the resume again, rebuild your operations and retry.
test checks a value before the other operations run. If it doesn't match, nothing is applied:
{
"operations": [
{ "op": "test", "path": "/basics/name", "value": "David Kowalski" },
{ "op": "replace", "path": "/basics/name", "value": "Dave Kowalski" }
]
}What happens when a patch is applied
- All or nothing. The operations run in order inside one transaction. If any operation fails, or the result doesn't match the resume schema, none of them are saved.
- Validation. The patched resume must pass the same validation as any other save. For example,
/metadata/templatemust be one of the available templates. Rich-text fields such asdescriptionand/summary/contentare HTML strings, so send HTML (<p>…</p>) rather than plain text or Markdown. - History. Each successful patch saves a version in the resume's history. It appears as AI edit, described as "from the assistant or API", and you can restore an earlier version from the editor. See Undoing changes and version history.
- Cover letters leave the resume. If a patch adds a section of type
cover-letter, the server saves its letter as a separate cover letter linked to the resume and removes the section. Manage letters with the/cover-lettersendpoints instead. - Public resumes update at once. If the resume is public, the change is visible on its public page straight away.
Errors
| Status | Code | Cause |
|---|---|---|
400 | BAD_REQUEST | The body is malformed, for example operations is empty or an operation lacks value or from. |
400 | INVALID_PATCH_OPERATIONS | An operation targets a path that doesn't exist, a test failed, or the result doesn't match the schema. data holds the failing index, the operation and a code such as TEST_OPERATION_FAILED or OPERATION_PATH_UNRESOLVABLE. |
401 | UNAUTHORIZED | The API key is missing, revoked or expired. |
404 | NOT_FOUND | The resume doesn't exist or belongs to someone else. |
409 | RESUME_VERSION_CONFLICT | The resume changed after expectedUpdatedAt. |
403 | RESUME_LOCKED | The resume is locked. Unlock it with POST /resumes/{id}/lock and { "isLocked": false }, or in the app. |
403 with code RESUME_LOCKED. Unlock the resume before retrying.Related guides
- Using the API: keys, authentication and the other endpoints.
- JSON resume schema: every path and value type you can patch.
- Using the MCP server: let an AI client write these patches for you.