Replace the display rules of an owned role
const url = 'https://api.harperharbor.com/open/v1/role/example/author-asset';const options = { method: 'PUT', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"rules":[{"id":"example","name":"example","find":"example","replace":"example","enabled":true}],"mountTrigger":"example","mountLayer":"under","pageMode":"classic","version":0,"sharedState":{"polls":{"additionalProperty":{"options":["example"],"share":true}},"counters":{"additionalProperty":{"goal":1,"maxStep":1,"perMember":1,"period":"none"}}}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request PUT \ --url https://api.harperharbor.com/open/v1/role/example/author-asset \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "rules": [ { "id": "example", "name": "example", "find": "example", "replace": "example", "enabled": true } ], "mountTrigger": "example", "mountLayer": "under", "pageMode": "classic", "version": 0, "sharedState": { "polls": { "additionalProperty": { "options": [ "example" ], "share": true } }, "counters": { "additionalProperty": { "goal": 1, "maxStep": 1, "perMember": 1, "period": "none" } } } }'Full replacement of the author asset (there is no partial update). Optimistic locking: version must equal the version last read (0 when no asset exists yet); a mismatch returns 409 version_conflict. mountLayer is required and must be under, over or cover; pageMode may be omitted (treated as classic), immersive or sandbox. Every rule needs a non-blank find; each replace is limited to 131072 bytes and the sum of id+name+find+replace across all rules to 33554432 bytes (validate_reject, with message and detail {reason: emptyFind|ruleReplace|rulesTotal|sharedState, index, name, max, actual, unit: bytes}). Saving sets status to passed immediately and increments version. Rules act on the rendering layer only and never enter the prompt. Exception: sharedState is kept when omitted or null, and {} clears it; an invalid declaration is rejected with reason: sharedState.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Role id owned by the caller.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Full rule list (replaces the stored list); null/omitted stores an empty list.
object
Trigger text/pattern that tells the client when to mount the persistent block (free text, stored as is).
Container the persistent block is mounted in (validate_reject when unknown).
immersive asks clients for a full-bleed page without the standard chrome. sandbox hands the whole chat area to the author’s page, rendered in an isolated frame (the new-style chat page); clients that cannot host it must tell the player instead of applying the rules the classic way. Empty/omitted = classic; unknown values are rejected.
Version last read; omitted/0 for the first save (409 if an asset already exists).
Omitted or null keeps the stored declaration; {} clears it.
object
Up to 16 polls by key. options lists 2–12 option IDs; share: true also allows the poll on share links.
object
object
Up to 16 counters by key. maxStep is 1–1000 per write, perMember caps each player per period, goal is optional, and period is none, day or week (UTC+8).
object
object
Responses
Section titled “Responses”Saved asset (new version, status: passed).
object
object
Absent in the empty shape.
Absent in the empty shape.
none when no asset exists. Saves currently store passed directly.
Optimistic-lock version; 0 when no asset exists.
Author-facing rejection reason (empty unless rejected). Absent in the empty shape.
Map from each Han character appearing in any rule’s find to a character class containing its Simplified/Traditional variants, for the client to expand before compiling the pattern. Absent in the empty shape.
object
Shared state the card declares: polls and counters players can write. Keys and option IDs are 1–64 letters, digits, ., _ or -, starting with a letter or digit; {} declares nothing. An invalid declaration is rejected with validate_reject (reason: sharedState).
object
Up to 16 polls by key. options lists 2–12 option IDs; share: true also allows the poll on share links.
object
object
Up to 16 counters by key. maxStep is 1–1000 per write, perMember caps each player per period, goal is optional, and period is none, day or week (UTC+8).
object
object
Example
{ "mountLayer": "under", "pageMode": "classic", "status": "none", "sharedState": { "counters": { "additionalProperty": { "period": "none" } } }}invalid_param (bad JSON) or validate_reject (unknown mountLayer/pageMode, empty find, rule or total size over limit). Body {"error": "<code>"}; for empty find and size limits also message (English sentence) and detail {reason, index (0-based, -1 for the whole set), name, max, actual, unit: "bytes"}.
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}forbidden — not the role owner.
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}role_not_found.
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}version_conflict — version does not match the stored version (also when an asset already exists and version is 0).
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}generic_error.
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}