> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.loreos.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.loreos.app/_mcp/server.

# Update a character

PATCH https://api.loreos.app/v1/characters/{slug}
Content-Type: application/json

Update a character (field-level) → publishes a new immutable version.

Idempotent on content (unchanged content reuses the version). 404 if the
slug is not owned by the caller's app.

Reference: https://docs.loreos.app/api-reference/characters/update-character

## Authentication

- `Authorization` header (bearer token, required) — LoreOS runtime API key (`ck_...`), sent as `Authorization: Bearer <key>`.

## Servers

- `https://api.loreos.app` (Production, default)
- `http://127.0.0.1:8000` (Local development)

## Request

### Path parameters

- `slug` (string, required)

### Body (application/json)

This endpoint expects a V1CharacterUpdateRequest.

- `display_name` (string, optional, nullable)
- `locale` (string, optional, nullable)
- `supported_input_languages` (list of string, optional, nullable) — Replace the character's accepted end-user input locales. Use only when intentionally widening or narrowing runtime routing.
- `input_language_policy` (enum, optional, nullable)
  - Allowed values: `enforce_supported_input`, `allow_cross_language`
- `profile` (CharacterProfile, optional, nullable) — Visible profile facts and style anchors. `extra="allow"` preserves forward compatibility, but readiness warns on unknown keys so typos do not silently become invisible runtime state.
- `voice_samples` (list of VoiceSample, optional, nullable)
- `example_dialogues` (list of ExampleDialogue, optional, nullable)
- `greeting` (Greeting, optional, nullable)
- `onboarding` (list of OnboardingEntry, optional, nullable)
- `character_direction` (CharacterDirection, optional, nullable)
- `life_template` (LifeTemplate, optional, nullable)
- `story_engine` (StoryEngine, optional, nullable)
- `arc_seeds` (list of ArcSeed, optional, nullable)
- `relationship_seed` (RelationshipSeed, optional, nullable)
- `emotion_baseline` (EmotionBaseline, optional, nullable)
- `forbidden_style` (list of map from string to any, optional, nullable)
- `handoff_triggers` (list of map from string to any, optional, nullable)
- `world_model_seed` (list of WorldModelSeedItem, optional, nullable)
- `behavioral_thresholds` (map from string to any, optional, nullable)
- `affordance_schema` (V1CharacterUpdateRequestAffordanceSchema, optional, nullable)

## Response

### 200

Successful Response

- `schema_version` ("v0", required) — LoreOS success-envelope version.
- `data` (CharacterUpdateData, required)
- `next_actions` (list of V1NextAction, required) — Concrete follow-up API actions; empty when no action is required.

## Errors

### 400 Bad Request Error

No patch fields were provided (`no_updates`).

- `detail` (V1ErrorDetail, required) — Stable public error core; domain-specific fields may be added alongside it.

### 401 Unauthorized Error

Runtime API key is missing, invalid, expired, or revoked.

- `detail` (V1ErrorDetail, required) — Stable public error core; domain-specific fields may be added alongside it.

### 404 Not Found Error

Character not found in the caller's app.

- `detail` (V1ErrorDetail, required) — Stable public error core; domain-specific fields may be added alongside it.

### 422 Unprocessable Entity Error

Patch content or affordance schema is invalid (`invalid_update` or `invalid_affordance_schema`).

- `detail` (V1ErrorDetail, required) — Stable public error core; domain-specific fields may be added alongside it.

## Types

### CharacterProfile

Visible profile facts and style anchors. `extra="allow"` preserves forward compatibility, but readiness warns on unknown keys so typos do not silently become invisible runtime state.

- `age_range` (string, optional, nullable) — Human-readable age band, e.g. 'late 20s' or '28-32'.
- `city` (string, optional, nullable) — Home/base city or area when the character is allowed to know it.
- `dating_intent` (string, optional, nullable) — Relationship posture if relevant to this character vertical.
- `visible_bio` (string, optional, nullable) — Short user-visible character bio. Prefer concrete lived details.
- `visible_interests` (list of string, optional) — Interests the character may safely reveal.
- `identity_anchors` (CharacterProfileIdentityAnchors, optional) — Stable identity/style anchors. Either a list[str] of text anchors for voice/persona continuity, OR a dict of visual keys (visual_summary / hair / style_constants / avoid_visual_changes) — the image pipeline reads the dict form to keep selfies visually consistent, so set a dict if you generate images.
- `visual_change_velocity` (enum, optional, nullable) — How quickly visual style/outfit/context should evolve.
  - Allowed values: `low`, `medium`, `high`
- `life_event_intensity` (enum, optional, nullable) — Default drama intensity for autonomous life events.
  - Allowed values: `low`, `medium`, `high`
- `latent_interests` (list of string, optional) — Interests that can emerge slowly through Story Room or chat.

### VoiceSample

- `text` (string, required) — A short example line in the character's voice. Required.
- `scenario_tag` (string, optional, nullable) — Situation label such as 'opener', 'teasing', or 'boundary'.
- `sample_role` (enum, optional, default: candidate) — anchor = strong style guide; candidate = softer example.
  - Allowed values: `anchor`, `candidate`
- `anchor_tier` (enum, optional, default: T2) — T1 strongest style anchor; T3 weakest/experimental.
  - Allowed values: `T1`, `T2`, `T3`

### ExampleDialogue

Few-shot user↔character interaction pattern. Unlike ``voice_samples`` (single-line voice anchors), this teaches pacing, boundary handling, question/answer shape, and how the character responds to user pressure. It is developer-authored guidance, not a committed memory.

- `scenario_tag` (string, required) — Situation label such as 'first_question', 'soft_boundary', or 'daily_share'.
- `turns` (list of ExampleDialogueTurn, required) — A compact user↔character exchange. Must include both roles.
- `usage` (enum, optional, default: interaction_pattern) — What the runtime should learn from this example.
  - Allowed values: `interaction_pattern`, `boundary_handling`, `topic_handling`, `voice_and_pacing`
- `teaching_goal` (string, optional, nullable) — Short note for coding agents: what this example should teach.

### Greeting

- `bubbles` (list of string, required) — Authored first character message bubbles emitted as-is.

### OnboardingEntry

- `question` (string, required)
- `answer` (string, required)
- `privacy_tier` (string, optional, default: private_knowledge) — How this self-knowledge may be used.
- `allowed_uses` (list of string, optional) — Examples: character_self_knowledge, planning_only.

### CharacterDirection

- `promise` (string, required) — The long-running character promise, not a one-week script.
- `core_motivation` (string, optional, nullable)
- `relationship_posture` (string, optional, nullable)
- `must_not` (list of string, optional) — Anti-railroad constraints and forbidden forced outcomes.

### LifeTemplate

- `weekday_blocks` (list of LifeTemplateBlock, optional) — Compressed weekday rhythm. Usually 3-5 blocks.
- `weekend_blocks` (list of LifeTemplateBlock, optional)
- `hobbies` (list of string, optional)
- `common_obligations` (list of string, optional)
- `recovery_behavior` (string, optional, nullable)

### StoryEngine

- `recurring_tensions` (list of string, optional)
- `allowed_event_sources` (list of enum, optional)
  - Allowed values: `routine_friction`, `supporting_cast_ping`, `object_mishap`, `small_obligation`, `environmental_change`, `goal_progress`, `quiet_reflection`, `social_contact`
- `supporting_cast` (list of SupportingCastEntry, optional)
- `blocked_motifs` (list of string, optional)

### ArcSeed

- `title` (string, required)
- `arc_question` (string, required)
- `duration_hint` (string, optional, nullable)
- `allowed_movements` (list of enum, optional)
  - Allowed values: `plant`, `reinforce`, `partial_payoff`, `defer`, `abandon`
- `forbidden_outcomes` (list of string, optional)
- `user_agency_points` (list of string, optional)

### RelationshipSeed

- `initial_distance` (string, optional, nullable)
- `sharing_style` (string, optional, nullable)
- `boundaries` (list of string, optional)

### EmotionBaseline

- `baseline_mood` (string, optional, nullable)
- `volatility` (enum, optional, nullable)
  - Allowed values: `low`, `medium`, `high`
- `stressors` (list of string, optional)
- `recovery_behavior` (string, optional, nullable)

### WorldModelSeedItem

Typed world seed. Existing fixtures may still use legacy `domain`/`claim_text`; the new API also supports typed location/person/object/routine seeds.

- `id` (string, optional, nullable)
- `type` (enum, optional, default: claim)
  - Allowed values: `location`, `person`, `object`, `organization`, `routine`, `constraint`, `claim`
- `name` (string, optional, nullable)
- `description` (string, optional, nullable)
- `relationship_to_character` (string, optional, nullable)
- `visibility` (enum, optional, default: character_known)
  - Allowed values: `character_known`, `session_visible`, `developer_only`
- `salience` (double, optional, default: 0.5)
- `domain` (string, optional, nullable)
- `claim_text` (string, optional, nullable)
- `confidence` (double, optional, nullable)

### V1CharacterUpdateRequestAffordanceSchema

### CharacterUpdateData

- `character_id` (string, required)
- `slug` (string, required)
- `requested_slug` (string, required)
- `status` ("published", required)
- `version_number` (integer, required)
- `authoring_readiness` (AuthoringReadiness, required)
- `publication` (CharacterPublication, required)

### V1NextAction

- `command` (string, required) — The next API command an agent can execute.
- `description` (string, required) — Why or when to execute the command.

### V1ErrorDetail

Stable public error core; domain-specific fields may be added alongside it.

- `code` (string, required)
- `message` (string, required)
- `fix` (string, required)

### CharacterProfileIdentityAnchors

Stable identity/style anchors. Either a list[str] of text anchors for voice/persona continuity, OR a dict of visual keys (visual_summary / hair / style_constants / avoid_visual_changes) — the image pipeline reads the dict form to keep selfies visually consistent, so set a dict if you generate images.

### ExampleDialogueTurn

- `role` (enum, required) — Speaker role in this authored interaction example.
  - Allowed values: `user`, `character`
- `text` (string, required) — One chat bubble or short turn. Keep examples natural and compact.

### LifeTemplateBlock

- `period` (enum, required) — Compressed daily period.
  - Allowed values: `morning`, `midday`, `afternoon`, `evening`, `night`
- `usual_activity` (string, required)
- `location_label` (string, optional, nullable)
- `social_contact_likelihood` (enum, optional, nullable)
  - Allowed values: `none`, `low`, `medium`, `high`

### SupportingCastEntry

- `id` (string, required)
- `name` (string, required)
- `role` (string, required)
- `agenda` (string, optional, nullable)
- `allowed_friction` (string, optional, nullable)

### AuthoringReadiness

- `status` (enum, required)
  - Allowed values: `ready`, `needs_attention`, `invalid`
- `authoring_quality_score` (integer, required)
- `missing_high_impact_fields` (list of string, required)
- `warnings` (list of map from string to string, required)
- `next_authoring_actions` (list of string, required)
- `readiness_kind` ("authoring", optional, default: authoring)
- `semantic_quality_evaluated` (false, optional, default: false)

### CharacterPublication

- `readiness_kind` ("authoring", required)
- `semantic_quality_evaluated` (false, required)
- `state` (enum, required)
  - Allowed values: `draft`, `needs_attention`, `ready`, `published`, `archived`
- `character_status` (string, required)
- `readiness_status` (string, required, nullable)
- `runtime_available` (boolean, required)
- `authoring_quality_ready` (boolean, required)
- `launch_ready` (boolean, required)

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "schema_version": "string",
  "data": {
    "character_id": "string",
    "slug": "string",
    "requested_slug": "string",
    "status": "string",
    "version_number": 1,
    "authoring_readiness": {
      "status": "ready",
      "authoring_quality_score": 1,
      "missing_high_impact_fields": [
        "string"
      ],
      "warnings": [
        {}
      ],
      "next_authoring_actions": [
        "string"
      ],
      "readiness_kind": "authoring",
      "semantic_quality_evaluated": false
    },
    "publication": {
      "readiness_kind": "string",
      "semantic_quality_evaluated": false,
      "state": "draft",
      "character_status": "string",
      "readiness_status": "string",
      "runtime_available": true,
      "authoring_quality_ready": true,
      "launch_ready": true
    }
  },
  "next_actions": [
    {
      "command": "string",
      "description": "string"
    }
  ]
}
```

**SDK Code**

```python
import requests

url = "https://api.loreos.app/v1/characters/slug"

payload = {}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.patch(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.loreos.app/v1/characters/slug';
const options = {
  method: 'PATCH',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.loreos.app/v1/characters/slug"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("PATCH", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.loreos.app/v1/characters/slug")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.patch("https://api.loreos.app/v1/characters/slug")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PATCH', 'https://api.loreos.app/v1/characters/slug', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.loreos.app/v1/characters/slug");
var request = new RestRequest(Method.PATCH);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.loreos.app/v1/characters/slug")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PATCH"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```