{"components":{"schemas":{"PaymentRequired":{"description":"Why an operation could not be paid for, and what would fix it. Returned as a JSON body on 402 over REST, and as the content of a tool error over MCP — the same shape on both.","properties":{"available_credits":{"description":"Cents currently on the balance. ABSENT if the balance could not be read — deliberately omitted rather than reported as 0, which would be indistinguishable from a genuinely empty balance and would tell an agent to top up when it should retry.","type":"integer"},"code":{"enum":["insufficient_credits","search_allowance_exhausted"],"type":"string"},"message":{"type":"string"},"operation":{"type":"string"},"required_credits":{"description":"Cents needed for this operation.","type":"integer"}},"type":"object"},"SearchHit":{"description":"One matching passage. `snippet` is the answer; `asset_id` is the citation.","properties":{"asset_id":{"type":"string"},"kind":{"type":"string"},"lang":{"type":"string"},"modality":{"enum":["audio","image","video","document","text"],"type":"string"},"page":{"type":["integer","null"]},"score":{"type":"number"},"snippet":{"type":"string"},"tenant_id":{"type":"string"},"time_end_ms":{"type":["integer","null"]},"time_start_ms":{"description":"Feed straight back to POST /download to fetch just this moment.","type":["integer","null"]}},"type":"object"},"SearchResponse":{"properties":{"facets":{"type":["object","null"]},"has_more":{"type":"boolean"},"limit":{"type":"integer"},"mode":{"type":"string"},"offset":{"type":"integer"},"results":{"items":{"$ref":"#/components/schemas/SearchHit"},"type":"array"},"search_id":{"type":"string"},"total":{"type":"integer"},"total_relation":{"description":"`approximate` means hits were removed by an authoritative visibility or moderation re-check after ranking.","enum":["exact","approximate"],"type":"string"}},"type":"object"}},"securitySchemes":{"apiKey":{"description":"A workspace agent key, sent as `x-api-key` or as `Authorization: Bearer`. Required: without one every operation here answers 401. A key sees its own tenant's content plus every listed asset in the marketplace.","in":"header","name":"x-api-key","type":"apiKey"}}},"info":{"description":"Passage-level search over audio, video, images and documents. Search returns the matching text, so most questions are answered without downloading anything. Ranking mode defaults to `hybrid` in this deployment. Worked examples: https://api.ipto.ai/docs/search-recipes.md","summary":"Search private multimodal data and fetch the bytes behind a result.","title":"IPTO agent API","version":"0.1.0"},"openapi":"3.1.0","paths":{"/asset/{id}":{"get":{"description":"Returns passage text with timecodes and page numbers. Not billed — this is the cheap way to read a document end to end, with no download.","operationId":"getAsset","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Asset and passages."},"404":{"description":"No such asset in scope."}},"summary":"One asset and every passage extracted from it."}},"/assets":{"get":{"description":"Use `/search` to *find* something; use this to enumerate everything. Returns `{assets, next}` — pass the fields of `next` back as `after_uploaded_at`/`after_asset_id` to continue, or stop when it is null. A cursor costs the same on the thousandth page as on the first, unlike an offset. Not billed. There is no public asset catalogue: without a tenant key this returns nothing.","operationId":"listAssets","parameters":[{"description":"1-100, default 50.","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Only this media type.","in":"query","name":"modality","required":false,"schema":{"type":"string"}},{"description":"Only assets in this state: indexed (searchable), pending, review (held for content review), rejected, failed.","in":"query","name":"status","required":false,"schema":{"type":"string"}},{"description":"Cursor, from the previous response's next.","in":"query","name":"after_uploaded_at","required":false,"schema":{"type":"integer"}},{"description":"Cursor, from the previous response's next.","in":"query","name":"after_asset_id","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of assets and the cursor for the next one."},"401":{"description":"A key is required."}},"summary":"Walk this workspace's own catalogue, newest first."}},"/dl/{token}":{"get":{"description":"Keyless — the token is the authorisation. Single use.","operationId":"fetchDownload","parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The media."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRequired"}}},"description":"Not enough credit to pay for these bytes. The link is NOT consumed — top up and GET the same URL again."},"403":{"description":"Invalid or expired token."},"410":{"description":"Already used; mint another."},"413":{"description":"Larger than the download limit."}},"security":[],"summary":"Fetch the bytes for a minted token."}},"/download":{"post":{"description":"Omit the range for the whole file, billed in full. Pass start_ms/end_ms from a search hit for just that moment of audio or video, billed only for the clip. The returned URL needs no key, works once, and expires.","operationId":"requestDownload","requestBody":{"content":{"application/json":{"schema":{"properties":{"asset_id":{"type":"string"},"end_ms":{"type":"integer"},"search_id":{"description":"From a search response, for attribution.","type":"string"},"start_ms":{"description":"Audio and video only.","type":"integer"}},"required":["asset_id"],"type":"object"}}},"required":true},"responses":{"200":{"description":"An absolute single-use URL, its TTL, and the price: `estimated_credits` for this fetch and your `available_credits`. Minting is free; the credits are spent when the URL is fetched."},"401":{"description":"Downloads require a key."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRequired"}}},"description":"Out of credit, or the plan's monthly search allowance is spent. The body carries `code`, `required_credits` and `available_credits`."},"404":{"description":"Not found, not visible in this scope, withdrawn from sale, or moderated. Deliberately indistinguishable: a private asset must not be confirmable by probing."}},"summary":"Mint a one-time URL for an asset's bytes."}},"/mcp":{"post":{"description":"Tools: search, get_asset, list_assets, request_download. Schemas at /mcp/tools.json. Protocol versions 2024-11-05 through 2026-07-28 are negotiated from what the client offers. Stateless: no session has to be kept alive. Notification-only posts are answered 202 with an empty body. Payment failures arrive as tool errors, not as statuses, so they never close the transport.","operationId":"mcp","responses":{"200":{"description":"A JSON-RPC result."},"202":{"description":"A notification was accepted. No body."},"401":{"description":"Missing or unknown key."}},"summary":"The same operations over MCP (Streamable HTTP)."}},"/search":{"get":{"description":"Billed as one metered search. The returned `snippet` is the passage text, so reading an answer costs nothing further.\n\nThe query has two grammars. With no `*` and no `~`: booleans (AND/OR/NOT), `+must`, `-exclude`, parentheses, \"exact phrases\" and `field:value`. With a `*` or `~` present it switches to a term grammar over the text fields — prefix (`term*`), fuzzy (`term~`, `term~2`), phrases and per-term `+`/`-` — where AND/OR/NOT are ordinary words and `field:value` is not a clause. The structured parameters below work in both, and are the reliable way to narrow a wildcard or fuzzy search.\n\nA query with no special syntax that returns nothing is retried once with typo tolerance; a query written with deliberate syntax is run as written.","operationId":"search","parameters":[{"description":"The query. Max 2048 characters. Called `query` on MCP.","in":"query","name":"q","required":true,"schema":{"type":"string"}},{"description":"keyword | semantic | hybrid.","in":"query","name":"mode","required":false,"schema":{"type":"string"}},{"description":"audio | image | video | document | text.","in":"query","name":"modality","required":false,"schema":{"type":"string"}},{"description":"Passage kind: transcript, caption, ocr, doc_chunk, user_text, metadata.","in":"query","name":"kind","required":false,"schema":{"type":"string"}},{"description":"Language code, e.g. en.","in":"query","name":"lang","required":false,"schema":{"type":"string"}},{"description":"upload | gdrive.","in":"query","name":"source","required":false,"schema":{"type":"string"}},{"description":"Only passages created at or after this unix second.","in":"query","name":"after","required":false,"schema":{"type":"integer"}},{"description":"Only passages created at or before this unix second.","in":"query","name":"before","required":false,"schema":{"type":"integer"}},{"description":"relevance (default) | recent.","in":"query","name":"sort","required":false,"schema":{"type":"string"}},{"description":"1-100, default 10.","in":"query","name":"limit","required":false,"schema":{"type":"integer"}},{"description":"Pagination offset, max 10000.","in":"query","name":"offset","required":false,"schema":{"type":"integer"}},{"description":"Wrap matched terms in the snippet with **…**.","in":"query","name":"highlight","required":false,"schema":{"type":"boolean"}},{"description":"Include approximate modality and kind counts.","in":"query","name":"facets","required":false,"schema":{"type":"boolean"}},{"description":"Idempotency key. Reuse ONLY when retrying the same search, so it is billed once. Also accepted as the `x-request-id` header, which wins if both are sent.","in":"query","name":"request_id","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}},"description":"Ranked passages."},"400":{"description":"Malformed query or out-of-range pagination."},"401":{"description":"Missing or unknown key, and public search is disabled."},"429":{"description":"Over this key's per-minute rate."},"503":{"description":"Search is saturated; retry with backoff."}},"summary":"Search the passage index."}}},"security":[{"apiKey":[]}],"servers":[{"url":"https://api.ipto.ai"}]}