Write many role fields in one request
const url = 'https://api.harperharbor.com/open/v1/role/example/document';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"idempotencyKey":"example","fields":{"roleName":"example","language":"zh-Hant","roleDesc":"example","roleReadme":"example","roleTag":["example"],"userName":"example","roleType":"example","roleSex":"man","roleAvatar":"https://example.com","roleBackground":"https://example.com","roleBackgroundLandscape":"https://example.com","roleShareImage":"example","roleDetailDesc":"example","roleWelcome":"example","talkExample":[{"roleType":"ai","content":"example"}],"roleOutputContract":"example","customInstructions":"example","nickname":"example","cardMeta":{"creator":"example","characterVersion":"example","source":["example"],"creatorNotesMultilingual":{"additionalProperty":"example"},"creationDate":1,"modificationDate":1},"responseDefaults":{"agency":"protect","style":"default","perspective":"card","length":"auto","lengthTarget":"200","pace":"natural","customStyle":"example","agencyNote":"example","styleNote":"example","perspectiveNote":"example","lengthNote":"example","paceNote":"example"}},"fieldPatches":{"roleDesc":{"baseSha256":"example","operations":[{"op":"replaceText","oldText":"example","newText":"example","text":"example","beforeText":"example","afterText":"example","replaceAll":false}]},"roleDetailDesc":{"baseSha256":"example","operations":[{"op":"replaceText","oldText":"example","newText":"example","text":"example","beforeText":"example","afterText":"example","replaceAll":false}]},"roleWelcome":{"baseSha256":"example","operations":[{"op":"replaceText","oldText":"example","newText":"example","text":"example","beforeText":"example","afterText":"example","replaceAll":false}]},"roleOutputContract":{"baseSha256":"example","operations":[{"op":"replaceText","oldText":"example","newText":"example","text":"example","beforeText":"example","afterText":"example","replaceAll":false}]},"customInstructions":{"baseSha256":"example","operations":[{"op":"replaceText","oldText":"example","newText":"example","text":"example","beforeText":"example","afterText":"example","replaceAll":false}]}}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.harperharbor.com/open/v1/role/example/document \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "idempotencyKey": "example", "fields": { "roleName": "example", "language": "zh-Hant", "roleDesc": "example", "roleReadme": "example", "roleTag": [ "example" ], "userName": "example", "roleType": "example", "roleSex": "man", "roleAvatar": "https://example.com", "roleBackground": "https://example.com", "roleBackgroundLandscape": "https://example.com", "roleShareImage": "example", "roleDetailDesc": "example", "roleWelcome": "example", "talkExample": [ { "roleType": "ai", "content": "example" } ], "roleOutputContract": "example", "customInstructions": "example", "nickname": "example", "cardMeta": { "creator": "example", "characterVersion": "example", "source": [ "example" ], "creatorNotesMultilingual": { "additionalProperty": "example" }, "creationDate": 1, "modificationDate": 1 }, "responseDefaults": { "agency": "protect", "style": "default", "perspective": "card", "length": "auto", "lengthTarget": "200", "pace": "natural", "customStyle": "example", "agencyNote": "example", "styleNote": "example", "perspectiveNote": "example", "lengthNote": "example", "paceNote": "example" } }, "fieldPatches": { "roleDesc": { "baseSha256": "example", "operations": [ { "op": "replaceText", "oldText": "example", "newText": "example", "text": "example", "beforeText": "example", "afterText": "example", "replaceAll": false } ] }, "roleDetailDesc": { "baseSha256": "example", "operations": [ { "op": "replaceText", "oldText": "example", "newText": "example", "text": "example", "beforeText": "example", "afterText": "example", "replaceAll": false } ] }, "roleWelcome": { "baseSha256": "example", "operations": [ { "op": "replaceText", "oldText": "example", "newText": "example", "text": "example", "beforeText": "example", "afterText": "example", "replaceAll": false } ] }, "roleOutputContract": { "baseSha256": "example", "operations": [ { "op": "replaceText", "oldText": "example", "newText": "example", "text": "example", "beforeText": "example", "afterText": "example", "replaceAll": false } ] }, "customInstructions": { "baseSha256": "example", "operations": [ { "op": "replaceText", "oldText": "example", "newText": "example", "text": "example", "beforeText": "example", "afterText": "example", "replaceAll": false } ] } } }'Applies a partial document to an owned private role in a single write. Every key under fields is optional; absent keys are untouched, present keys overwrite (an empty string clears the field where allowed). fieldPatches carries anchored text edits (operations) applied on top of the current value of the corresponding field and is the way to edit long text without resending it. At least one key under fields must be present (nothing to update otherwise); if nothing is effectively changed (e.g. only a blank roleName), the service answers invalid_arguments. Rules: roleName blank is ignored; roleAvatar/roleBackground blank is ignored, otherwise must be an http(s) URL (invalid_asset_url); roleSex accepts man|women|other and aliases (male, female, woman, 男, 女, 其他; empty clears; else invalid_role_sex); talkExample[].roleType accepts ai|user (assistant maps to ai) and content must be non-blank (invalid_talk_example); roleTag replaces the whole tag list (each non-blank string becomes a platform tag {icon:"", text, type:2}); customInstructions is writable here but is never returned by v1 read routes. Character limits depend on the card language (see GET /role/validate → tokenBudget.limits): roleDesc 500 (English 2500), roleReadme 20000 (English 40000), roleDetailDesc 10000 (English 50000), roleWelcome 8000 (English 10000), customInstructions 500 for zh-Hant/zh-Hans, 1500 otherwise, roleOutputContract 2000. Violations return <field>_too_long codes (role_desc_too_long, role_readme_too_long, role_detail_too_long, role_welcome_too_long, custom_instructions_too_long, role_output_contract_too_long), which the error mapper surfaces as HTTP 500 with retryable: true. The role must be private (public_role_requires_clone). Alternates/prologue greetings are not part of this document — use PATCH /role/{roleId}/welcome.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Role id. Only the path parameter is honoured; a roleId inside the body is ignored.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Accepted; not used for deduplication.
Partial document. Absent keys are untouched.
object
Name; blank ignored.
Card language. Only zh-Hant and zh-Hans can be swapped, and only on a card whose language is one of them: the original text moves to the new script, which the play page converts from. Other values, or any value on a card in another language, return invalid_arguments.
Intro; limit 500 chars (English 2500).
Detail introduction in Markdown for the card’s public page (the summary stays in roleDesc). Presentation only: never sent to the model during chat. Empty string clears it. Limit 20000 chars (English 40000); over the limit returns role_readme_too_long.
Replacement tag list; blank strings dropped.
Player name used by the card.
Card type; stored as-is (not validated). Understood values: companion, story, game, generator.
Stored value; aliases male, female, woman, 男, 女, 其他 are mapped; empty clears.
Http(s) URL; blank ignored. HarperHarbor: compatibility alias for the portrait background of the agent configuration; no independent avatar is stored. If roleBackground is also supplied, it takes precedence, including an empty string. An avatar-only write updates the portrait.
Http(s) URL; blank ignored.
Optional landscape (16:9) background: http(s) URL; blank ignored. roleBackground (portrait) stays the baseline.
Optional share image (1.91:1, 1200×630) for link previews (og:image): http(s) URL; empty string clears it.
Private definition; limit 10000 chars (English 50000).
Opening message; limit 8000 chars (English 10000).
Replacement example dialogue.
object
Speaker; assistant is accepted and stored as ai.
Line text; trimmed; must be non-blank.
Output format contract; trimmed; limit 2000 chars.
Custom instructions that replace the corresponding default content-scope segment in prompt construction, not the entire system prompt. Existing authorization, validation and moderation rules still apply. An empty value clears the override.
What {{char}} expands to in prompts when it should differ from roleName. Empty string means roleName is used. Absent leaves it untouched; empty string clears it (nickname_too_long above 70 characters).
Replaces the whole object; send {} to clear. Absent leaves it untouched.
object
Original author credit.
Card version string as written by the author.
Source URLs or identifiers.
Language code → creator notes in that language.
object
Unix seconds.
Unix seconds.
Author defaults for response preferences. Same keys and values as the player’s overrides, every value a string (lengthTarget as a decimal string). They apply only where the player has not chosen otherwise: when the player picks a different option for an axis, the author’s note, custom style and length target for that axis no longer apply; picking the author’s own option keeps them. Legacy values (plain, dialogue, descriptive, brief, balanced, detailed) are rejected. lengthTarget is required exactly when length is target, customStyle exactly when style is custom.
object
Anchored edits applied after fields, each on the field of the same name.
object
Anchored edit applied to the current value of a text field.
object
Optional hex SHA-256 of the text the patch was computed against; the patch is rejected (invalid_field_patch) when it does not match the stored text.
Operations applied in order.
One anchored text operation. Anchors (oldText, beforeText, afterText) must be found exactly once unless replaceAll is set; otherwise invalid_field_patch (anchor_not_found / anchor_not_unique / invalid_operation).
object
Operation kind. replaceText: replace oldText with newText; deleteText: remove oldText; insertText: insert text before beforeText or after afterText (exactly one anchor); appendText/prependText: add text at the end/start. oldText/text must be non-empty. Anchors must occur exactly once unless replaceAll is set (replace/delete only).
Text to find (replace operations).
Replacement text.
Text to insert (insert operations).
Anchor: insert before this text.
Anchor: insert after this text.
Replace every occurrence instead of requiring a unique anchor.
Anchored edit applied to the current value of a text field.
object
Optional hex SHA-256 of the text the patch was computed against; the patch is rejected (invalid_field_patch) when it does not match the stored text.
Operations applied in order.
One anchored text operation. Anchors (oldText, beforeText, afterText) must be found exactly once unless replaceAll is set; otherwise invalid_field_patch (anchor_not_found / anchor_not_unique / invalid_operation).
object
Operation kind. replaceText: replace oldText with newText; deleteText: remove oldText; insertText: insert text before beforeText or after afterText (exactly one anchor); appendText/prependText: add text at the end/start. oldText/text must be non-empty. Anchors must occur exactly once unless replaceAll is set (replace/delete only).
Text to find (replace operations).
Replacement text.
Text to insert (insert operations).
Anchor: insert before this text.
Anchor: insert after this text.
Replace every occurrence instead of requiring a unique anchor.
Anchored edit applied to the current value of a text field.
object
Optional hex SHA-256 of the text the patch was computed against; the patch is rejected (invalid_field_patch) when it does not match the stored text.
Operations applied in order.
One anchored text operation. Anchors (oldText, beforeText, afterText) must be found exactly once unless replaceAll is set; otherwise invalid_field_patch (anchor_not_found / anchor_not_unique / invalid_operation).
object
Operation kind. replaceText: replace oldText with newText; deleteText: remove oldText; insertText: insert text before beforeText or after afterText (exactly one anchor); appendText/prependText: add text at the end/start. oldText/text must be non-empty. Anchors must occur exactly once unless replaceAll is set (replace/delete only).
Text to find (replace operations).
Replacement text.
Text to insert (insert operations).
Anchor: insert before this text.
Anchor: insert after this text.
Replace every occurrence instead of requiring a unique anchor.
Anchored edit applied to the current value of a text field.
object
Optional hex SHA-256 of the text the patch was computed against; the patch is rejected (invalid_field_patch) when it does not match the stored text.
Operations applied in order.
One anchored text operation. Anchors (oldText, beforeText, afterText) must be found exactly once unless replaceAll is set; otherwise invalid_field_patch (anchor_not_found / anchor_not_unique / invalid_operation).
object
Operation kind. replaceText: replace oldText with newText; deleteText: remove oldText; insertText: insert text before beforeText or after afterText (exactly one anchor); appendText/prependText: add text at the end/start. oldText/text must be non-empty. Anchors must occur exactly once unless replaceAll is set (replace/delete only).
Text to find (replace operations).
Replacement text.
Text to insert (insert operations).
Anchor: insert before this text.
Anchor: insert after this text.
Replace every occurrence instead of requiring a unique anchor.
Custom instructions that replace the corresponding default content-scope segment in prompt construction, not the entire system prompt. Existing authorization, validation and moderation rules still apply. An empty value clears the override.
object
Optional hex SHA-256 of the text the patch was computed against; the patch is rejected (invalid_field_patch) when it does not match the stored text.
Operations applied in order.
One anchored text operation. Anchors (oldText, beforeText, afterText) must be found exactly once unless replaceAll is set; otherwise invalid_field_patch (anchor_not_found / anchor_not_unique / invalid_operation).
object
Operation kind. replaceText: replace oldText with newText; deleteText: remove oldText; insertText: insert text before beforeText or after afterText (exactly one anchor); appendText/prependText: add text at the end/start. oldText/text must be non-empty. Anchors must occur exactly once unless replaceAll is set (replace/delete only).
Text to find (replace operations).
Replacement text.
Text to insert (insert operations).
Anchor: insert before this text.
Anchor: insert after this text.
Replace every occurrence instead of requiring a unique anchor.
Responses
Section titled “Responses”Role summary plus the list of patched fields.
object
Summary returned by every role write.
object
Role id.
Current visibility. HarperHarbor defaults to unlisted (usable by ID without community listing).
Moderation status.
Always owner on these routes.
Present when a deep patch was applied to a single field.
object
Number of operations applied.
Rune count before.
Rune count after.
Hex SHA-256 of the text before.
Hex SHA-256 of the text after.
Per-field patch summaries keyed by field name; present when fieldPatches were used.
object
object
Number of operations applied.
Rune count before.
Rune count after.
Hex SHA-256 of the text before.
Hex SHA-256 of the text after.
Character counts before/after for the edited field (e.g. roleDetailDesc, roleWelcome).
object
object
Character (rune) count before the write.
Character (rune) count after the write.
Suggested next MCP tool names; informational.
object
Document format version. This names the format, not the service that produced it. Some providers still return a vendor-prefixed legacy value for this format; compare the suffix or treat the string as opaque.
Field names that were written.
Summaries of applied fieldPatches.
object
object
Number of operations applied.
Rune count before.
Rune count after.
Hex SHA-256 of the text before.
Hex SHA-256 of the text after.
Always validate_role.
Example
{ "role": { "roleVisibility": "private", "reviewStatus": "not_submitted", "accountPermission": "owner" }, "document": { "documentVersion": "rolePatch.v1" }}invalid JSON body, nothing to update (body {"error": "..."}), or invalid_arguments (nothing effectively patched; {"error","retryable":false}).
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}unauthorized.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}permission_denied.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}record not found, public_role_requires_clone, *_too_long, invalid_asset_url, invalid_role_sex, invalid_talk_example, invalid_field_patch (anchor not found/not unique/invalid op), or another unclassified error; retryable: true.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}