{"openapi":"3.1.0","info":{"title":"Pouchy Companion API","version":"1.0.0","description":"Embed the Pouchy companion: \"Login with Pouchy\" (OAuth 2.1 Authorization Code + PKCE) and the Cloud Companion Runtime (sessions, world-state, agentic tools, memory, voice). Bearer tokens are opaque pchy_… access tokens (RFC 6750).\n\nVersioning: pin a major version in the URL via the /api/v1 alias (the unversioned /api paths also work). Every response carries the running version in the X-Pouchy-Api-Version header; a retiring endpoint announces an RFC 8594 Deprecation + Sunset header with at least a 90-day runway. See docs/companion-api-versioning.md."},"servers":[{"url":"https://www.pouchy.ai","description":"Production"}],"tags":[{"name":"oauth","description":"Login with Pouchy (Authorization Code + PKCE)"},{"name":"session","description":"Companion sessions + event channels"},{"name":"mcp","description":"Inbound MCP (Pouchy companion as a provider)"}],"components":{"securitySchemes":{"companionToken":{"type":"http","scheme":"bearer","bearerFormat":"pchy_…","description":"Opaque Pouchy access token (scope-gated)."},"firebaseUser":{"type":"http","scheme":"bearer","bearerFormat":"Firebase ID token"}},"schemas":{"TokenResponse":{"type":"object","required":["access_token","token_type"],"properties":{"access_token":{"type":"string","example":"pchy_…"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Access-token lifetime (seconds)."},"refresh_token":{"type":"string","example":"pchyr_…"},"scope":{"type":"string","description":"Space-separated granted scopes."}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"OAuthApp":{"type":"object","properties":{"clientId":{"type":"string","example":"pcap_…"},"name":{"type":"string"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"scopes":{"type":"array","items":{"type":"string"}}}},"Envelope":{"type":"object","description":"The shared control/data-plane envelope.","required":["v","id","ts","type","payload"],"properties":{"v":{"type":"integer","const":1},"id":{"type":"string"},"session":{"type":"string"},"ts":{"type":"integer"},"type":{"type":"string"},"payload":{}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}}},"security":[{"companionToken":[]}],"paths":{"/api/oauth/authorize":{"get":{"tags":["oauth"],"summary":"Validate an authorization request (consent-screen backend)","security":[],"parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string"}},{"name":"scope","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Validated; returns appName + granted scope subset."},"400":{"description":"Invalid client/redirect/scope."}}},"post":{"tags":["oauth"],"summary":"Approve consent and mint a one-time authorization code","security":[{"firebaseUser":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id","redirect_uri","code_challenge"],"properties":{"client_id":{"type":"string"},"redirect_uri":{"type":"string"},"response_type":{"type":"string","enum":["code"]},"code_challenge":{"type":"string"},"code_challenge_method":{"type":"string","enum":["S256"]},"scope":{"type":"array","items":{"type":"string"}},"state":{"type":"string"}}}}}},"responses":{"200":{"description":"{ redirectTo } with code + state."},"400":{"description":"invalid_request"}}}},"/api/oauth/token":{"post":{"tags":["oauth"],"summary":"Exchange an auth code (or refresh token) for an access token","security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","required":["grant_type"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"code":{"type":"string"},"code_verifier":{"type":"string"},"redirect_uri":{"type":"string"},"refresh_token":{"type":"string"},"client_id":{"type":"string"}}}}}},"responses":{"200":{"description":"Token pair.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"description":"OAuth error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/api/oauth/register":{"get":{"tags":["oauth"],"summary":"List the developer's registered clients","security":[{"firebaseUser":[]}],"responses":{"200":{"description":"{ apps: OAuthApp[] }"}}},"post":{"tags":["oauth"],"summary":"Register a public PKCE client","security":[{"firebaseUser":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","redirectUris"],"properties":{"name":{"type":"string"},"redirectUris":{"type":"array","items":{"type":"string","format":"uri"}},"scopes":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthApp"}}}},"400":{"description":"No valid redirect_uri."}}}},"/api/oauth/register/{clientId}":{"delete":{"tags":["oauth"],"summary":"Delete a client the caller owns","security":[{"firebaseUser":[]}],"parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted."},"404":{"description":"Not found."}}}},"/api/companion/session":{"post":{"tags":["session"],"summary":"Start or resume a session (hello handshake)","security":[{"companionToken":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"surface":{"type":"string","example":"game"},"modalities":{"type":"array","items":{"type":"string"}},"tools":{"type":"array","items":{"type":"object"}},"appContext":{"type":"object"}}}}}},"responses":{"200":{"description":"hello.ack { session, grantedScopes, resumeCursor }."}}}},"/api/companion/session/{sessionId}/input":{"post":{"tags":["session"],"summary":"Send a user text turn","security":[{"companionToken":["chat"]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string"},"images":{"type":"array","items":{"type":"string","format":"uri"}}}}}}},"responses":{"200":{"description":"Reply enqueued; observe via the stream."}}}},"/api/companion/session/{sessionId}/context":{"post":{"tags":["session"],"summary":"Push live world-state (CloudEvents envelope)","security":[{"companionToken":["worldstate.write"]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ingested into the session buffer."}}}},"/api/companion/session/{sessionId}/stream":{"get":{"tags":["session"],"summary":"Outbound companion event stream (SSE)","description":"Server-Sent Events of companion.* envelopes. Reconnect with a cursor to resume.","security":[{"companionToken":["events.subscribe"]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"text/event-stream of Envelope objects."}}}},"/api/companion/session/{sessionId}/tool-result":{"post":{"tags":["session"],"summary":"Return the result of a companion.tool_call the app performed","security":[{"companionToken":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Recorded; the turn resumes when all results are in."}}}},"/api/companion/session/{sessionId}/call":{"post":{"tags":["session"],"summary":"Start a realtime voice call (mint WebRTC credentials)","security":[{"companionToken":["call"]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"control.call_ready credentials."}}}},"/api/companion/session/{sessionId}/confirm":{"post":{"tags":["session"],"summary":"Approve/deny a sensitive op from a trusted surface","description":"First-party only — requires the Pouchy user's Firebase ID token, never a third-party companion token.","security":[{"firebaseUser":[]}],"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["confirmId","approve"],"properties":{"confirmId":{"type":"string"},"approve":{"type":"boolean"}}}}}},"responses":{"200":{"description":"{ status: approved | denied }."},"409":{"description":"Already resolved or expired."}}}},"/api/companion/mcp":{"post":{"tags":["mcp"],"summary":"Inbound MCP (JSON-RPC 2.0): companion_chat / recall_memory / remember / notify_world_state","security":[{"companionToken":[]}],"responses":{"200":{"description":"JSON-RPC result."}}}}}}