# Environment (/docs/environment) Studio [#studio] Use the following endpoint to access the studio to register, add credits, and create API keys. ```bash title="Studio" https://studio.narrativeprotocol.com ``` API Endpoint [#api-endpoint] Use the following API endpoint for all requests: ```bash title="API Endpoint" https://api.narrativeprotocol.com ``` Mainnet [#mainnet] Oracle smart contract for production purposes NEAR Smart Contract [#near-smart-contract] ```text title="NEAR smart contract" oracle-1.narrativeprotocol.near ``` Testnet / Devnet [#testnet--devnet] Oracle smart contract for development and testing purposes NEAR Smart Contract [#near-smart-contract-1] ```text title="NEAR smart contract" narrative-p.testnet ``` Solana Program [#solana-program] ```text title="Solana Program ID" Dn8SMZM2FkSNw1YF8DkXF8d3L7TpYQfNoaz4jdLtKLNx ``` # Introduction (/docs) Narrative Protocol [#narrative-protocol] Narrative Protocol is a **World State & Event Engine** that enables you to define and execute complex world simulations. The documentation is also available for machine readers at [/llms.txt](/llms.txt) (concise) and [/llms-full.txt](/llms-full.txt) (complete). Key Concepts [#key-concepts] Layers [#layers] The system separates the **design** layer (Blueprint) and **runtime** layer (Live): * **Blueprint**: Define your world, entity schemas, and events * **Live**: Create deployments, instances, and execute events Core Components [#core-components] | Component | Layer | Description | | -------------- | --------- | ------------------------------------------------------------------ | | Worlds | Blueprint | Container for all definitions (title, tags, prompt seed) | | Entity Schemas | Blueprint | Define the shape of entities (e.g., Horse, Track, Jockey) | | Events | Blueprint | Define what can happen (with versioned behavior) | | Deployments | Live | Running instance of a world with its own state | | Instances | Live | Actual entities based on entity schemas (e.g., HORSE\_1, HORSE\_2) | Feature Highlights [#feature-highlights] AI Event Execution [#ai-event-execution] Events can mutate the world by using an AI engine with configurable model selection. Each deployment can use a different AI model. Every AI response includes a cryptographic attestation for verification. Snapshotting [#snapshotting] Create new deployments by copying state from existing ones, useful for forking simulations or creating test environments. Multi-Chain Oracle [#multi-chain-oracle] Event execution records can be pushed to Solana and/or NEAR networks. This is useful for on-chain games or archival records. * [Quickstart Guide](/docs/quickstart) - Get running in 5 minutes * [Core Concepts](/docs/concepts) - Understand the architecture * [API Reference](/docs/api-reference) - Complete API documentation # Quickstart (/docs/quickstart) Quickstart [#quickstart] This guide walks through creating a world, defining schemas and events, running a deployment, and executing an event. Set your base API URL to `https://api.narrativeprotocol.com`. Getting an API key [#getting-an-api-key] Before making API calls, create an API key in [https://studio.narrativeprotocol.com](https://studio.narrativeprotocol.com). You will use this key in the `Authorization` header for all `/api/*` requests. *** Blueprint Layer [#blueprint-layer] The Blueprint layer is where you define your world structure. This includes creating worlds, entity schemas with attributes, and events with versioned behavior. These definitions are separate from runtime execution. Step 1: Create your world [#step-1-create-your-world] A world is the top-level container for your simulation. It holds `entities`, `events`, and can have multiple `deployments`. The `promptSeed` helps the AI understand your world context. cURL JavaScript Python ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "name": "Horse Racing", "description": "A horse racing simulation world", "domainTags": ["sports", "simulation"], "promptSeed": "This world simulates realistic horse racing events." }' ``` ```javascript const response = await fetch(`https://api.narrativeprotocol.com/api/worlds`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " }, body: JSON.stringify({ name: "Horse Racing", description: "A horse racing simulation world", domainTags: ["sports", "simulation"], promptSeed: "This world simulates realistic horse racing events." }) }); console.log(await response.json()); ``` ```python import requests response = requests.post( "https://api.narrativeprotocol.com/api/worlds", headers={ "Content-Type": "application/json", "Authorization": "Bearer " }, json={ "name": "Horse Racing", "description": "A horse racing simulation world", "domainTags": ["sports", "simulation"], "promptSeed": "This world simulates realistic horse racing events." } ) print(response.json()) ``` ```json { "success": true, "data": { "id": 39, "address": "0xd36f777a077ff3825653f6d521f1437b4da69699", "userId": 746631, "name": "Horse Racing", "description": "A horse racing simulation world", "domainTags": ["sports", "simulation"], "promptSeed": "This world simulates realistic horse racing events.", "isPublic": false, "createdAt": "2026-02-18T04:13:02.315Z", "updatedAt": "2026-02-18T04:13:02.315Z" } } ``` Step 2: Define an entity schema [#step-2-define-an-entity-schema] An Entity Schema defines the structure for entities in your world. For example, a "horse" schema defines what attributes (name, speed, wins) all horses will have. You can define attributes during entity schema creation, including `type`, `constraints`, and `defaultValue`. Attribute `type` values: * `string` * `integer` * `float` * `boolean` * `json` Attribute `constraints` fields: * `min` (number) * `max` (number) * `enum` (string array) cURL JavaScript Python ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds//entity-schemas \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "name": "horse", "description": "A racing horse", "attributes": [ { "name": "speed_rating", "type": "float", "constraints": { "min": 0, "max": 1 }, "defaultValue": 0.5 }, { "name": "wins", "type": "integer", "constraints": { "min": 0 }, "defaultValue": 0 } ] }' ``` ```javascript const response = await fetch(`https://api.narrativeprotocol.com/api/worlds//entity-schemas`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " }, body: JSON.stringify({ name: "horse", description: "A racing horse", attributes: [ { name: "speed_rating", type: "float", constraints: { min: 0, max: 1 }, defaultValue: 0.5 }, { name: "wins", type: "integer", constraints: { min: 0 }, defaultValue: 0 } ] }) }); console.log(await response.json()); ``` ```python import requests response = requests.post( "https://api.narrativeprotocol.com/api/worlds//entity-schemas", headers={ "Content-Type": "application/json", "Authorization": "Bearer " }, json={ "name": "horse", "description": "A racing horse", "attributes": [ { "name": "speed_rating", "type": "float", "constraints": { "min": 0, "max": 1 }, "defaultValue": 0.5 }, { "name": "wins", "type": "integer", "constraints": { "min": 0 }, "defaultValue": 0 } ] } ) print(response.json()) ``` ```json { "success": true, "data": { "id": 53, "worldId": 39, "name": "horse", "description": "A racing horse", "createdAt": "2026-02-18T04:13:49.117Z", "updatedAt": "2026-02-18T04:13:49.117Z", "attributes": [ { "name": "speed_rating", "type": "float", "constraints": { "min": 0, "max": 1 }, "defaultValue": 0.5 }, { "name": "wins", "type": "integer", "constraints": { "min": 0 }, "defaultValue": 0 } ] } } ``` Step 3: Create an event [#step-3-create-an-event] Events define what can happen in your world. `firstVersion` fields: * `inputSchema`: Defines the input payload expected at execution time. * `readEntities`: Lists entity schemas the event can read. * `stateChangeSchema`: Per-schema mutation mode (`partial`, `full`, `append`). `partial` merges into existing state, `full` replaces state, and `append` appends to arrays. * `outputSchema`: Defines the expected public result fields. * `mutationSettings`: Execution mode config (`ai` or `direct`; `direct` uses a template). When `mode` is `ai`, `behaviorPrompt` is required. When `mode` is `direct`, `template` is required. * `executionSettings`: Execution visibility and triggers (`admin`, `public`, `cron`) with optional `priceUsd` and `cron` config. For more details, see [Events](/docs/concepts/events). cURL JavaScript Python ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds//events \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "name": "race_result", "description": "Resolves a race and updates horse stats", "firstVersion": { "inputSchema": { "raceId": "string" }, "readEntities": [{ "schema": "horse" }], "stateChangeSchema": { "horse": "partial" }, "outputSchema": { "winner": "string", "time": "string" }, "mutationSettings": { "mode": "ai", "behaviorPrompt": "Determine the race winner based on horse speed ratings. Update the winner's wins count." } } }' ``` ```javascript const response = await fetch(`https://api.narrativeprotocol.com/api/worlds//events`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " }, body: JSON.stringify({ name: "race_result", description: "Resolves a race and updates horse stats", firstVersion: { inputSchema: { raceId: "string" }, readEntities: [{ schema: "horse" }], stateChangeSchema: { horse: "partial" }, outputSchema: { winner: "string", time: "string" }, mutationSettings: { mode: "ai", behaviorPrompt: "Determine the race winner based on horse speed ratings. Update the winner's wins count." } } }) }); console.log(await response.json()); ``` ```python import requests response = requests.post( "https://api.narrativeprotocol.com/api/worlds//events", headers={ "Content-Type": "application/json", "Authorization": "Bearer " }, json={ "name": "race_result", "description": "Resolves a race and updates horse stats", "firstVersion": { "inputSchema": { "raceId": "string" }, "readEntities": [{ "schema": "horse" }], "stateChangeSchema": { "horse": "partial" }, "outputSchema": { "winner": "string", "time": "string" }, "mutationSettings": { "mode": "ai", "behaviorPrompt": "Determine the race winner based on horse speed ratings. Update the winner's wins count." } } } ) print(response.json()) ``` *** Live Layer [#live-layer] The Live layer is where your simulation actually runs. Deployments are isolated runtime instances of a world. Step 4: Create a deployment [#step-4-create-a-deployment] A deployment is a running instance of a world with its own isolated state. cURL JavaScript Python ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds//deployments \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "name": "Season 1", "bindings": [{ "event": , "eventVersion": , "targetChains": ["near-testnet"] }] }' ``` ```javascript const response = await fetch(`https://api.narrativeprotocol.com/api/worlds//deployments`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " }, body: JSON.stringify({ name: "Season 1", bindings: [{ event: , eventVersion: , targetChains: ["near-testnet"] }] }) }); console.log(await response.json()); ``` ```python import requests response = requests.post( "https://api.narrativeprotocol.com/api/worlds//deployments", headers={ "Content-Type": "application/json", "Authorization": "Bearer " }, json={ "name": "Season 1", "bindings": [{ "event": , "eventVersion": , "targetChains": ["near-testnet"] }] } ) print(response.json()) ``` The `targetChains` option on each binding determines where event data for that event is stored on-chain: * `[]` - No on-chain storage (default) * `["solana-devnet"]` - Push to Solana devnet * `["near-testnet"]` - Push to NEAR testnet * `["solana-devnet", "near-testnet"]` - Push to both chains Step 5: Create entity instances [#step-5-create-entity-instances] Entity instances are the actual data in your deployment. cURL JavaScript Python ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds//deployments//entity-instances \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "entitySchema": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight Comet", "speed_rating": 0.85 } }' ``` ```javascript const response = await fetch(`https://api.narrativeprotocol.com/api/worlds//deployments//entity-instances`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " }, body: JSON.stringify({ entitySchema: "horse", instanceId: "HORSE_1", state: { name: "Midnight Comet", speed_rating: 0.85 } }) }); console.log(await response.json()); ``` ```python import requests response = requests.post( "https://api.narrativeprotocol.com/api/worlds//deployments//entity-instances", headers={ "Content-Type": "application/json", "Authorization": "Bearer " }, json={ "entitySchema": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight Comet", "speed_rating": 0.85 } } ) print(response.json()) ``` Step 6: Execute an event [#step-6-execute-an-event] Executing an event triggers the AI engine and applies state changes to your deployment. cURL JavaScript Python ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds//deployments//execute \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "event": , "input": { "raceId": "RACE_001" } }' ``` ```javascript const response = await fetch(`https://api.narrativeprotocol.com/api/worlds//deployments//execute`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer " }, body: JSON.stringify({ event: , input: { raceId: "RACE_001" } }) }); console.log(await response.json()); ``` ```python import requests response = requests.post( f"https://api.narrativeprotocol.com/api/worlds//deployments//execute", headers={ "Content-Type": "application/json", "Authorization": "Bearer " }, json={ "event": , "input": { "raceId": "RACE_001" } } ) print(response.json()) ``` ```json { "success": true, "data": { "historyId": 11133, "eventVersion": 1, "stateChanges": { "horse:HORSE_1": { "wins": 1 } }, "result": { "time": "2026-02-18T00:00:00Z", "winner": "Midnight Comet" }, "attestation": { "signature": "0x0b09aa672cde45034a1091532b1e7f7e31b476181fd06ca4faafc566245a823f3ff0e8a65afd102b64a1370170e5819f242480e727d449cbdcc338d7324b9a191c", "signing_address": "0xbBC409e6b529817D257aD61D5738D8e3a39b5791", "signing_algo": "ecdsa", "text": "d3fc5ada116a1c0c9359ea6b7297b210cfecd393facc04c5d20209002a2dd126:ad558a6182aa4c55374cbd5eae218d6b2dae1957780630ecc6a0f01e49214af9" }, "oracle": { "solana": null, "near": { "txHash": "8LjP9sqX26oQgScPy5RGzs8dEzwZiNSQHKrTpCLrP5yy", "receiptId": "ZWsNXEXPp1KyQWB68psjypi1PHGjMC8MrAomHBQLpGr" } } } } ``` Step 7: View updated state [#step-7-view-updated-state] After executing an event, you can query the entity instances to see how the state has changed. cURL JavaScript Python ```bash curl https://api.narrativeprotocol.com/api/worlds//deployments//entity-instances \ -H "Authorization: Bearer " ``` ```javascript const response = await fetch(`https://api.narrativeprotocol.com/api/worlds//deployments//entity-instances`, { headers: { "Authorization": "Bearer " } }); console.log(await response.json()); ``` ```python import requests response = requests.get( f"https://api.narrativeprotocol.com/api/worlds//deployments//entity-instances", headers={ "Authorization": "Bearer " } ) print(response.json()) ``` Next Steps [#next-steps] * Learn about [Core Concepts](/docs/concepts) - Architecture, versioning, modes * Explore [AI Engine](/docs/concepts/ai-engine) - How events are processed and model selection * See [On-chain Oracle](/docs/concepts/on-chain-oracle) - Multi-chain verification * Check the [API Reference](/docs/api-reference) - Complete endpoints # AI Engine (/docs/concepts/ai-engine) AI Engine [#ai-engine] Events in Narrative Protocol are executed by an AI engine that computes state changes based on world context, current state, event configuration, and user input. Execution Flow [#execution-flow] ``` 1. User calls POST /api/worlds/:worldAddress/deployments/:deploymentAddress/execute │ ▼ 2. System gathers context ├── World definition (name, description, tags, promptSeed) ├── Current entity states └── Event version config (mutationSettings.behaviorPrompt, schemas) │ ▼ 3. AI Engine processes (AI / configurable model) ├── Understands world context ├── Reads current state ├── Applies behavior prompt └── Generates state changes + public result │ ▼ 4. System applies changes ├── Updates entity instances ├── Records in event history └── Pushes to chain(s) (if configured) │ ▼ 5. Returns response with stateChanges, result, attestation, oracle ``` AI Model Selection [#ai-model-selection] Each deployment can specify which AI model to use via the `aiModelId` field. If not set, the default model `openai/gpt-oss-120b` is used. Available Models [#available-models] List them via: ```http GET /api/ai-models ``` ```json { "success": true, "data": [ { "modelId": "openai/gpt-oss-120b", "modelDisplayName": "GPT OSS 120B", "modelDescription": "...", "contextLength": 131000, "attestationSupported": true, "verifiable": true, "isDefault": true } ] } ``` Setting a Model on Deployment [#setting-a-model-on-deployment] ```json POST /api/worlds/:worldAddress/deployments { "name": "Season 1", "aiModelId": "openai/gpt-oss-120b", "bindings": [{ "event": "race_result", "eventVersion": 1 }] } ``` Request Format [#request-format] ```http POST /api/worlds/:worldAddress/deployments/:deploymentAddress/execute ``` ```json title="Request Body" { "event": "race_result", "input": { "raceId": "RACE_2024_001", "trackCondition": "wet" } } ``` Response Format [#response-format] ```json title="Response" { "success": true, "data": { "historyId": 42, "eventVersion": 1, "stateChanges": { "horse:HORSE_1": { "wins": 6, "lastRace": "2024-06-15" }, "horse:HORSE_2": { "stamina": 0.72 } }, "result": { "winner": "HORSE_1", "time": "1:45.32", "conditions": "wet track" }, "attestation": { "signature": "0x77057b...", "signing_address": "0x34B7Bc...", "signing_algo": "ecdsa", "text": "eda20c62..." }, "oracle": { "solana": { "signature": "5xYz...", "eventRecordPda": "7abc..." }, "near": null } } } ``` AI Context [#ai-context] The AI engine receives structured context: World Context [#world-context] ```json { "name": "Horse Racing", "description": "A horse racing simulation", "domainTags": ["sports", "simulation"], "promptSeed": "This world simulates realistic horse racing." } ``` Current State [#current-state] ```json [ { "schemaName": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight", "speed": 0.85, "wins": 5 } }, { "schemaName": "horse", "instanceId": "HORSE_2", "state": { "name": "Thunder", "speed": 0.78, "wins": 2 } } ] ``` Event Configuration [#event-configuration] ```json { "name": "race_result", "inputSchema": { "raceId": "string", "trackCondition": "string" }, "stateChangeSchema": { "horse": "partial" }, "outputSchema": { "winner": "string", "time": "string" }, "mutationSettings": { "mode": "ai", "behaviorPrompt": "Determine the race winner based on horse stats and conditions." }, "executionSettings": { "visibility": "admin" } } ``` Attestation [#attestation] Every AI response includes a cryptographic attestation: | Field | Description | | ----------------- | ------------------------------------- | | `signature` | ECDSA signature (65 bytes hex) | | `signing_address` | Ethereum-style address (20 bytes hex) | | `signing_algo` | Algorithm used (`"ecdsa"`) | | `text` | Hash of signed content | The attestation allows independent verification that the response came from NEAR AI and the content hasn't been modified. This is crucial for auditability and trust in AI-driven state changes. This allows independent verification that: 1. The response came from AI 2. The content hasn't been modified LLM Metadata [#llm-metadata] Event history records include LLM metadata for debugging and transparency: | Field | Description | | ------------------- | -------------------------------------------- | | `llmModel` | The model used (e.g., `openai/gpt-oss-120b`) | | `plainTextRequest` | The full prompt sent to the LLM | | `plainTextResponse` | The raw response from the LLM | State Change Application [#state-change-application] After AI returns, the system applies changes based on `stateChanges`: ```json // stateChanges { "horse": "partial", "race_log": "append" } // AI returns { "horse:HORSE_1": { "wins": 6 }, "race_log:LOG_1": { "entries": ["HORSE_1 won"] } } // Applied: // - horse:HORSE_1 gets wins merged (partial) // - race_log:LOG_1 gets entries appended (append) ``` Error Handling [#error-handling] | Scenario | Response | | ----------------- | ------------------------- | | Event not bound | 400 Bad Request | | Deployment locked | 400 Bad Request | | AI unavailable | 500 Internal Server Error | | Invalid input | 400 Bad Request | Related Concepts [#related-concepts] * [Event Versioning](/docs/concepts/events) - Behavior prompts and schemas * [Entity Instances](/docs/concepts/entities) - State change application * [On-chain Oracle](/docs/concepts/on-chain-oracle) - On-chain recording * [Deployments](/docs/concepts/deployment) - AI model selection per deployment # Architecture (/docs/concepts/architecture) Architecture [#architecture] Narrative Protocol uses a two-layer architecture that separates **design-time** definitions from **runtime** state. Blueprint Layer (Design-Time) [#blueprint-layer-design-time] The Blueprint layer contains definitions that describe the structure and behavior of your world. These don't hold runtime data. Worlds [#worlds] Top-level containers that group related definitions together. ```json title="World Definition" { "name": "Horse Racing", "description": "A horse racing simulation", "domainTags": ["sports", "simulation"], "promptSeed": "This world simulates realistic horse racing events." } ``` **Fields:** * `name` - Human-readable identifier * `description` - What this world simulates * `domainTags` - Categories for organization * `promptSeed` - Base context for AI-driven event resolution Entity Schemas [#entity-schemas] Define the shape of entities. They are templates, not data containers. ```json title="Entity Schema" { "name": "horse", "description": "A racing horse", "attributes": [ { "name": "speed_rating", "type": "float", "defaultValue": 0.5 }, { "name": "stamina", "type": "float", "defaultValue": 0.5 } ] } ``` **Attribute Types:** | Type | Description | | --------- | ---------------------- | | `string` | Text values | | `integer` | Whole numbers | | `float` | Floating-point numbers | | `boolean` | True/false values | | `json` | Complex nested data | Events [#events] Define what can happen in the world. Events have versioned behavior. ```json title="Event Definition" { "name": "race_result", "description": "Resolves a race", "firstVersion": { "inputSchema": { "raceId": "string" }, "stateChangeSchema": { "horse": "partial" }, "mutationSettings": { "mode": "ai", "behaviorPrompt": "Determine the winner based on horse stats" } } } ``` Live Layer (Runtime) [#live-layer-runtime] The Live layer contains actual runtime state and execution records. Deployments [#deployments] Running instances of a world with their own isolated state. ```json title="Deployment" { "worldAddress": "0xd36f777a077ff3825653f6d521f1437b4da69699", "name": "Season 1", "mode": "upgradable", "bindings": [{ "event": "race_result", "eventVersion": 1 }] } ``` Entity Instances [#entity-instances] Actual data belonging to a deployment. Multiple instances can share a schema. ```json title="Entity Instance" { "deploymentAddress": "0x123...abc", "entitySchema": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight Comet", "speed_rating": 0.85 } } ``` Event Bindings [#event-bindings] Connect deployments to specific event versions. Event History [#event-history] Records of every event execution with inputs, outputs, and attestations. Relationship Diagram [#relationship-diagram] ``` Blueprint Layer Live Layer ──────────────── ───────────────── World ─────────────────────── Deployment (targetChain) │ │ ├── Entity Schema ────────── Entity Instance │ └── Attribute └── state (JSON) │ └── Event ───────────────── Event Binding └── Event Version └── History Record ├── Solana Record └── NEAR Record ``` Design Principles [#design-principles] 1. **Separation of Concerns**: Definitions (what can exist) vs State (what does exist) 2. **Immutability**: Published versions and locked deployments cannot change 3. **Auditability**: Full history with cryptographic attestations 4. **Verifiability**: On-chain records for independent verification Related Concepts [#related-concepts] * [Events](/docs/concepts/events) - Event versioning * [Deployments](/docs/concepts/deployment) - Deployments * [Entities](/docs/concepts/entities) - Entities * [On-chain Oracle](/docs/concepts/on-chain-oracle) - On-chain verification # Deployments (/docs/concepts/deployment) Deployments [#deployments] A **Deployment** is a running instance of a world. It contains the actual runtime state - entity instances, event bindings, and execution history. Multiple deployments can exist for a single world, each with isolated state. Creating a Deployment [#creating-a-deployment] Deployments are created with a first event binding: ```json POST /api/worlds/:worldAddress/deployments { "name": "Season 1", "description": "First season of the racing simulation", "isLLMPublic": false, "aiModelId": "openai/gpt-oss-120b", "payToAddress": "6Q2...wallet", "bindings": [{ "event": "race_result", "eventVersion": 1, "targetChains": ["solana-devnet"], "onchain": { "stateChanges": ["wins", "speed_rating"], "result": ["winner"] } }] } ``` Fields [#fields] Use this table when creating a deployment payload. | Field | Type | Required | Description | | ------------------------- | --------- | -------- | ---------------------------------------------------- | | `name` | string | Yes | Human-readable name | | `description` | string | No | Optional description | | `aiModelId` | string | No | AI model to use (default: `openai/gpt-oss-120b`) | | `isLLMPublic` | boolean | No | Whether LLM prompt/response data is publicly visible | | `payToAddress` | string | No | Receiver wallet (solana) for paid public execution | | `mode` | enum | No | `upgradable` (default) or `locked` | | `bindings` | object\[] | Yes | Initial event bindings (array) | | `bindings[].event` | string | Yes | Event name to bind | | `bindings[].eventVersion` | number | Yes | Event version number to bind | | `bindings[].targetChains` | string\[] | No | On-chain oracle targets (see below) per binding | | `bindings[].onchain` | object | No | Field filter for on-chain push (per binding) | Target Chains [#target-chains] Use `targetChains` on each binding to choose where execution records for that event are pushed. The `targetChains` field is an array of network-specific chain identifiers. An empty array (default) means no on-chain storage. | Value | Description | Max Data Size | | ---------------- | ---------------------- | -------------------- | | `solana-devnet` | Push to Solana devnet | 1232 bytes per field | | `solana-mainnet` | Push to Solana mainnet | 1232 bytes per field | | `near-testnet` | Push to NEAR testnet | 2048 bytes per field | | `near-mainnet` | Push to NEAR mainnet | 2048 bytes per field | You can specify multiple chains per binding: ```json { "bindings": [{ "event": "race_result", "eventVersion": 1, "targetChains": ["solana-devnet", "near-testnet"] }] } ``` When both Solana and NEAR chains are included, the stricter Solana size limit applies. Public & Paid Execution [#public--paid-execution] Deployments can expose execution publicly via `/app/deployments/:address/execute` when the bound event version has `executionSettings.visibility = "public"`. If `executionSettings.priceUsd` is set, public callers must include a valid x402 `X-Payment` header and the deployment must define `payToAddress`. AI Model Selection [#ai-model-selection] Each deployment can specify which AI model to use for event execution via `aiModelId`. Available models are listed at `GET /api/ai-models`. If not specified, the default model (`openai/gpt-oss-120b`) is used. Selective On-Chain Push [#selective-on-chain-push] The `onchain` config controls which fields from `stateChanges` and `result` get pushed to the blockchain. This is now configured per-binding: ```json { "bindings": [{ "event": "race_result", "eventVersion": 1, "onchain": { "stateChanges": ["wins", "speed_rating"], "result": ["winner"] } }] } ``` When set, only the specified keys are included in the on-chain push. When omitted or null, all data is pushed. This is useful for staying within Solana's 1232-byte per-field limit. What a Deployment Contains [#what-a-deployment-contains] ``` Deployment ├── Entity Instances (actual data) │ ├── horse:HORSE_1 { name: "Midnight", speed: 0.85 } │ ├── horse:HORSE_2 { name: "Thunder", speed: 0.78 } │ └── ... ├── Event Bindings (which event versions to use) │ ├── race_result -> version 1 │ └── ... └── Event History (execution records) ├── History #1: race_result executed at 2024-06-15 └── ... ``` Deployment Modes [#deployment-modes] Deployments operate in one of two modes: `upgradable` (default) or `locked`. Upgradable Mode [#upgradable-mode] The default mode for new deployments. All operations are permitted. **Allowed Operations:** * Execute events * Create/update entity instances * Add event bindings * Update deployment metadata * Transition to locked mode Locked Mode [#locked-mode] Locking a deployment is **permanent and cannot be undone**. Once locked, the deployment becomes read-only forever. Make sure to create a snapshot before locking if you need to continue the simulation. A frozen state where no modifications are allowed. **This transition is irreversible.** **Blocked Operations:** * Execute events * Create/update/delete entity instances * Add event bindings * Delete the deployment **Allowed Operations:** * Read deployment data * Read entity instances * Read event history Locking a Deployment [#locking-a-deployment] ```http POST /api/worlds/:worldAddress/deployments/:deploymentAddress/lock ``` Locking a deployment is **permanent and cannot be undone**. Once locked, the deployment becomes read-only forever. Mode Comparison [#mode-comparison] | Feature | Upgradable | Locked | | ----------------- | ---------- | ------------ | | Execute events | Yes | No | | Modify instances | Yes | No | | Add bindings | Yes | No | | Read data | Yes | Yes | | View history | Yes | Yes | | Delete deployment | Yes | No | | Transition | To locked | None (final) | Snapshotting [#snapshotting] Snapshotting allows you to create a new deployment by copying the state from an existing one. This is useful for forking simulations, creating test environments, or preserving state before major changes. How It Works [#how-it-works] When creating a deployment with a `sourceDeploymentId`, the system: 1. Creates the new deployment 2. Copies all entity instances from the source 3. Copies event bindings (except duplicates) 4. New deployment starts as `upgradable` ```json title="Request Body" { "name": "Season 2", "bindings": [{ "event": "race_result", "eventVersion": 2 }], "sourceDeployment": "0x123...abc" } ``` What Gets Copied [#what-gets-copied] | Component | Copied | Notes | | ---------------- | ------ | ------------------------------ | | Entity instances | Yes | Full state at time of snapshot | | Event bindings | Yes | Except duplicates | | Event history | No | New deployment starts fresh | | Deployment mode | No | Always starts as upgradable | | Deployment name | No | Must provide new name | Use Cases [#use-cases] * **Forking Simulations**: Create parallel timelines from a common point * **Testing**: Test new event versions without affecting production * **Pre-Lock Preservation**: Create a snapshot before locking to continue later * **Version Migration**: Upgrade to new event versions while preserving state - Source deployment must be from the **same world** - You must own the source deployment - Source deployment can be in any mode (upgradable or locked) Related Concepts [#related-concepts] * [Architecture](/docs/concepts/architecture) - Blueprint vs Live layer * [Entities](/docs/concepts/entities) - Data within deployments * [Events](/docs/concepts/events) - Event versioning * [On-chain Oracle](/docs/concepts/on-chain-oracle) - On-chain verification * [AI Engine](/docs/concepts/ai-engine) - Model selection and execution # Entities (/docs/concepts/entities) Entities [#entities] Entity instances are the actual data in a deployment. They follow a schema, and each instance has its own state. Schema vs Instance [#schema-vs-instance] ``` Entity Schema: "horse" │ ├── HORSE_1: { name: "Midnight Comet", speed: 0.85, wins: 3 } ├── HORSE_2: { name: "Thunder Bolt", speed: 0.78, wins: 1 } └── HORSE_3: { name: "Golden Arrow", speed: 0.92, wins: 5 } ``` * **Schema**: Defines structure (what fields exist, their types, defaults) * **Instance**: Holds actual values for a specific entity Creating Instances [#creating-instances] ```http title="Request" POST /api/worlds/:worldAddress/deployments/:deploymentAddress/entity-instances ``` ```json title="Request Body" { "entitySchema": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight Comet", "speed_rating": 0.85 } } ``` Instance IDs [#instance-ids] The `instanceId` is a unique string identifier within the deployment: * Must be unique per deployment + schema combination * Used in state change references (e.g., `"horse:HORSE_1"`) * Case-sensitive Default Values [#default-values] Attributes not provided in `state` use schema defaults: ```json title="Default Values Example" // Schema defaults: speed_rating=0.5, stamina=0.5, wins=0 // Create with partial state { "instanceId": "HORSE_1", "state": { "name": "Star" } } // Result { "instanceId": "HORSE_1", "state": { "name": "Star", "speed_rating": 0.5, "stamina": 0.5, "wins": 0 } } ``` Updating State [#updating-state] Via Event Execution [#via-event-execution] Events update state through AI-computed changes: ```json title="State Changes" // AI returns stateChanges { "horse:HORSE_1": { "wins": 4, "lastRace": "2024-06-15" }, "horse:HORSE_2": { "stamina": 0.65 } } ``` The key format is `"schemaName:instanceId"`. State Change Modes [#state-change-modes] When events execute, the `stateChangeSchema` (event version field) determines how runtime `stateChanges` apply: | Mode | Behavior | Use Case | | --------- | --------------------------------- | --------------- | | `partial` | Merge changes into existing state | Most updates | | `full` | Replace entire state | Complete resets | | `append` | Append to arrays, merge objects | Logs, history | Partial Example [#partial-example] ```json // Existing: { name: "Star", speed: 0.8, wins: 3 } // Change: { wins: 4 } // Result: { name: "Star", speed: 0.8, wins: 4 } ``` Full Example [#full-example] ```json // Existing: { name: "Star", speed: 0.8, wins: 3 } // Change: { name: "Star", wins: 4 } // Result: { name: "Star", wins: 4 } // speed is gone ``` Append Example [#append-example] ```json // Existing: { name: "Star", raceLog: ["race1"] } // Change: { raceLog: ["race2"] } // Result: { name: "Star", raceLog: ["race1", "race2"] } ``` Querying Instances [#querying-instances] List by Deployment [#list-by-deployment] ```http GET /api/worlds/:worldAddress/deployments/:deploymentAddress/entity-instances ``` List by Schema [#list-by-schema] ```http GET /api/worlds/:worldAddress/deployments/:deploymentAddress/entity-instances?entitySchema="schemaName" ``` Constraints [#constraints] * Cannot modify instances in locked deployments - Instance IDs must be unique within deployment + schema Related Concepts [#related-concepts] * [Architecture](/docs/concepts/architecture) - Schema vs Instance relationship * [AI Engine](/docs/concepts/ai-engine) - How events modify state # Events (/docs/concepts/events) Events [#events] An **Event** in Narrative Protocol defines something that can happen in your world. Events are the actions, triggers, or occurrences that drive state changes in your simulation. What is an Event? [#what-is-an-event] Events describe behavior, not data. They answer the question: "What can happen in this world?" Examples: * `race_result` - A horse race is completed * `training_session` - A horse undergoes training * `injury_occurred` - A horse gets injured * `weather_change` - Weather conditions shift Event Structure [#event-structure] Each event belongs to a world and has: | Field | Description | | ------------- | ------------------------------------- | | `name` | Unique identifier within the world | | `description` | Human-readable explanation | | `versions` | One or more versioned implementations | Creating an Event [#creating-an-event] Events are always created with their first version: ```http title="Request" POST /api/worlds/:worldAddress/events ``` ```json title="Request Body" { "name": "race_result", "description": "Resolves a race and updates horse statistics", "firstVersion": { "inputSchema": { "raceId": "string" }, "readEntities": [{ "schema": "horse" }, { "schema": "jockey" }], "stateChangeSchema": { "horse": "partial" }, "outputSchema": { "winner": "string", "time": "string" }, "mutationSettings": { "mode": "ai", "behaviorPrompt": "Determine the race winner based on horse stats." }, "executionSettings": { "visibility": "admin" } } } ``` Event Versioning [#event-versioning] Events have versions that become immutable once locked. This ensures reproducibility and auditability of state changes over time. Why Versioning? [#why-versioning] Event behavior may need to change over time: * Fix bugs in behavior prompts * Add new input fields * Adjust state change logic Without versioning, changing an event would make historical executions impossible to reproduce. Version Lifecycle [#version-lifecycle] ``` Version 1 (draft) │ ▼ [lock] Version 1 (locked) ─── immutable, bound to deployments │ ▼ [create new version] Version 2 (draft) │ ▼ [lock] Version 2 (locked) ─── immutable, can be bound ``` Draft State [#draft-state] New versions start as drafts. You can modify: * `inputSchema` - What data the event accepts * `readEntities` - Which entity schemas to read * `stateChangeSchema` - How state changes (partial/full/append) * `outputSchema` - What gets returned publicly * `mutationSettings` - Execution mode and settings (AI prompt or direct template) Locked State [#locked-state] Once locked via `POST /api/worlds/:worldAddress/events/:eventName/event-versions/:version/lock`: * Version becomes **immutable** * Can be bound to deployments * Cannot be deleted Locking a version is **permanent**. Once locked, the version cannot be modified or deleted. Always test drafts thoroughly before locking. Version Fields [#version-fields] | Field | Type | Description | | ------------------- | --------- | ----------------------------------------------------------------------------------- | | `inputSchema` | object | Expected input fields and types | | `readEntities` | object\[] | Entity schema read config (`schema`, optional `filter`) | | `stateChangeSchema` | object | Per-schema change mode (`partial`, `full`, `append`) | | `outputSchema` | object | Public output structure | | `mutationSettings` | object | Execution mode settings: `ai` (with `behaviorPrompt`) or `direct` (with `template`) | | `executionSettings` | object | Visibility: `admin`, `public`, or `cron` (+ price/cron config) | readEntities and Filter [#readentities-and-filter] The `readEntities` field in event versions specifies which entity instances to load from the world state. The `filter` array tells the system which specific entity instance IDs to load from the event input. Filter Syntax [#filter-syntax] `filter` is an array of input paths that tells the system which specific entity instance IDs to load from the event input (e.g., `["raceId"]` looks up `eventInput.raceId` to find the target instance). Examples [#examples] 1. Simple input key [#1-simple-input-key] Event input: ```json { "raceId": "RACE_001" } ``` readEntities config: ```json [ { "schemaId": 1, "filter": ["raceId"] } ] ``` Loads instances where `instanceId === "RACE_001"` *** 2. Nested input key [#2-nested-input-key] Event input: ```json { "data": { "raceId": "RACE_001" } } ``` readEntities config: ```json [ { "schemaId": 1, "filter": ["data.raceId"] } ] ``` Uses dot notation to access nested values *** 3. Multiple filters (OR logic) [#3-multiple-filters-or-logic] Event input: ```json { "horse1": "HORSE_1", "horse2": "HORSE_2" } ``` readEntities config: ```json [ { "schemaId": 1, "filter": ["horse1", "horse2"] } ] ``` Loads both HORSE\_1 and HORSE\_2 instances *** 4. Schema-prefixed instance IDs [#4-schema-prefixed-instance-ids] If your instance IDs are prefixed with schema name (e.g., "horse:HORSE\_1"), use the schema name in the filter: Event input: ```json { "winnerId": "horse:HORSE_1" } ``` readEntities config: ```json [ { "schemaId": 1, "filter": ["winnerId"] } ] ``` Matches `instanceId === "horse:HORSE_1"` exactly *** 5. Multiple schemas with different filters [#5-multiple-schemas-with-different-filters] Event input: ```json { "raceId": "RACE_001", "betId": "BET_001" } ``` readEntities config: ```json [ { "schemaId": 1, "filter": ["raceId"] }, { "schemaId": 2, "filter": ["betId"] } ] ``` Schema 1 loads race instances, schema 2 loads bet instances *** 6. No filter (load all instances of schema) [#6-no-filter-load-all-instances-of-schema] ```json [ { "schemaId": 1 } ] ``` Loads all instances of schema 1 (useful for listing queries) Mutation Settings [#mutation-settings] `mutationSettings` controls how entity states are mutated during execution. State mutation always ends as runtime `stateChanges`, and the version `stateChangeSchema` map decides how those changes apply (`partial`, `full`, `append`). * When `mutationSettings.mode` is `"ai"`: `behaviorPrompt` is required, `template` must not be provided * When `mutationSettings.mode` is `"direct"`: `template` is required, `behaviorPrompt` must not be provided AI Mutation (mode: "ai") [#ai-mutation-mode-ai] In AI mode, the engine builds execution context from: * world definition (`name`, `description`, `domainTags`, `promptSeed`) * event version config (`inputSchema`, `readEntities`, `behaviorPrompt`, `outputSchema`) * current deployment state (filtered by `readEntities`) * runtime input The model returns structured `stateChanges` and `result`, then the system validates and applies the changes. Use AI mode when mutation logic depends on interpretation, simulation, or generative reasoning. Direct Mutation (mode: "direct") [#direct-mutation-mode-direct] In direct mode, execution does not rely on LLM reasoning for mutation. The system resolves a deterministic `mutationSettings.template` and produces `stateChanges`/`result` directly. Use direct mode when: * updates should be deterministic and explainable from input/template * you want lower variance than prompt-based execution * event logic is mostly transform/map/set operations `mutationSettings.template` is required when `mode` is `direct`. Example (`direct` mutation): ```json { "inputSchema": { "winner": "string" }, "outputSchema": { "winner": "string" }, "readEntities": [{ "schema": "horse" }], "stateChangeSchema": { "horse": "partial" }, "mutationSettings": { "mode": "direct", "template": { "stateChanges": { "horse:{{input.winner}}": { "wins": { "$increment": 1 } } }, "result": { "winner": "{{input.winner}}" } } }, "executionSettings": { "visibility": "admin" } } ``` Template Engine (Direct + Cron) [#template-engine-direct--cron] Direct mutation templates and cron input templates use the same resolver. Template syntax: * `{{input.someKey}}` from execution input * `{{walletAddress}}` wallet executing public app route (if present) * `{{timestamp}}` execution timestamp (ISO string) * Cron context variables (available in cron-triggered events): * `{{date}}` — Current date in ISO format (e.g., "2024-01-15") * `{{unix}}` — Current Unix timestamp in seconds * `{{counter}}` — Auto-incrementing counter that persists between cron runs * `{{cronState.key}}` — Access custom state stored from previous executions Type behavior: * If a value is exactly `{{...}}`, the resolver preserves primitive type when possible (for example number). * If `{{...}}` appears inside a larger string, it is interpolated as text. Operator support (applied against current instance state): * `$set`: set value directly (replaces entire field) * `$merge`: merge object into existing object field (preserves existing keys) * `$increment`: increment number (defaults to `+1` if value omitted/non-numeric) * `$append`: append item to array Direct template shape: ```json { "horse:HORSE_1": { "wins": { "$increment": 1 }, "lastWinnerWallet": { "$set": "{{walletAddress}}" }, "raceLog": { "$append": "{{input.raceId}}" } } } ``` Cron Context Example [#cron-context-example] When executing events via cron, you can use all the context variables to track state across scheduled executions: ```json { "executionSettings": { "visibility": "cron", "cron": { "schedule": "0 * * * *", "input": { "currentTime": "{{unix}}", "currentDate": "{{date}}", "runNumber": "{{counter}}", "previousRun": "{{cronState.counter}}" } }, "mutations": { "cronTracker:DAILY": { "totalRuns": { "$set": "{{counter}}" }, "lastRunTime": { "$set": "{{unix}}" }, "previousCounter": { "$set": "{{cronState.counter}}" } } } } } ``` The `counter` increments automatically on each cron execution and persists across runs. Use `{{cronState.counter}}` to access the previous counter value, or store custom state using any key name that will be available in `cronState` for the next execution. Notes: * Top-level keys should be `schemaName:instanceId`. * Missing instances can be auto-created during direct execution. * Input is validated before resolution (size/depth limits and blocked patterns). Execution Settings [#execution-settings] `executionSettings` controls who can run an event version and when it can run. Concurrent Execution Lock [#concurrent-execution-lock] Events use a mutex (mutual exclusion) lock to prevent race conditions when multiple executions target the same entity data. An event cannot execute concurrently if: 1. **Same event** - The identical event is already executing on the deployment 2. **Shared entity schema** - Any entity schema is both read by the new event and modified by a running event (via `readEntities` or `stateChanges`) ```typescript // Lock prevents concurrent execution if: eventA.readEntities ∩ eventB.stateChanges ≠ ∅ ``` This ensures data consistency when events modify overlapping entities. If a conflicting execution is in progress, the new execution request will fail. Visibility Modes [#visibility-modes] | Value | Meaning | | -------- | ------------------------------------------------------------------------------------------------------ | | `admin` | API-key authenticated execution via `/api/worlds/:worldAddress/deployments/:deploymentAddress/execute` | | `public` | Wallet-signed execution via `/app/deployments/:address/execute` | | `cron` | Scheduled execution by the background cron executor | Public Execution Details [#public-execution-details] When `visibility` is `public`: * deployment must be public * caller must provide `wallet`, `signature`, `message` * if `priceUsd > 0`, caller must provide a valid x402 `X-Payment` header Example (`public` with price): ```json { "executionSettings": { "visibility": "public", "priceUsd": 0.5 } } ``` Caller executes via: ```http POST /app/deployments/:address/execute ``` Cron Execution Details [#cron-execution-details] When `visibility` is `cron`: * set `executionSettings.cron.schedule` (valid cron expression) * optionally set `executionSettings.cron.input` template * executor runs periodically and triggers due events automatically Example (`cron` visibility): ```json { "executionSettings": { "visibility": "cron", "cron": { "schedule": "*/5 * * * *", "input": { "tick": "{{counter}}", "timestamp": "{{timestamp}}" } } } } ``` Cron input template example: ```json { "executionSettings": { "visibility": "cron", "cron": { "schedule": "0 * * * *", "input": { "tick": "{{counter}}", "runAt": "{{timestamp}}", "day": "{{date}}", "lastCounter": "{{cronState.counter}}" } } } } ``` State Change Modes [#state-change-modes] The `stateChangeSchema` maps entity schema names to change modes: ```json title="State Change Schema" { "horse": "partial", "race_record": "append" } ``` | Mode | Behavior | | --------- | ------------------------------------------- | | `partial` | Merge changes into existing state (default) | | `full` | Replace entire state | | `append` | Append to arrays, merge objects | Binding Versions to Deployments [#binding-versions-to-deployments] Deployments bind to specific versions. Multiple deployments can use different versions: ``` Event: "race_result" ├── Version 1 (locked) ─── bound to "Production" ├── Version 2 (locked) ─── bound to "Staging" └── Version 3 (draft) ─── not yet locked ``` Creating a New Version [#creating-a-new-version] ```http title="Request" POST /api/worlds/:worldAddress/events/:eventName/event-versions ``` ```json title="Request Body" { "inputSchema": { "raceId": "string", "weather": "string" }, "readEntities": [{ "schema": "horse" }, { "schema": "jockey" }], "stateChangeSchema": { "horse": "partial" }, "mutationSettings": { "mode": "ai", "behaviorPrompt": "Consider weather conditions when determining the winner." } } ``` Version numbers auto-increment based on existing versions. Best Practices [#best-practices] 1. **Test drafts thoroughly** before locking 2. **Document changes** in version descriptions 3. **Use staging deployments** to test new versions 4. **Never rely on modifying locked versions** - create new ones instead API Reference [#api-reference] * `POST /api/worlds/:worldAddress/events` - Create event with first version * `POST /api/worlds/:worldAddress/events/:eventName/event-versions` - Create additional versions * `POST /api/worlds/:worldAddress/events/:eventName/event-versions/:version/lock` - Lock (make immutable) Related Concepts [#related-concepts] * [AI Engine](/docs/concepts/ai-engine) - How events are processed * [Deployments](/docs/concepts/deployment) - Where events are bound and executed # Concepts (/docs/concepts) Core Concepts [#core-concepts] Narrative Protocol is a **World State & Event Engine** that manages complex simulations with versioned events, immutable state transitions, and AI-driven event execution. System Architecture [#system-architecture] The system uses a two-layer architecture: | Layer | Purpose | Components | | --------- | ------------------------- | ------------------------------------------------------ | | Blueprint | Design-time definitions | Worlds, Entity Schemas, Events, Event Versions | | Live | Runtime state & execution | Deployments, Entity Instances, Event Bindings, History | Terminology [#terminology] * **Entity Schema**: A blueprint that defines fields and types. * **Entity Instance**: Runtime data that follows an entity schema. * **Event**: A named action that can be executed. * **Event Version**: An immutable implementation of an event after publishing. * **Deployment**: A running instance of a world with isolated state. Key Concepts [#key-concepts] Architecture [#architecture] Blueprint vs Live layer separation. How design-time definitions become runtime instances. Entities [#entities] Multiple instances per schema, each with their own state. Events [#events] Events are the actions, triggers, or occurrences that drive state changes in your simulation. Deployments [#deployments] Running instances of a world with isolated state and on-chain oracle support. AI Engine [#ai-engine] Events are executed by an AI engine that computes state changes. On-chain Oracle [#on-chain-oracle] Event records are pushed to Solana and/or NEAR for on-chain immutability. Data Flow [#data-flow] ``` World (Blueprint) ├── Entity Schemas (define structure) │ └── Attributes └── Events (define behavior) └── Event Versions (immutable after publish) Deployment (Live, targetChains: ["solana-devnet"], etc.) ├── Entities (actual data) ├── Event Bindings (which versions to use) └── Event History (execution records) ├── Solana Records (on-chain verification) └── NEAR Records (on-chain verification) ``` New to Narrative Protocol? Start with the [Quickstart Guide](/docs/quickstart) to get running in 5 minutes. # On-chain Oracle (/docs/concepts/on-chain-oracle) On-chain Oracle [#on-chain-oracle] Narrative Protocol pushes event execution records to blockchain networks, providing immutable on-chain records for verification and auditability. The system supports multiple chains: **Solana** (devnet/mainnet) and **NEAR** (testnet/mainnet). Overview [#overview] After each event execution: 1. **Database Storage**: Full event data is stored 2. **On-chain Record**: Event data pushed to selected blockchain(s) 3. **Attestation**: AI attestation stored on-chain This enables independent verification of event executions. Target Chain Selection [#target-chain-selection] Use this section when deciding which chains event executions should write to. When creating a deployment, specify which chain network(s) to use via the `targetChains` array on each binding: | Value | Description | Max Data Size | | ---------------- | ---------------------- | -------------------- | | `solana-devnet` | Push to Solana devnet | 1232 bytes per field | | `solana-mainnet` | Push to Solana mainnet | 1232 bytes per field | | `near-testnet` | Push to NEAR testnet | 2048 bytes per field | | `near-mainnet` | Push to NEAR mainnet | 2048 bytes per field | An empty array `[]` (default) means no on-chain storage. ```json title="Deployment Configuration" { "worldId": 1, "name": "Season 1", "bindings": [{ "event": "race_result", "eventVersion": 1, "targetChains": ["solana-devnet", "near-testnet"] }] } ``` When both Solana and NEAR chains are included, the stricter Solana 1232-byte limit applies to size validation. Selective On-Chain Push [#selective-on-chain-push] Bindings can configure which fields get pushed on-chain via the `onchain` config: ```json { "bindings": [{ "event": "race_result", "eventVersion": 1, "targetChains": ["solana-devnet"], "onchain": { "stateChanges": ["wins", "speed_rating"], "result": ["winner"] } }] } ``` When `onchain` is set, only the specified keys from `stateChanges` and `result` are included in the on-chain push. When omitted or null, all data is pushed. This is particularly useful for Solana deployments where the 1232-byte per-field limit requires careful data selection. What Gets Stored On-chain [#what-gets-stored-on-chain] The Solana program uses a **single account per deployment** (PDA: `["deployment", deployment_id]`). Each `push_event` overwrites the account with the latest event data, so rent is paid only once per deployment. | Field | Type | Description | | ------------------ | --------- | --------------------------------------- | | `deployment_id` | u64 | Deployment identifier | | `event_version_id` | u64 | Event version of the latest event | | `event_history_id` | u64 | History ID of the latest event | | `event_count` | u64 | Total events pushed for this deployment | | `input_hash` | \[u8; 32] | SHA-256 of latest input JSON | | `state_changes` | String | Latest state changes JSON | | `result` | String | Latest result JSON | | `attestation` | struct | Attestation from the latest event | | `executed_at` | i64 | When latest event was executed | | `pushed_at` | i64 | When latest record was pushed | Solana Integration [#solana-integration] Program ID [#program-id] ``` Dn8SMZM2FkSNw1YF8DkXF8d3L7TpYQfNoaz4jdLtKLNx ``` Accounts [#accounts] **Config Account** (PDA: `["config"]`) ```rust pub struct Config { pub authority: Pubkey, // Who can push events pub event_count: u64, // Total events pushed pub bump: u8, // PDA bump } ``` **Deployment Record Account** (PDA: `["deployment", deployment_id_bytes]`) ```rust pub struct DeploymentRecord { pub deployment_id: u64, pub event_version_id: u64, pub event_history_id: u64, pub event_count: u64, pub input_hash: [u8; 32], #[max_len(1232)] pub state_changes: String, #[max_len(1232)] pub result: String, pub attestation: Attestation, pub executed_at: i64, pub pushed_at: i64, pub bump: u8, } ``` NEAR Integration [#near-integration] Addresses [#addresses] ``` oracle-1.narrativeprotocol.near (mainnet) narrative-p.testnet (testnet) ``` Contract Structure [#contract-structure] ```rust pub struct Contract { authority: AccountId, event_count: u64, events: LookupMap, } pub struct EventRecord { pub deployment_id: u64, pub event_version_id: u64, pub event_history_id: u64, pub input_hash: [u8; 32], pub state_changes: String, pub result: String, pub attestation: Attestation, pub executed_at: u64, pub pushed_at: u64, } ``` Contract Methods [#contract-methods] | Method | Description | Access | | ---------------------------- | --------------------------------------- | ----------------- | | `initialize` | Set up contract authority | Once per contract | | `push_event` | Push new event record | Authority only | | `get_deployment_event` | Get event by deployment ID and event ID | Public | | `get_deployment_event_count` | Get total event count | Public | Attestation Structure [#attestation-structure] Both chains store the same attestation format: ```rust pub struct Attestation { pub signature: Vec, // ECDSA signature (65 bytes) pub signing_address: [u8; 20], // Ethereum address pub signing_algo: String, // "ecdsa" pub text: String, // Signed content hash } ``` API Response [#api-response] Event execution response includes oracle results for configured chains: ```json { "success": true, "data": { "historyId": 42, "stateChanges": { ... }, "result": { ... }, "attestation": { ... }, "oracle": { "solana": { "signature": "5xYzABC123def456...", "eventRecordPda": "7abcDEF789ghi012..." }, "near": { "txHash": "Abc123...xyz", "receiptId": "Def456...uvw" } } } } ``` The `oracle` object contains: * `solana` - Solana transaction details (if any Solana network is in `targetChains`) * `near` - NEAR transaction details (if any NEAR network is in `targetChains`) Either field will be `null` if that chain family is not configured or not selected. Verification Flow [#verification-flow] To verify an event: 1. **Fetch on-chain record** from the appropriate chain 2. **Fetch off-chain data** from API: `GET /api/worlds/:worldAddress/deployments/:deploymentAddress/history` 3. **Compare state\_changes and result** - on-chain contains full JSON (or filtered JSON if `onchain` config is set) 4. **Compute hash** of input 5. **Compare hash** - it must match on-chain value 6. **Verify attestation** - check ECDSA signature Solana Verification [#solana-verification] ```javascript const onChainRecord = await program.account.deploymentRecord.fetch(pda); const offChainData = await fetch( `/api/worlds/${worldId}/deployments/${id}/history?page=1&limit=10`, ); const onChainStateChanges = JSON.parse(onChainRecord.stateChanges); assert.deepEqual(onChainStateChanges, offChainData.stateChanges); const inputHash = sha256(JSON.stringify(offChainData.input)); assert(inputHash === onChainRecord.inputHash); ``` NEAR Verification [#near-verification] ```javascript const onChainRecord = await contract.get_event({ event_history_id: historyId }); const offChainData = await fetch( `/api/worlds/${worldId}/deployments/${id}/history?page=1&limit=10`, ); const onChainStateChanges = JSON.parse(onChainRecord.state_changes); assert.deepEqual(onChainStateChanges, offChainData.stateChanges); const inputHash = sha256(JSON.stringify(offChainData.input)); assert(arrayEquals(inputHash, onChainRecord.input_hash)); ``` Data Size Limits [#data-size-limits] Use these limits to validate `stateChanges` and `result` payload sizes before execution. When using Solana chains, be mindful of the 1232-byte per-field limit. Use the `onchain` config on the deployment to select only the fields you need on-chain. | Chain Family | Max per field | Notes | | ------------ | ------------- | ------------------------------------- | | Solana | 1232 bytes | Use `onchain` config to filter fields | | NEAR | 2048 bytes | | | Both | 1232 bytes | Uses stricter Solana limit | If your `stateChanges` or `result` exceeds 1232 bytes when using a Solana chain, the event execution will fail with a validation error. Related Concepts [#related-concepts] * [AI Engine](/docs/concepts/ai-engine) - Generates attestations * [Deployments](/docs/concepts/deployment) - Target chains and `onchain` configuration # AI Models (/docs/api-reference/ai-models) AI Models API [#ai-models-api] List available AI models for event execution. Endpoints [#endpoints] List AI Models [#list-ai-models] ```http GET /api/ai-models ``` Examples [#examples] Get Available Models [#get-available-models] ```bash curl https://api.narrativeprotocol.com/api/ai-models \ -H "Authorization: Bearer " ``` ```json { "success": true, "data": [ { "modelId": "openai/gpt-oss-120b", "modelDisplayName": "GPT OSS 120B", "modelDescription": "Default model for event execution", "contextLength": 131000, "attestationSupported": true, "verifiable": true, "isDefault": true } ] } ``` Usage [#usage] Set the AI model when creating a deployment: ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds/39/deployments \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "name": "Season 1", "aiModelId": "openai/gpt-oss-120b", "bindings": [{ "event": "race_result", "eventVersion": 1 }] }' ``` # Deployments (/docs/api-reference/deployments) Deployments API [#deployments-api] Deployment routes are world-scoped. Endpoints [#endpoints] List Deployments [#list-deployments] ```http GET /api/worlds/:worldAddress/deployments?page=1&limit=20 ``` Create Deployment [#create-deployment] ```http POST /api/worlds/:worldAddress/deployments ``` ```json title="Request Body" { "name": "Season 1", "description": "Runtime deployment", "mode": "upgradable", "aiModelId": "openai/gpt-oss-120b", "isLLMPublic": false, "payToAddress": "6Q2...wallet", "bindings": [ { "event": "race_result", "eventVersion": 1, "targetChains": ["near-testnet"], "onchain": { "stateChanges": ["wins", "speed_rating"], "result": ["winner"] } } ], "sourceDeployment": "0x123...abc" } ``` | Field | Type | Required | Description | | --------------------------------- | ---------- | -------- | ----------------------------------------------------------------- | | `name` | `string` | Yes | Deployment name. | | `description` | `string` | No | Description. | | `mode` | `enum` | No | `upgradable` or `locked` (default `upgradable`). | | `aiModelId` | `string` | No | Selected model ID. | | `isLLMPublic` | `boolean` | No | Whether LLM prompt/response data is public. | | `payToAddress` | `string` | No | Payment receiver Solana address for paid public execution. | | `bindings` | `object[]` | Yes | Initial event bindings (array). | | `bindings[].event` | `string` | Yes | Event name. | | `bindings[].eventVersion` | `number` | Yes | Relative version number. | | `bindings[].targetChains` | `enum[]` | No | Chain networks for on-chain push (per binding). | | `bindings[].onchain` | `object` | No | Field filter for on-chain push (per binding). | | `bindings[].onchain.stateChanges` | `string[]` | No | Allowed state-change keys. | | `bindings[].onchain.result` | `string[]` | No | Allowed result keys. | | `sourceDeployment` | `string` | No | Clone entity state and bindings from source deployment (address). | Get Deployment [#get-deployment] ```http GET /api/worlds/:worldAddress/deployments/:address ``` Update Deployment [#update-deployment] ```http PUT /api/worlds/:worldAddress/deployments/:address ``` ```json title="Request Body" { "name": "Season 1 Updated", "description": "Updated description", "aiModelId": "openai/gpt-oss-120b", "isLLMPublic": true, "payToAddress": "6Q2...wallet" } ``` | Field | Type | Required | Description | | -------------- | --------- | -------- | --------------------------------------------------------- | | `name` | `string` | No | Deployment name. | | `description` | `string` | No | Description. | | `aiModelId` | `string` | No | Selected model ID. Set to `null` to remove. | | `isLLMPublic` | `boolean` | No | Whether LLM prompt/response data is public. | | `payToAddress` | `string` | No | Payment receiver Solana address. Set to `null` to remove. | Delete Deployment [#delete-deployment] ```http DELETE /api/worlds/:worldAddress/deployments/:address ``` Execute Event [#execute-event] ```http POST /api/worlds/:worldAddress/deployments/:address/execute ``` ```json title="Request Body" { "event": "race_result", "input": { "raceId": "RACE_001" } } ``` | Field | Type | Required | Description | | ------- | -------- | -------- | ---------------------- | | `event` | `string` | Yes | Event name to execute. | | `input` | `object` | No | Event input payload. | Lock Deployment [#lock-deployment] ```http POST /api/worlds/:worldAddress/deployments/:address/lock ``` Publish Deployment [#publish-deployment] ```http POST /api/worlds/:worldAddress/deployments/:address/publish ``` Makes the deployment public, enabling public execution. This endpoint is idempotent — calling it on an already public deployment returns success. **Response:** ```json { "success": true, "data": { "isPublic": true }, "message": "Deployment published successfully" } ``` Or if already public: ```json { "success": true, "data": { "isPublic": true }, "message": "Deployment is already public" } ``` List Event History [#list-event-history] ```http GET /api/worlds/:worldAddress/deployments/:address/history?page=1&limit=10 ``` List Event Bindings [#list-event-bindings] ```http GET /api/worlds/:worldAddress/deployments/:address/event-bindings ``` Get Event Binding [#get-event-binding] ```http GET /api/worlds/:worldAddress/deployments/:address/event-bindings?event=race_result ``` Query parameters: * `event` - Event name (required) Create Event Bindings [#create-event-bindings] ```http POST /api/worlds/:worldAddress/deployments/:address/event-bindings ``` ```json title="Request Body" [ { "event": "race_result", "eventVersion": 2 } ] ``` | Field | Type | Required | Description | | -------------- | -------- | -------- | -------------------------------- | | `event` | `string` | Yes | Event name to bind. | | `eventVersion` | `number` | Yes | Relative version number to bind. | Request body must be an array. Duplicate events are rejected. Update Event Bindings [#update-event-bindings] ```http PUT /api/worlds/:worldAddress/deployments/:address/event-bindings ``` ```json title="Request Body" [ { "event": "race_result", "eventVersion": 3, "targetChains": ["solana-devnet"], "onchain": { "stateChanges": ["wins"], "result": ["winner"] } } ] ``` | Field | Type | Required | Description | | ---------------------- | ---------- | -------- | --------------------------------- | | `event` | `string` | Yes | Event name to update. | | `eventVersion` | `number` | No | New version number to bind. | | `targetChains` | `enum[]` | No | Chain networks for on-chain push. | | `onchain` | `object` | No | Field filter for on-chain push. | | `onchain.stateChanges` | `string[]` | No | Allowed state-change keys. | | `onchain.result` | `string[]` | No | Allowed result keys. | Request body must be an array. Duplicate events are rejected. Delete Event Binding [#delete-event-binding] ```http DELETE /api/worlds/:worldAddress/deployments/:address/event-bindings?event=race_result ``` Query parameters: * `event` - Event name to unbind (required) List Entity Instances [#list-entity-instances] ```http GET /api/worlds/:worldAddress/deployments/:address/entity-instances?page=1&limit=20 ``` Create Entity Instance [#create-entity-instance] ```http POST /api/worlds/:worldAddress/deployments/:address/entity-instances ``` ```json title="Request Body" { "entitySchema": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight Comet", "speed_rating": 0.85 } } ``` | Field | Type | Required | Description | | -------------- | -------- | -------- | -------------------------------- | | `entitySchema` | `string` | Yes | Entity schema name. | | `instanceId` | `string` | Yes | Unique per deployment+schema. | | `state` | `object` | No | Initial state. Defaults to `{}`. | Public Execution [#public-execution] Public execution is available via app route: ```http POST /app/deployments/:address/execute ``` ```json title="Request Body" { "event": "race_result", "input": { "raceId": "RACE_001" }, "wallet": "", "signature": "", "message": "narrativeprotocol:::" } ``` | Field | Type | Required | Description | | ----------- | -------- | -------- | ---------------------------------------------------------------- | | `event` | `string` | Yes | Event name to execute. | | `input` | `object` | No | Event input payload. | | `wallet` | `string` | Yes | Signer wallet (base58 public key). | | `signature` | `string` | Yes | Signature over `message`. | | `message` | `string` | Yes | `narrativeprotocol:{deploymentAddress}:{eventName}:{timestamp}`. | Rules: * Event must allow public execution. * If `executionSettings.priceUsd > 0`, provide valid `X-Payment` header (x402). # Entity Instances (/docs/api-reference/entity-instances) Entity Instances API [#entity-instances-api] Entity instance routes are deployment-scoped under worlds. Endpoints [#endpoints] List Entity Instances [#list-entity-instances] ```http GET /api/worlds/:worldAddress/deployments/:address/entity-instances?entitySchema=horse&page=1&limit=20 ``` Query parameters: * `entitySchema` - Filter by entity schema name (optional) Create Entity Instance [#create-entity-instance] ```http POST /api/worlds/:worldAddress/deployments/:address/entity-instances ``` ```json title="Request Body" { "entitySchema": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight Comet", "speed_rating": 0.85 } } ``` | Field | Type | Required | Description | | -------------- | -------- | -------- | ----------------------------------------- | | `entitySchema` | `string` | Yes | Entity schema name. | | `instanceId` | `string` | Yes | Instance key unique in deployment+schema. | | `state` | `object` | No | Initial state. Defaults to `{}`. | Get Entity Instance [#get-entity-instance] ```http GET /api/worlds/:worldAddress/deployments/:address/entity-instances/:instanceId ``` Update Entity Instance [#update-entity-instance] ```http PUT /api/worlds/:worldAddress/deployments/:address/entity-instances/:instanceId ``` ```json title="Request Body" { "state": { "name": "Midnight Comet", "speed_rating": 0.9, "wins": 5 } } ``` | Field | Type | Required | Description | | ------- | -------- | -------- | ----------------------------------------------- | | `state` | `object` | Yes | Updated state object (replaces existing state). | Delete Entity Instance [#delete-entity-instance] ```http DELETE /api/worlds/:worldAddress/deployments/:address/entity-instances/:instanceId ``` Example [#example] ```bash curl -X POST https://api.narrativeprotocol.com/api/worlds/0x123.../deployments/0xabc.../entity-instances \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "entitySchema": "horse", "instanceId": "HORSE_1", "state": { "name": "Midnight Comet", "speed_rating": 0.85 } }' ``` # Entity Schemas (/docs/api-reference/entity) Entity Schemas API [#entity-schemas-api] Entity schema routes are world-scoped. Endpoints [#endpoints] List Entity Schemas [#list-entity-schemas] ```http GET /api/worlds/:worldAddress/entity-schemas?page=1&limit=20 ``` Create Entity Schema [#create-entity-schema] ```http POST /api/worlds/:worldAddress/entity-schemas ``` ```json title="Request Body" { "name": "horse", "description": "A racing horse", "attributes": [ { "name": "speed_rating", "type": "float", "constraints": { "min": 0, "max": 1 }, "defaultValue": 0.5 } ] } ``` | Field | Type | Required | Description | | ----------------------------------- | ---------- | -------- | ----------------------------------------------- | | `name` | `string` | Yes | Schema name, 1-255 chars | | `description` | `string` | No | Schema description | | `attributes` | `object[]` | No | Inline attributes created with the schema | | `attributes[].name` | `string` | Yes | Attribute key | | `attributes[].type` | `enum` | Yes | `integer \| float \| string \| boolean \| json` | | `attributes[].constraints` | `object` | No | Validation rules for attribute values | | `attributes[].constraints.min` | `number` | No | Lower bound | | `attributes[].constraints.max` | `number` | No | Upper bound | | `attributes[].constraints.enum` | `string[]` | No | Allowed string values | | `attributes[].constraints.pattern` | `string` | No | Regex for string attributes | | `attributes[].constraints.required` | `boolean` | No | Required semantics flag | | `attributes[].defaultValue` | `any` | No | Default state value | List Attributes for a Schema [#list-attributes-for-a-schema] ```http GET /api/worlds/:worldAddress/entity-schemas/:schemaName/attributes?page=1&limit=20 ``` Create Attribute [#create-attribute] ```http POST /api/worlds/:worldAddress/entity-schemas/:schemaName/attributes ``` ```json title="Request Body" { "name": "wins", "type": "integer", "constraints": { "min": 0 }, "defaultValue": 0 } ``` | Field | Type | Required | Description | | ---------------------- | ---------- | -------- | ----------------------------------------------- | | `name` | `string` | Yes | Attribute key | | `type` | `enum` | Yes | `integer \| float \| string \| boolean \| json` | | `constraints` | `object` | No | Validation rules | | `constraints.min` | `number` | No | Lower bound | | `constraints.max` | `number` | No | Upper bound | | `constraints.enum` | `string[]` | No | Allowed string values | | `constraints.pattern` | `string` | No | Regex for string attributes | | `constraints.required` | `boolean` | No | Required semantics flag | | `defaultValue` | `any` | No | Default state value | Get Attribute by Name [#get-attribute-by-name] ```http GET /api/worlds/:worldAddress/entity-schemas/:schemaName/attributes/:name ``` Update Attribute by Name [#update-attribute-by-name] ```http PUT /api/worlds/:worldAddress/entity-schemas/:schemaName/attributes/:name ``` ```json title="Request Body" { "name": "wins", "type": "integer", "constraints": { "min": 0, "required": true }, "defaultValue": 0 } ``` | Field | Type | Required | Description | | -------------- | -------- | -------- | ---------------------------------------------------------------------------------- | | `name` | `string` | No | New attribute key | | `type` | `enum` | No | `integer \| float \| string \| boolean \| json` | | `constraints` | `object` | No | Validation rules (same shape as create) | | `defaultValue` | `any` | No | Default value for future merges/defaulting | Delete Attribute by Name [#delete-attribute-by-name] ```http DELETE /api/worlds/:worldAddress/entity-schemas/:schemaName/attributes/:name ``` Get Entity Schema [#get-entity-schema] ```http GET /api/worlds/:worldAddress/entity-schemas/:schemaName ``` Update Entity Schema [#update-entity-schema] ```http PUT /api/worlds/:worldAddress/entity-schemas/:schemaName ``` ```json title="Request Body" { "name": "horse", "description": "A racing horse updated" } ``` | Field | Type | Required | Description | | ------------- | -------- | -------- | ------------------- | | `name` | `string` | No | Schema name. | | `description` | `string` | No | Schema description. | Delete Entity Schema [#delete-entity-schema] ```http DELETE /api/worlds/:worldAddress/entity-schemas/:schemaName ``` # Events (/docs/api-reference/events) Events API [#events-api] Event routes are world-scoped. Endpoints [#endpoints] List Events [#list-events] ```http GET /api/worlds/:worldAddress/events?page=1&limit=20 ``` Create Event [#create-event] ```http POST /api/worlds/:worldAddress/events ``` ```json title="Request Body" { "name": "race_result", "description": "Resolves a race and updates horse stats", "firstVersion": { "inputSchema": { "raceId": "string" }, "outputSchema": { "winner": "string", "time": "string" }, "readEntities": [{ "schema": "horse" }], "stateChanges": { "horse": "partial" }, "behaviorPrompt": "Determine the race winner based on horse speed ratings.", "mutationSettings": { "mode": "ai" }, "executionSettings": { "visibility": "admin" } } } ``` | Field | Type | Required | Description | | ---------------------------------------------- | ---------- | ------------- | ------------------------------------------------- | | `name` | `string` | Yes | Event name, unique within world. | | `description` | `string` | No | Event description. | | `firstVersion` | `object` | Yes | Initial event-version definition. | | `firstVersion.inputSchema` | `object` | No | Execution input contract. | | `firstVersion.outputSchema` | `object` | No | Public result contract. | | `firstVersion.readEntities` | `object[]` | No | Read access config. | | `firstVersion.readEntities[].schema` | `string` | Yes | Entity schema name to read. | | `firstVersion.readEntities[].filter` | `string[]` | No | Filter expressions (see below). | | `firstVersion.stateChanges` | `object` | No | Per-schema mode map. | | `firstVersion.behaviorPrompt` | `string` | No | Prompt for AI mode. | | `firstVersion.mutationSettings.mode` | `enum` | No | `ai` or `direct`. | | `firstVersion.mutationSettings.template` | `object` | Conditionally | Required when mode is `direct`. | | `firstVersion.executionSettings.visibility` | `enum` | No | `admin`, `public`, or `cron`. | | `firstVersion.executionSettings.priceUsd` | `number` | No | Price for public execution. | | `firstVersion.executionSettings.cron.schedule` | `string` | Conditionally | Required for cron visibility; must be valid cron. | | `firstVersion.executionSettings.cron.input` | `object` | No | Cron input template. | Get Event [#get-event] ```http GET /api/worlds/:worldAddress/events/:eventName ``` Update Event [#update-event] ```http PUT /api/worlds/:worldAddress/events/:eventName ``` ```json title="Request Body" { "name": "race_result", "description": "Updated event description" } ``` | Field | Type | Required | Description | | ------------- | -------- | -------- | ------------------ | | `name` | `string` | No | Event name. | | `description` | `string` | No | Event description. | Delete Event [#delete-event] ```http DELETE /api/worlds/:worldAddress/events/:eventName ``` List Event Versions [#list-event-versions] ```http GET /api/worlds/:worldAddress/events/:eventName/event-versions?page=1&limit=20 ``` Create Event Version [#create-event-version] ```http POST /api/worlds/:worldAddress/events/:eventName/event-versions ``` ```json title="Request Body" { "inputSchema": { "raceId": "string", "weather": "string" }, "outputSchema": { "winner": "string", "time": "string", "weather": "string" }, "readEntities": [{ "schema": "horse", "filter": ["eq:instanceId:HORSE_1"] }], "stateChanges": { "horse": "partial" }, "behaviorPrompt": "Consider weather conditions.", "mutationSettings": { "mode": "direct", "template": { "result": { "winner": "{{input.winner}}" } } }, "executionSettings": { "visibility": "cron", "cron": { "schedule": "*/5 * * * *", "input": { "tick": "{{counter}}" } } } } ``` | Field | Type | Required | Description | | --------------------------------- | ---------- | ------------- | -------------------------------------------------- | | `inputSchema` | `object` | No | Execution input contract. | | `outputSchema` | `object` | No | Public result contract. | | `readEntities` | `object[]` | No | Read access config. | | `readEntities[].schema` | `string` | Yes | Entity schema name to read. | | `readEntities[].filter` | `string[]` | No | Filter expressions. | | `stateChanges` | `object` | No | Per-schema mode map (`partial`, `full`, `append`). | | `behaviorPrompt` | `string` | No | Prompt for AI mode. | | `mutationSettings.mode` | `enum` | No | `ai` or `direct`. | | `mutationSettings.template` | `object` | Conditionally | Required when mode is `direct`. | | `executionSettings.visibility` | `enum` | No | `admin`, `public`, or `cron`. | | `executionSettings.priceUsd` | `number` | No | Price for public execution. | | `executionSettings.cron.schedule` | `string` | Conditionally | Required for cron visibility; must be valid cron. | | `executionSettings.cron.input` | `object` | No | Cron input template. | readEntities Filter [#readentities-filter] The `filter` field in `readEntities` specifies which entity instance IDs to load from the event input. Filter Syntax [#filter-syntax] `filter` is an array of input paths that tells the system which specific entity instance IDs to load from the event input (e.g., `["raceId"]` looks up `eventInput.raceId` to find the target instance). Examples [#examples] **1. Simple input key** Event input: `{ "raceId": "RACE_001" }` ```json [{ "schema": "horse", "filter": ["raceId"] }] ``` Loads instances where `instanceId === "RACE_001"` *** **2. Nested input key** Event input: `{ "data": { "raceId": "RACE_001" } }` ```json [{ "schema": "horse", "filter": ["data.raceId"] }] ``` Uses dot notation to access nested values *** **3. Multiple filters (OR logic)** Event input: `{ "horse1": "HORSE_1", "horse2": "HORSE_2" }` ```json [{ "schema": "horse", "filter": ["horse1", "horse2"] }] ``` Loads both HORSE\_1 and HORSE\_2 instances *** **4. Schema-prefixed instance IDs** If instance IDs are prefixed with schema name (e.g., "horse:HORSE\_1"): Event input: `{ "winnerId": "horse:HORSE_1" }` ```json [{ "schema": "horse", "filter": ["winnerId"] }] ``` Matches `instanceId === "horse:HORSE_1"` exactly *** **5. Multiple schemas with different filters** Event input: `{ "raceId": "RACE_001", "betId": "BET_001" }` ```json [ { "schema": "race", "filter": ["raceId"] }, { "schema": "bet", "filter": ["betId"] } ] ``` *** **6. No filter (load all instances)** ```json [{ "schema": "horse" }] ``` Loads all instances of schema (useful for listing queries) Lock Event Version [#lock-event-version] ```http POST /api/worlds/:worldAddress/events/:eventName/event-versions/:version/lock ``` Get Event Version [#get-event-version] ```http GET /api/worlds/:worldAddress/events/:eventName/event-versions/:version ``` Update Event Version [#update-event-version] ```http PUT /api/worlds/:worldAddress/events/:eventName/event-versions/:version ``` ```json title="Request Body" { "inputSchema": { "raceId": "string", "weather": "string" }, "outputSchema": { "winner": "string", "time": "string" }, "readEntities": [{ "schema": "horse", "filter": ["raceId"] }], "stateChangeSchema": { "horse": "partial" }, "behaviorPrompt": "Updated behavior prompt.", "mutationSettings": { "mode": "ai", "behaviorPrompt": "New prompt" }, "executionSettings": { "visibility": "admin" } } ``` | Field | Type | Required | Description | | --------------------------------- | ---------- | ------------- | -------------------------------------------------- | | `inputSchema` | `object` | No | Execution input contract. | | `outputSchema` | `object` | No | Public result contract. | | `readEntities` | `object[]` | No | Read access config. | | `readEntities[].schema` | `string` | Yes | Entity schema name to read. | | `readEntities[].filter` | `string[]` | No | Filter expressions. | | `stateChangeSchema` | `object` | No | Per-schema mode map (`partial`, `full`, `append`). | | `behaviorPrompt` | `string` | No | Prompt for AI mode. | | `mutationSettings` | `object` | No | Mutation configuration. Set to `null` to remove. | | `mutationSettings.mode` | `enum` | No | `ai` or `direct`. | | `mutationSettings.template` | `object` | Conditionally | Required when mode is `direct`. | | `mutationSettings.behaviorPrompt` | `string` | No | Prompt for AI mode. | | `executionSettings` | `object` | No | Execution settings. Set to `null` to remove. | | `executionSettings.visibility` | `enum` | No | `admin`, `public`, or `cron`. | | `executionSettings.priceUsd` | `number` | No | Price for public execution. | | `executionSettings.cron.schedule` | `string` | Conditionally | Required for cron visibility. | | `executionSettings.cron.input` | `object` | No | Cron input template. | Delete Event Version [#delete-event-version] ```http DELETE /api/worlds/:worldAddress/events/:eventName/event-versions/:version ``` # API Reference (/docs/api-reference) API Reference [#api-reference] This section documents the current Narrative Protocol API surface. Authentication [#authentication] * `Authorization: Bearer ` is required for all `/api/*` routes. * Public execution uses `/app/*` routes and wallet signature auth. Endpoints [#endpoints] Worlds [#worlds] * [Worlds](/docs/api-reference/worlds) - World lifecycle and publishing Entity Schemas & Attributes [#entity-schemas--attributes] * [Entity Schemas](/docs/api-reference/entity) - World-scoped schema and attribute routes Events & Versions [#events--versions] * [Events](/docs/api-reference/events) - Event and event-version management Deployments [#deployments] * [Deployments](/docs/api-reference/deployments) - Runtime environments and execution Entity Instances [#entity-instances] * [Entity Instances](/docs/api-reference/entity-instances) - Deployment state data AI Models [#ai-models] * [AI Models](/docs/api-reference/ai-models) - Available models # Worlds (/docs/api-reference/worlds) Worlds API [#worlds-api] Create and manage worlds in Narrative Protocol. Endpoints [#endpoints] List Worlds [#list-worlds] ```http GET /api/worlds?page=1&limit=20 ``` Create World [#create-world] ```http POST /api/worlds ``` ```json title="Request Body" { "name": "Horse Racing", "description": "A horse racing simulation world", "domainTags": ["sports", "simulation"], "promptSeed": "This world simulates realistic horse racing events." } ``` | Field | Type | Required | Description | | ------------- | ---------- | -------- | ---------------------------------------------- | | `name` | `string` | Yes | World name, 1-255 chars. | | `description` | `string` | No | Human-readable description. | | `domainTags` | `string[]` | No | Domain/category tags. Defaults to `[]`. | | `promptSeed` | `string` | No | Base context used for event execution prompts. | Get World [#get-world] ```http GET /api/worlds/:idOrAddress ``` Supports both numeric ID (e.g., `39`) or hex address (e.g., `0x1234...`). Update World [#update-world] ```http PUT /api/worlds/:idOrAddress ``` ```json title="Request Body" { "name": "Horse Racing v2", "description": "Updated description", "domainTags": ["sports", "simulation", "strategy"], "promptSeed": "Updated world guidance" } ``` | Field | Type | Required | Description | | ------------- | ---------- | -------- | ------------------------------------ | | `name` | `string` | No | Same as create; updates world name. | | `description` | `string` | No | Same as create; updates description. | | `domainTags` | `string[]` | No | Same as create; updates domain tags. | | `promptSeed` | `string` | No | Same as create; updates prompt seed. | Publish World [#publish-world] ```http POST /api/worlds/:idOrAddress/publish ``` Unpublish World [#unpublish-world] ```http POST /api/worlds/:idOrAddress/unpublish ``` Delete World [#delete-world] ```http DELETE /api/worlds/:idOrAddress ```