{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://parlane.ai/spec/manifest.schema.json",
  "title": "App Manifest",
  "description": "The app manifest served at the well-known path (see brand.wellKnownPath, e.g. /.well-known/parlane.json) or returned by the MCP manifest resource. Declares identity and which surfaces the backend supports. Every field is optional; the client applies the documented defaults. Surface visibility is driven by the declaration alone — the client hides tabs for undeclared surfaces and does not infer them from MCP capability discovery. The optional `theme` object (D15) skins this app's own screens; when both are present, `theme.colors.accent` takes precedence over the top-level `accent`. See docs/06-integration-kit.md §2.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "version": {
      "type": "integer",
      "minimum": 1,
      "default": 1,
      "description": "Manifest/protocol schema version. Defaults to 1. Bumped only on a breaking protocol change; additive fields (e.g. server-side TTS in 1.1) do not require a bump."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 120,
      "description": "Human-readable display name for the connected app. If omitted, the client uses the host of the connection URL."
    },
    "icon": {
      "type": "string",
      "format": "uri",
      "description": "Absolute https URL to a square app icon (PNG/JPG/SVG). If omitted, the client renders a generated placeholder from the name's initials."
    },
    "accent": {
      "type": "string",
      "pattern": "^#[0-9a-fA-F]{6}$",
      "description": "Six-digit hex accent color (e.g. #5B8DEF) that tints this app's chrome, send button, and dashboard highlights. If omitted, a neutral system accent is used."
    },
    "description": {
      "type": "string",
      "maxLength": 500,
      "description": "Optional one- or two-sentence description shown on the connect/consent screen."
    },
    "theme": {
      "type": "object",
      "additionalProperties": false,
      "description": "Server-driven theming (D15). Skins ONLY this connected app's own screens (its tabs under the app); the app switcher and global chrome keep the client's default look. All fields optional. The client applies the tokens as-is with minimal essential-contrast fallbacks only — the server is responsible for choosing legible, well-contrasted values. Configured here, never in-app.",
      "properties": {
        "appearance": {
          "type": "string",
          "enum": ["auto", "dark", "light"],
          "default": "auto",
          "description": "Preferred appearance for this app's screens. \"auto\" (default) follows the device light/dark setting; \"dark\"/\"light\" pin it regardless of the device setting."
        },
        "colors": {
          "type": "object",
          "additionalProperties": false,
          "description": "Optional palette tokens (six-digit hex). Any token omitted falls back to the client's default for the effective appearance. Set tokens in contrast pairs (a background token with its matching text token); the app applies an essential-contrast fallback for any pair left dangerously illegible, but that is a safety net, not a design tool.",
          "properties": {
            "accent": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Primary accent for this app: send button, highlights, primary buttons. Takes precedence over the top-level `accent` when both are present."
            },
            "background": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Screen background behind this app's content."
            },
            "surface": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Base surface color for cards and grouped content."
            },
            "surfaceRaised": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Elevated surface color for raised or layered elements above `surface`."
            },
            "textPrimary": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Primary text color; must read legibly on `background` and `surface`."
            },
            "textSecondary": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Secondary/subdued text color for captions, subtitles, and metadata."
            },
            "positive": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Color for positive/success sentiment (e.g. upward metric deltas)."
            },
            "negative": {
              "type": "string",
              "pattern": "^#[0-9a-fA-F]{6}$",
              "description": "Color for negative/error sentiment (e.g. downward deltas, destructive emphasis)."
            }
          }
        }
      }
    },
    "surfaces": {
      "type": "object",
      "additionalProperties": false,
      "description": "Which surfaces the backend supports. Omit a surface to leave it unadvertised; the client hides surfaces the manifest does not declare. The client does NOT infer surfaces from MCP capability discovery — declare what you serve.",
      "properties": {
        "chat": {
          "type": "object",
          "additionalProperties": false,
          "description": "Conversational chat surface (streaming text, markdown, tool activity, attachments).",
          "properties": {
            "endpoint": {
              "type": "string",
              "enum": ["mcp", "rest"],
              "default": "mcp",
              "description": "Transport for chat. \"mcp\" routes through the MCP chat tool; \"rest\" uses POST /chat with an SSE response. Defaults to mcp."
            },
            "attachments": {
              "type": "boolean",
              "default": false,
              "description": "Whether the chat accepts attachments (images/text/URLs, 10 MB cap). Defaults to false."
            }
          }
        },
        "voice": {
          "type": "object",
          "additionalProperties": false,
          "description": "Voice conversation surface. v1 is a push-to-talk loop with on-device STT and TTS; v1.1 adds an optional server-side TTS contract (D14).",
          "properties": {
            "mode": {
              "type": "string",
              "enum": ["ptt"],
              "default": "ptt",
              "description": "Voice interaction mode. v1 supports only \"ptt\" (push-to-talk / tap-to-toggle). Defaults to ptt."
            },
            "stt": {
              "type": "string",
              "enum": ["device"],
              "default": "device",
              "description": "Speech-to-text location. v1 supports only on-device capture (\"device\"). Defaults to device."
            },
            "tts": {
              "description": "Text-to-speech source. \"device\" (default) uses the platform synthesizer. An object with mode \"server\" (v1.1, D14) makes the client POST reply text to the given endpoint and play the returned audio, falling back to device TTS on error or timeout.",
              "oneOf": [
                {
                  "type": "string",
                  "enum": ["device"],
                  "description": "On-device system TTS. Zero setup; the default."
                },
                {
                  "type": "object",
                  "additionalProperties": false,
                  "description": "Server-side TTS contract (v1.1, D14). Generic text-in / audio-out; Voicebox is the flagship documented recipe, never a hard dependency.",
                  "required": ["mode", "endpoint"],
                  "properties": {
                    "mode": {
                      "type": "string",
                      "enum": ["server"],
                      "description": "Selects the server-side TTS path."
                    },
                    "endpoint": {
                      "type": "string",
                      "description": "Path or absolute URL the client POSTs reply text to; the endpoint returns audio (wav or mp3, chunked transfer permitted for long replies)."
                    },
                    "format": {
                      "type": "string",
                      "enum": ["wav", "mp3"],
                      "default": "mp3",
                      "description": "Audio encoding the endpoint returns. Defaults to mp3."
                    }
                  }
                }
              ]
            }
          }
        },
        "dashboards": {
          "type": "array",
          "description": "Declared interactive dashboards. Each entry names a UI document the client can fetch and render.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["id", "title", "source"],
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^[A-Za-z0-9_-]+$",
                "description": "Stable dashboard identifier (used in deep links, refresh targets, and GET /ui/{id})."
              },
              "title": {
                "type": "string",
                "description": "Human-readable dashboard title shown in the dashboard tab strip."
              },
              "source": {
                "type": "string",
                "description": "Where the UI document lives: an MCP resource URI (parlane://ui/{id} — prefix from brand.mcpResourcePrefix), a REST path (/ui/{id}), or an absolute URL."
              },
              "refresh": {
                "type": "object",
                "additionalProperties": false,
                "description": "Optional auto-refresh hint. Absent means fetch-once + pull-to-refresh only.",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": ["manual", "poll", "push"],
                    "default": "manual",
                    "description": "manual = pull-to-refresh only; poll = client re-fetches on an interval; push = server streams updates over SSE/MCP."
                  },
                  "intervalSeconds": {
                    "type": "integer",
                    "minimum": 2,
                    "description": "Poll interval in seconds when mode is \"poll\"."
                  }
                }
              }
            }
          }
        },
        "push": {
          "type": "object",
          "additionalProperties": false,
          "description": "Push notification surface. Requires the relay (see architecture §2.2).",
          "properties": {
            "relay": {
              "type": "boolean",
              "default": false,
              "description": "Whether this app delivers push via the relay. Defaults to false."
            }
          }
        },
        "shortcuts": {
          "type": "array",
          "description": "Siri / App Intents shortcut phrases mapped to backend tools.",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": ["phrase", "tool"],
            "properties": {
              "phrase": {
                "type": "string",
                "description": "Spoken/typed phrase surfaced as an App Intent, e.g. \"check the house\"."
              },
              "tool": {
                "type": "string",
                "description": "Backend tool name invoked when the phrase fires."
              }
            }
          }
        },
        "share": {
          "type": "object",
          "additionalProperties": false,
          "required": ["accepts", "tool"],
          "description": "Share-sheet target. Declares what content this app ingests and the tool that receives it.",
          "properties": {
            "accepts": {
              "type": "array",
              "minItems": 1,
              "uniqueItems": true,
              "items": {
                "type": "string",
                "enum": ["url", "text", "image"]
              },
              "description": "Content kinds accepted from the share sheet (v1: url, text, image)."
            },
            "tool": {
              "type": "string",
              "description": "Backend tool name that receives shared content."
            }
          }
        }
      }
    }
  }
}
