> 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.

# Validate Character

POST https://api.loreos.app/v1/characters/validate
Content-Type: application/json

Dry-run validate an authoring payload WITHOUT creating the character.

Same validation + readiness scoring as ``POST /characters`` (north-star shape,
affordance schema, field ranges) but it never persists. Lets an agent iterate to
``authoring_readiness.status == "ready"`` before spending a create. Errors use the
same 422 codes as create, so a clean validate guarantees a clean create.

Reference: https://docs.loreos.app/api-reference/characters/validate-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

### Body (application/json)

This endpoint expects a V1CharacterCreateRequest.

- `slug` (string, required)
- `display_name` (string, required)
- `locale` (string, optional, default: ko-KR)
- `default_locale` (string, optional, nullable) — Default authored localization. When localizations is provided, the top-level display_name/profile/greeting/voice/example fields are the backward-compatible view of this locale.
- `primary_reply_language` (enum, optional, default: en-US) — Creation-time source of truth for visible character speech. Choose the language users should normally see in character replies, not the character's region or the latest user's message language. Use voice_samples, example_dialogues, and greeting text in this language unless stable code-switching is intentionally authored. Supported: en-US (English, default), ko-KR (Korean), ja-JP (Japanese), zh-CN (Simplified Chinese), zh-TW (Traditional Chinese). locale remains regional/provider metadata and does not control speech.
  - Allowed values: `en-US`, `ja-JP`, `ko-KR`, `zh-CN`, `zh-TW`
- `supported_input_languages` (list of string, optional, nullable) — Optional runtime guard for end-user input locales. When omitted, LoreOS accepts sessions in the character's primary_reply_language only. Use this when a character is intentionally multilingual; otherwise a KO-only character will reject an EN/JA session instead of serving the wrong locale by accident.
- `input_language_policy` (enum, optional, default: enforce_supported_input) — enforce_supported_input rejects /v1/sessions when request.locale is not in supported_input_languages. allow_cross_language records the metadata but lets the customer app own catalog routing.
  - Allowed values: `enforce_supported_input`, `allow_cross_language`
- `background_features` (V1CharacterBackgroundFeaturesCreatePolicy, optional) — Creation-time recurring-workflow policy. The default requests all four paid background features when the server policy is live; use \{"auto\_activate": false} for a character with no background schedules.
- `profile` (CharacterProfile, optional) — 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)
- `example_dialogues` (list of ExampleDialogue, optional)
- `localizations` (map from string to V1CharacterLocalizationInput, optional, nullable) — Per-locale authored runtime packs keyed by BCP-47 locale. Each session selects exactly one localization; voice_samples, example_dialogues, greeting, and localized profile from other locales are not injected into that session's context window.
- `greeting` (Greeting, optional, nullable) — Authored first character message emitted as-is on session start.
- `onboarding` (list of OnboardingEntry, optional)
- `character_direction` (CharacterDirection, optional, nullable)
- `life_template` (LifeTemplate, optional, nullable)
- `story_engine` (StoryEngine, optional, nullable)
- `arc_seeds` (list of ArcSeed, optional)
- `relationship_seed` (RelationshipSeed, optional, nullable)
- `emotion_baseline` (EmotionBaseline, optional, nullable)
- `forbidden_style` (list of map from string to any, optional)
- `handoff_triggers` (list of map from string to any, optional)
- `world_model_seed` (list of WorldModelSeedItem, optional)
- `inherits_style_anchors` (list of string, optional)
- `shared_style_anchors` (list of map from string to any, optional) — Resolved negative style examples used by runtime quality prompts. For migrated internal fixtures, send this alongside inherits_style_anchors so DB-backed character content stays self-contained.
- `raw_sources` (list of V1RawSource, optional)
- `behavioral_thresholds` (BehavioralThresholds, optional, nullable)
- `affordance_schema` (V1CharacterCreateRequestAffordanceSchema, optional, nullable)

## Response

### 200

Successful Response

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

## Errors

### 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.

### 422 Unprocessable Entity Error

Validation Error

- `detail` (list of ValidationError, optional)

## Types

### V1CharacterBackgroundFeaturesCreatePolicy

- `auto_activate` (boolean, optional, default: true) — Whether this create request may register the four recurring paid background workflows when the provisioning policy is live: story_room (daily), offscreen_heartbeat (~10 min), ambient_life (hourly), and offscreen_scene (~5 min). Set false to atomically skip all schedule registration for this character; activate individual features later with /activate-features.

### 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.

### V1CharacterLocalizationInput

- `locale` (string, optional, nullable)
- `reply_language` (enum, optional) — Visible reply language for sessions using this localization.
  - Allowed values: `en-US`, `ja-JP`, `ko-KR`, `zh-CN`, `zh-TW`
- `display_name` (string, optional, nullable)
- `profile` (map from string to any, optional, nullable)
- `greeting` (Greeting, optional, nullable)
- `voice_samples` (list of VoiceSample, optional)
- `example_dialogues` (list of ExampleDialogue, optional)
- `onboarding` (list of OnboardingEntry, optional)
- `style_notes` (list of any, optional)
- `honorifics` (map from string to any, optional, nullable)
- `do_not_translate_terms` (list of string, optional)
- `status` (enum, optional, default: published)
  - Allowed values: `draft`, `published`, `archived`
- `is_default` (boolean, optional, default: false)

### 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)

### V1RawSource

One raw text source the engine extracts a character's world-model + voice from. ``source_type`` + ``text`` are required (an entry without text carries no signal); extra keys are allowed and preserved for forward-compatibility.

- `source_type` (string, required) — What kind of source this is — e.g. 'chat_log', 'bio', 'self_description', 'note'.
- `text` (string, required) — The raw text the engine extracts the character's voice/world-model from.
- `label` (string, optional, nullable)
- `privacy_tier` (string, optional, nullable)
- `run_ingestion_pipeline` (boolean, optional, nullable)

### BehavioralThresholds

- `emotional_response_mode` (enum, optional, default: measured)
  - Allowed values: `raw`, `expressive`, `measured`, `sanitized_block`
- `emotional_mirror_intensity` (double, optional, default: 0.5)
- `sanitization_tolerance` (double, optional, default: 0.3)
- `snap_back_threshold` (double, optional, default: 0.6)
- `withdrawal_phrasing_style` (enum, optional, default: soft)
  - Allowed values: `soft`, `direct`, `cold`
- `proactivity_tolerance` (double, optional, default: 0.25)
- `warm_followup_enabled` (boolean, optional, default: false)
- `session_resume_enabled` (boolean, optional, default: true)
- `cold_initiate_enabled` (boolean, optional, default: false)
- `warm_followup_min_idle_seconds` (integer, optional, default: 15)
- `warm_followup_max_window_seconds` (integer, optional, default: 90)
- `session_resume_min_idle_minutes` (integer, optional, default: 30)
- `session_resume_max_unanswered_character_turns` (integer, optional, default: 5)
- `cold_initiate_min_idle_minutes` (integer, optional, default: 15)
- `min_gap_between_initiate_minutes` (integer, optional, default: 20)
- `daily_initiate_cap` (integer, optional, default: 3)
- `image_proactivity` (double, optional, default: 0) — How eagerly the character attaches a selfie/image to a message (0 = never, 1 = most eager). Applies to BOTH replies and proactive sends; needs an identity asset.
- `daily_image_cap` (integer, optional, default: 0) — Maximum images (selfies) the character sends per day. 0 disables images.
- `selfie_comfort` (double, optional, default: 0) — How comfortable the character is sending selfies of itself (0 = avoids them, 1 = very comfortable). Gates self-portrait images specifically.
- `withhold_propensity` (double, optional, default: 0.4)
- `emit_delay_when_low_affection` (boolean, optional, default: true)
- `exit_boundary_violation_count` (integer, optional, default: 3)
- `re_engage_openness` (double, optional, default: 0.5)

### V1CharacterCreateRequestAffordanceSchema

### CharacterValidationData

- `valid` (true, required)
- `slug` (string, required)
- `authoring_readiness` (AuthoringReadiness, required)
- `publication_preview` (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)

### ValidationError

- `loc` (list of ValidationErrorLocItems, required)
- `msg` (string, required)
- `type` (string, required)
- `input` (any, optional)
- `ctx` (ValidationErrorCtx, optional)

### 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)

### ValidationErrorLocItems

### ValidationErrorCtx

## Examples

**Request**

```json
{
  "slug": "string",
  "display_name": "string"
}
```

**Response**

```json
{
  "schema_version": "string",
  "data": {
    "valid": true,
    "slug": "string",
    "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_preview": {
      "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/validate"

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

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

print(response.json())
```

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

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/validate"

	payload := strings.NewReader("{\n  \"slug\": \"string\",\n  \"display_name\": \"string\"\n}")

	req, _ := http.NewRequest("POST", 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/validate")

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

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"slug\": \"string\",\n  \"display_name\": \"string\"\n}"

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.post("https://api.loreos.app/v1/characters/validate")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"slug\": \"string\",\n  \"display_name\": \"string\"\n}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

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

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

```csharp
using RestSharp;

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

```swift
import Foundation

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

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.loreos.app/v1/characters/validate")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
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()
```