{"openapi":"3.1.0","info":{"title":"Search API","version":"0.1.0","description":"Global search over the platform's entities, answered from a dedicated search database. The service owns no domain state and no commands: Postgres logical replication feeds each source service's journal into local mirrors, and a fold turns them into one search index — so queries never load the operational primaries. People are the one live lookup (their identifying data is sealed in the CRM journal and can never be indexed elsewhere): person hits come from CRM's `GET /parties` with the caller's own token at query time, degrading to a flagged partial result when CRM is unreachable."},"paths":{"/search":{"get":{"tags":["search"],"operationId":"search.query","parameters":[{"name":"q","in":"query","schema":{"type":"string","minLength":1,"maxLength":120,"description":"case-insensitive substring of the entity's searchable text"},"required":true,"example":"lantern"},{"name":"kinds","in":"query","schema":{"type":"string"},"required":false,"example":"item,product,order"},{"name":"limit","in":"query","schema":{"type":"string"},"required":false,"example":"20"}],"security":[{"bearer":[]}],"responses":{"200":{"description":"SearchResults","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResultsEncoded"},"examples":{"contract":{"summary":"search.query","value":{"hits":[{"id":"itm_01a15b1d-32a8-785a-98eb-d43b3ac4c143","kind":"item","title":"LANTERN-BRASS","subtitle":"Brass camp lantern","sku":"LANTERN-BRASS"},{"id":"prd_01a15b54-21aa-71a5-a874-5967972729f4","kind":"product","title":"Brass camp lantern"},{"id":"ord_01a15b8b-10ac-726b-93de-6280ba50afe9","kind":"order","title":"SO-01042","subtitle":"Hollowbrook Supply Co."}],"total":3}}}}}},"401":{"description":"Unauthorized | TokenAlreadyUsed","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/UnauthorizedEncoded"},{"$ref":"#/components/schemas/TokenAlreadyUsedEncoded"}]}}}},"402":{"description":"SubscriptionRequired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionRequiredEncoded"}}}},"403":{"description":"TenantRequired | ScopeRequired | PlatformAdminRequired","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/TenantRequiredEncoded"},{"$ref":"#/components/schemas/ScopeRequiredEncoded"},{"$ref":"#/components/schemas/PlatformAdminRequiredEncoded"}]}}}},"409":{"description":"MirroredFromSource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MirroredFromSourceEncoded"}}}},"503":{"description":"RequestLogUnavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogUnavailableEncoded"}}}}},"description":"Everything matching `q`, across the platform, of the kinds the caller's scopes reach (hits and `total`): inventory items (SKU, name, barcode), products (title, handle, vendor, variant SKUs), purchase orders (number, reference, supplier), orders (number, reference), companies (name, external id) and people (name, email). Matching is a case-insensitive substring, the same rule every grid's own `q` applies. All but people answer from the service's own search index — folded from local logical-replication mirrors of the source journals, so a search never reads an operational primary and a hit can be at most a fold interval stale. People are the deliberate exception: names and emails are sealed in the CRM journal (right-to-erasure crypto-shredding), so person hits are fetched live from CRM's `GET /parties` with the caller's own token, and when CRM cannot answer the response carries `peopleUnavailable` instead of failing. `kinds` narrows the search; `limit` (default 20, capped at 50) applies per source; `total` counts every match either source reported. The index ranks at most a few hundred candidates per term, the best kind of match first, so a term matching more documents than that counts the ones it ranked and pages the best of those.\n\nRequires `stock:read` or `products:read` or `purchasing:read` or `orders:read` or `catalogs:read` or `companies:read` or `customers:read`.","x-mcp-tool":{"name":"search_query","title":"Search: Search everything","toolset":"search","readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},"x-required-scopes":["stock:read","products:read","purchasing:read","orders:read","catalogs:read","companies:read","customers:read"],"x-example-source":{"origin":"contract"}}},"/replica/status":{"get":{"tags":["replica"],"operationId":"replica.status","parameters":[],"security":[{"bearer":[]}],"responses":{"200":{"description":"SearchStatusReport","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchStatusReport"},"examples":{"contract":{"summary":"replica.status","value":{"warnBytes":268435456,"slots":[{"source":"inventory","slot":"search_inventory_events_sub","active":true,"lagBytes":24576,"stalled":false},{"source":"purchasing","slot":"search_purchasing_events_sub","active":true,"lagBytes":8192,"stalled":false},{"source":"fulfillment","slot":"search_fulfillment_events_sub","active":true,"lagBytes":131072,"stalled":false},{"source":"crm","slot":"search_crm_events_sub","active":true,"lagBytes":0,"stalled":false}],"fold":[{"source":"inventory","tenants":12,"maxFoldedSequence":48211},{"source":"purchasing","tenants":9,"maxFoldedSequence":6120},{"source":"fulfillment","tenants":11,"maxFoldedSequence":30457},{"source":"crm","tenants":12,"maxFoldedSequence":2894}]}}}}}},"401":{"description":"Unauthorized | TokenAlreadyUsed","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/UnauthorizedEncoded"},{"$ref":"#/components/schemas/TokenAlreadyUsedEncoded"}]}}}},"402":{"description":"SubscriptionRequired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionRequiredEncoded"}}}},"403":{"description":"TenantRequired | ScopeRequired | PlatformAdminRequired","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/TenantRequiredEncoded"},{"$ref":"#/components/schemas/ScopeRequiredEncoded"},{"$ref":"#/components/schemas/PlatformAdminRequiredEncoded"}]}}}},"409":{"description":"MirroredFromSource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MirroredFromSourceEncoded"}}}},"503":{"description":"RequestLogUnavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogUnavailableEncoded"}}}}},"description":"Replication-slot health for search's read replica, one entry per configured source journal (inventory, purchasing, fulfillment, crm). Each entry reports the slot name, whether a walsender is attached (`active`), and `lagBytes` — the WAL the source database is retaining for this consumer, measured on the source as `pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)`. `stalled` is true when the slot is inactive, missing, unreachable, or lagging past `warnBytes` (`REPLICA_LAG_WARN_BYTES`, default 256 MiB). This matters because a stalled slot retains WAL on the *source* until its disk fills — check here (or the periodic `search.replica` log line) before that happens. `fold` reports the index fold behind the mirrors: one entry per source with the highest folded journal sequence across tenants and how many tenants have one. Liveness is deliberately separate: `/healthz` stays 200 while this payload carries the warning. Empty `slots` means search is running without a replica (memory fallback). Requires the `admin` job. Break-glass for a permanently dead consumer: `SELECT pg_drop_replication_slot('<slot>')` on the source, then re-establish the subscription. Appends nothing: a read of facts already journalled.\n\nRequires `activity:read`.","x-mcp-withheld":"Replication lag between the journals and this service's read model: an operator's health signal, not merchant data.","x-required-scopes":["activity:read"],"x-example-source":{"origin":"contract"}}}},"components":{"schemas":{"SearchKind":{"type":"string","enum":["item","product","purchase-order","order","company","person"],"description":"What kind of entity a search hit is, and what a query may narrow to: item, product, purchase-order, order, company or person."},"Sku":{"type":"string","minLength":1,"pattern":"^\\s*[A-Z0-9](?:[A-Z0-9 ._/-]*[A-Z0-9._/-])?\\s*$","description":"A-Z 0-9 space - _ . / (trimmed, uppercased)"},"HistoryStanding":{"type":"string","enum":["mirrored","now-live","historical"],"description":"Where an order from before Manifold stands: `mirrored` while its source still works it, `now-live` once adopted as a Manifold order at go live, `historical` once it ended"},"SearchHitEncoded":{"type":"object","properties":{"id":{"type":"string","minLength":1},"kind":{"$ref":"#/components/schemas/SearchKind"},"title":{"type":"string","minLength":1},"subtitle":{"type":"string"},"status":{"type":"string"},"sku":{"$ref":"#/components/schemas/Sku"},"history":{"$ref":"#/components/schemas/HistoryStanding"},"liveOrderId":{"type":"string","minLength":1},"placedAt":{"type":"string"}},"required":["id","kind","title"],"additionalProperties":false},"SearchResultsEncoded":{"type":"object","properties":{"hits":{"type":"array","items":{"$ref":"#/components/schemas/SearchHitEncoded"}},"total":{"type":"integer","minimum":0},"peopleUnavailable":{"type":"string"}},"required":["hits","total"],"additionalProperties":false},"UnauthorizedEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["Unauthorized"]},"reason":{"type":"string"}},"required":["_tag","reason"],"additionalProperties":false},"TokenAlreadyUsedEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["TokenAlreadyUsed"]},"tokenId":{"type":"string"}},"required":["_tag","tokenId"],"additionalProperties":false},"RequestLogUnavailableEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["RequestLogUnavailable"]},"tokenId":{"type":"string"},"reason":{"type":"string"}},"required":["_tag","tokenId","reason"],"additionalProperties":false},"TenantRequiredEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["TenantRequired"]},"userId":{"type":"string"}},"required":["_tag","userId"],"additionalProperties":false},"ScopeRequiredEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["ScopeRequired"]},"required":{"type":"array","items":{"type":"string"}}},"required":["_tag","required"],"additionalProperties":false},"PlatformAdminRequiredEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["PlatformAdminRequired"]},"userId":{"type":"string"}},"required":["_tag","userId"],"additionalProperties":false},"SubscriptionRequiredEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["SubscriptionRequired"]},"status":{"type":"string"}},"required":["_tag","status"],"additionalProperties":false},"MirroredFromSourceEncoded":{"type":"object","properties":{"_tag":{"type":"string","enum":["MirroredFromSource"]},"source":{"type":"string"}},"required":["_tag","source"],"additionalProperties":false},"ReplicaSlotStatus":{"type":"object","properties":{"source":{"type":"string"},"slot":{"type":"string"},"active":{"type":"boolean"},"lagBytes":{"anyOf":[{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},{"type":"null"}]},"stalled":{"type":"boolean"},"reason":{"type":"string"}},"required":["source","slot","active","lagBytes","stalled"],"additionalProperties":false},"SearchFoldStatus":{"type":"object","properties":{"source":{"type":"string"},"tenants":{"type":"integer","minimum":0},"maxFoldedSequence":{"anyOf":[{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},{"type":"null"}]}},"required":["source","tenants","maxFoldedSequence"],"additionalProperties":false},"SearchStatusReport":{"type":"object","properties":{"warnBytes":{"anyOf":[{"type":"number"},{"type":"string","enum":["Infinity","-Infinity","NaN"]}]},"slots":{"type":"array","items":{"$ref":"#/components/schemas/ReplicaSlotStatus"}},"fold":{"type":"array","items":{"$ref":"#/components/schemas/SearchFoldStatus"}}},"required":["warnBytes","slots","fold"],"additionalProperties":false}},"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"A tenant JWT: sign in through the auth service, choose the organization, then `GET /api/auth/token`. Tokens last 15 minutes."}}},"security":[],"tags":[{"name":"search","description":"Global search over the platform's entities."},{"name":"replica","description":"Operational visibility into the logical-replication read replica."}],"servers":[{"url":"https://search.getmanifold.io","description":"Production"}]}