{"openapi":"3.1.0","info":{"title":"LetBe.ai Public API","version":"1.0.0","summary":"Run hosted AI scenarios (flows) from your own code.","description":"LetBe.ai hosts AI scenarios you build visually in the portal. Publish a scenario, then drive a whole conversation with one endpoint: POST /api/v1/chat/next. Every response returns an opaque `state` string; send it back on the next call. Audio and file inputs use the multipart variants of the same endpoint.\n\n**Authentication.** Every `/api/v1` endpoint except this document takes an account-owned API key: `Authorization: Bearer lb_...`. Keys are created self-serve on the portal's Developer Access page (https://portal.letbe.ai/developer). Apps where each end user connects their own LetBe.ai account use OAuth 2.0 authorization code with PKCE (S256); the token exchange returns an API key scoped to the user's grant. Server metadata: https://letbe.ai/.well-known/oauth-authorization-server.\n\n**Errors.** Every error is JSON: `{ \"code\": string, \"message\": string, \"hint\"?: string, \"docs\"?: string }`. `code` is stable and safe to branch on; `message` is for humans. Out-of-funds is HTTP 402 with `walletLimit: true`. Unknown routes are HTTP 404 with `code: not_found`.\n\n**Rate limits.** Upload and delete endpoints are throttled per client IP and answer with the IETF `RateLimit` and `RateLimit-Policy` headers on every response; when exhausted they return HTTP 429 with `Retry-After` (seconds). Other endpoints are limited by the account's prepaid wallet rather than by request rate. Abusive traffic may also be answered at the edge with 429 or 403; always honor `Retry-After` when present.\n\n**Billing.** Usage is billed to the wallet of the account that owns the API key, per model and processing type. 1 USD = 1,000 credits. New accounts start with free credits, so an agent can complete the whole onboarding without human contact.\n\nAI assistants can also connect over the Model Context Protocol at `https://letbe.ai/api/mcp` (see `x-mcp`): the same scenarios, documents and wallet as tools, with sign-in through this OAuth server.","termsOfService":"https://letbe.ai/terms","contact":{"name":"LetBe.ai support","email":"support@letbe.ai","url":"https://letbe.ai/contact"}},"externalDocs":{"description":"Developer documentation","url":"https://letbe.ai/docs/"},"servers":[{"url":"https://letbe.ai/api","description":"Production"}],"tags":[{"name":"Flows","description":"Run published scenarios step by step."},{"name":"Knowledge","description":"Manage the retrieval collections and files a scenario answers from."},{"name":"OAuth","description":"Connect an end user's LetBe.ai account to your app."},{"name":"Meta","description":"Discovery and health."}],"security":[{"ApiKeyAuth":[]}],"paths":{"/v1/openapi.json":{"get":{"tags":["Meta"],"operationId":"getOpenApiSpec","summary":"This OpenAPI description","description":"Returns this document. No authentication. Also published statically at https://letbe.ai/openapi.json.","security":[],"responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/v1/ping":{"get":{"tags":["Meta"],"operationId":"ping","summary":"Verify an API key","description":"Cheapest call that exercises authentication. Returns `{ data: { ok: true } }` when the key is valid. Deliberately echoes no account identifiers.","responses":{"200":{"description":"The key is valid.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","const":true}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/blocks":{"get":{"tags":["Meta"],"operationId":"listBlockTypes","summary":"List the block types a scenario may contain","description":"Every block type with its ports, the plan it needs to run, and the JSON Schema of its settings. Save refuses any block whose type is not listed here or whose settings do not match its schema. Public: no key needed.","security":[],"responses":{"200":{"description":"The block types.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/BlockType"}}}}}}}}}},"/v1/blocks/{type}":{"get":{"tags":["Meta"],"operationId":"getBlockType","summary":"One block type","description":"One block type by its id, with the JSON Schema of its settings. Public: no key needed.","security":[],"parameters":[{"name":"type","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The block type.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/BlockType"}}}}}},"404":{"description":"No such block type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/chat/next":{"post":{"tags":["Flows"],"operationId":"runFlowStep","summary":"Run the next step of a published scenario","description":"Drives a conversation with a published scenario (flow). Call it with an empty `state` to start; every response carries a new opaque `state` to send back on the following call together with the user's next `userInput`. `expectingUserInput` tells you whether the scenario is waiting for input, `expectingInputType` what kind, and `done` whether the run has finished. Alternatively pass `conversationId` and let the platform keep the state for you.\n\nHTTP 201 is returned on every successful step (not only the first).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowStepRequest"}}}},"responses":{"201":{"$ref":"#/components/responses/FlowStep"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/WalletLimit"},"404":{"$ref":"#/components/responses/FlowNotFound"}}}},"/v1/chat/next/audio":{"post":{"tags":["Flows"],"operationId":"runFlowStepWithAudio","summary":"Run the next step with spoken input","description":"Same contract as `runFlowStep`, but the user's input is an audio file (multipart field `audio`). The platform transcribes it and continues the flow in one call, so a scenario step that expects speech needs no separate speech-to-text request. Use `windowSeconds` (1-120, default 60) to tell the platform how long the clip is when you stream fixed windows.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/FlowStepAudioRequest"}}}},"responses":{"201":{"$ref":"#/components/responses/FlowStep"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/WalletLimit"},"404":{"$ref":"#/components/responses/FlowNotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"}}}},"/v1/chat/next/file":{"post":{"tags":["Flows","Knowledge"],"operationId":"runFlowStepWithFile","summary":"Run the next step with a document attached","description":"Same contract as `runFlowStep`, with a document in the multipart field `file`. The scenario decides what happens to it: a RAG Ingest block validates it and queues it for retrieval indexing (poll `getRagFile` for status), an OCR block extracts its text immediately. There is deliberately no standalone upload endpoint, so every upload is billed and attributed to a scenario run.\n\nThrottled per client IP (see `RateLimit` headers).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/FlowStepFileRequest"}}}},"responses":{"201":{"$ref":"#/components/responses/FlowStep"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/WalletLimit"},"404":{"$ref":"#/components/responses/FlowNotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/rag/collections":{"get":{"tags":["Knowledge"],"operationId":"listRagCollections","summary":"List retrieval collections","description":"Returns the retrieval collections owned by the API key's account. A collection groups the documents a scenario's RAG Search block answers from.","responses":{"200":{"description":"Collections owned by this account.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RagCollection"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/rag/collections/{id}":{"delete":{"tags":["Knowledge"],"operationId":"deleteRagCollection","summary":"Delete a retrieval collection","description":"Deletes a collection and every file and chunk in it. Only collections owned by the API key's account are visible; others answer 404. Throttled per client IP (see `RateLimit` headers).","parameters":[{"$ref":"#/components/parameters/CollectionId"}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/v1/rag/collections/{id}/files":{"get":{"tags":["Knowledge"],"operationId":"listRagCollectionFiles","summary":"List files in a collection","description":"Returns every file in the collection with its ingestion status.","parameters":[{"$ref":"#/components/parameters/CollectionId"}],"responses":{"200":{"description":"Files in the collection.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RagFile"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/rag/files/{id}":{"get":{"tags":["Knowledge"],"operationId":"getRagFile","summary":"Get a file's ingestion status","description":"Poll this after `runFlowStepWithFile` until `status` is `ready` (or `failed`, with `errorCode` and `errorMessage`). New files start as `pending`.","parameters":[{"$ref":"#/components/parameters/FileId"}],"responses":{"200":{"description":"The file and its status.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/RagFile"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Knowledge"],"operationId":"deleteRagFile","summary":"Delete a file from its collection","description":"Removes the file and its chunks. Throttled per client IP (see `RateLimit` headers).","parameters":[{"$ref":"#/components/parameters/FileId"}],"responses":{"204":{"description":"Deleted."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/oauth/scopes":{"get":{"tags":["OAuth"],"operationId":"listOAuthScopes","summary":"List OAuth scopes","description":"The scopes an app may request when a user connects their LetBe.ai account. Scopes marked `public: false` are reserved for first-party apps.","security":[],"responses":{"200":{"description":"Scope definitions.","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OAuthScope"}}}}}}}}}},"/oauth/token":{"post":{"tags":["OAuth"],"operationId":"exchangeOAuthCode","summary":"Exchange an authorization code for an API key","description":"Second leg of the OAuth 2.0 authorization code flow. Send the user to `https://portal.letbe.ai/oauth/authorize` with `client_id`, `redirect_uri`, `scope`, `state`, `code_challenge` and `code_challenge_method=S256`; the portal redirects back with `code`. Exchange it here within 5 minutes. Public clients prove possession with `code_verifier` (PKCE); confidential clients may send `client_secret` instead.\n\nThe credential issued is a LetBe.ai API key limited to the granted scopes. It is returned both as the standard `access_token` (`token_type: Bearer`) and, for existing integrations, inside `data.apiKey`. Use it as `Authorization: Bearer ...` on every `/api/v1` call. The user can revoke it at any time from the portal.\n\nAlso accepts `grant_type=urn:ietf:params:oauth:grant-type:device_code` with `device_code` from `POST /oauth/device` (RFC 8628); while the person has not clicked Allow yet the answer is 400 with `error: authorization_pending`.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/OAuthTokenRequest"}}}},"responses":{"200":{"description":"The code was valid; an API key was issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"Unsupported grant type, bad or expired code, PKCE mismatch, or unknown client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The account has reached its limit of connected apps or API keys.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/oauth/register":{"post":{"tags":["OAuth"],"operationId":"registerOAuthClient","summary":"Register an OAuth client automatically (dynamic client registration)","description":"RFC 7591 dynamic client registration, used by AI assistants and MCP hosts before their first sign-in. No authentication. Only public clients (PKCE, `token_endpoint_auth_method: none`) are accepted, redirect URIs must be https, loopback http or an app scheme, and the scopes are capped to the ones an assistant may hold. The record is not owned by any user; the user consents to each connection on the authorization page and can disconnect it from the portal at any time.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthRegisterRequest"}}}},"responses":{"201":{"description":"The client was registered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthRegisterResponse"}}}},"400":{"description":"Invalid metadata or redirect URI (RFC 7591 error body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthRegisterError"}}}},"429":{"description":"Too many registrations from this address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/oauth/device":{"post":{"tags":["OAuth"],"operationId":"startDeviceAuthorization","summary":"Start a device authorization (for AI assistants)","description":"RFC 8628 device authorization grant. No authentication. Returns a short code and a link; the person opens the link (they sign in if needed) and clicks Allow. The caller then polls `POST /oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code` and the `device_code` every `interval` seconds until it receives the key (an assistant-only key that works on the MCP server), or `expired_token` / `access_denied`. Meant for terminal agents that can make HTTP calls but not open a browser callback.","security":[],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthDeviceRequest"}}}},"responses":{"200":{"description":"Codes issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthDeviceResponse"}}}},"400":{"description":"Unsupported scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthRegisterError"}}}},"429":{"description":"Too many attempts from this address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"lb_ API key","description":"Account-owned API key created on https://portal.letbe.ai/developer, or issued by the OAuth token exchange. Keys start with `lb_`."},"OAuth2":{"type":"oauth2","description":"Authorization code with PKCE (S256 only). The token exchange returns an API key that is then used as a Bearer token.","flows":{"authorizationCode":{"authorizationUrl":"https://portal.letbe.ai/oauth/authorize","tokenUrl":"https://letbe.ai/api/oauth/token","scopes":{"api_key:create":"Receive an app-specific API key after user authorization.","flows:run":"Execute published flow endpoints with the issued API key.","wallet:read":"Read the remaining wallet balance for display and preflight checks.","scenarios:read":"List and open the user's scenarios and the published ones.","scenarios:write":"Create scenarios and change the ones the user owns. Never deletes.","scenarios:publish":"Publish and unpublish the user's scenarios.","rag:read":"List and search the user's uploaded documents.","rag:write":"Add documents to the user's collections."}}}}},"parameters":{"CollectionId":{"name":"id","in":"path","required":true,"description":"Collection id (UUID).","schema":{"type":"string","format":"uuid"}},"FileId":{"name":"id","in":"path","required":true,"description":"File id (UUID).","schema":{"type":"string","format":"uuid"}}},"headers":{"RateLimit":{"description":"IETF RateLimit header (draft-7): `limit=<n>, remaining=<n>, reset=<seconds>`.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"IETF RateLimit-Policy header (draft-7): `<limit>;w=<window seconds>`.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":0}}},"responses":{"FlowStep":{"description":"The step ran. Returned with HTTP 201 on every successful step.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlowStepResponse"}}}},"BadRequest":{"description":"Missing or invalid input, or the scenario step failed. `code` identifies the cause (for example `v1_1` when `flowId` is missing, `v1_img` for a bad image, `invalid_json` for an unparseable body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed (`api2`), or invalid/expired (`api3`) API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"WalletLimit":{"description":"The account's prepaid wallet cannot cover this step. Top up at https://portal.letbe.ai/wallet and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletLimitError"}}}},"FlowNotFound":{"description":"No published flow with that id, and none owned by this account (`v1_2`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Nothing with that id is visible to this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PayloadTooLarge":{"description":"The upload exceeds the size limit (audio 15 MB, documents 25 MB by default).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooManyRequests":{"description":"Per-IP throttle exhausted (`rl_limit`). Wait `Retry-After` seconds.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","description":"Every error response uses this envelope.","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable identifier. Branch on this, not on `message`."},"message":{"type":"string","description":"Human-readable explanation."},"hint":{"type":"string","description":"What to do next, when the platform can tell."},"docs":{"type":"string","format":"uri","description":"Where the API is described."}},"examples":[{"code":"api1","message":"API key required. Include Authorization header: \"Bearer <api-key>\""},{"code":"not_found","message":"No API route matches GET /api/v1/nope","hint":"Check the path and method against the OpenAPI description. Public endpoints live under /api/v1.","docs":"https://letbe.ai/openapi.json"}]},"WalletLimitError":{"type":"object","required":["walletLimit","message"],"properties":{"walletLimit":{"type":"boolean","const":true},"message":{"type":"string"},"ctaPath":{"type":"string","description":"Portal path where the account owner can add funds.","examples":["/wallet"]},"ctaLabel":{"type":"string","examples":["Add funds"]}}},"FlowStepRequest":{"type":"object","required":["flowId"],"properties":{"flowId":{"type":"string","description":"Id of a published scenario, or of a private scenario owned by the API key's account."},"state":{"type":"string","description":"Opaque state string from the previous response. Omit or send empty to start a new run."},"userInput":{"type":"string","description":"The user's input for this step, when the scenario expects one."},"poll":{"type":"boolean","description":"true while the previous response carried `pending` (a Video block is making a clip): asks whether the clip is ready without sending a message. Send the same `state` or `conversationId`. Poll every 5 seconds until the response has no `pending`."},"keepAlive":{"type":"boolean","description":"true for answers that may take more than a minute to write (a long text). The response then starts at once with status 200 and stays open until the JSON is ready; a failure arrives in the body as `error` (with its `status`) instead of an HTTP error status."},"conversationId":{"type":"string","description":"Server-managed memory: send the id from a previous response instead of `state` and the platform keeps the conversation. A stale id starts a fresh conversation."},"vars":{"type":"object","description":"Client variables the scenario can read (string values).","additionalProperties":{"type":"string"}},"images":{"type":"array","maxItems":4,"description":"Up to 4 pictures of what the caller is looking at, as `data:image/(jpeg|png|webp);base64,...` URLs of at most 1.5 MB each. Passed to the AI Model block when the model can see; never stored in state.","items":{"type":"string","pattern":"^data:image/(jpeg|png|webp);base64,"}}}},"FlowStepAudioRequest":{"type":"object","required":["flowId","audio"],"properties":{"flowId":{"type":"string"},"audio":{"type":"string","format":"binary","description":"Audio clip in any common container (webm is the default when no content type is sent). Max 15 MB by default."},"state":{"type":"string","description":"Opaque state string from the previous response."},"userInput":{"type":"string","description":"Optional text context passed alongside the transcript."},"windowSeconds":{"type":"integer","minimum":1,"maximum":120,"default":60,"description":"Length of the clip in seconds when streaming fixed windows."},"conversationId":{"type":"string"},"vars":{"type":"string","description":"JSON object of client variables, serialized as a string (multipart)."}}},"FlowStepFileRequest":{"type":"object","required":["flowId","file"],"properties":{"flowId":{"type":"string"},"file":{"type":"string","format":"binary","description":"Document for a RAG Ingest block (PDF, DOCX, TXT, MD, HTML) or the input an OCR block is configured to accept. Max 25 MB by default."},"state":{"type":"string"},"userInput":{"type":"string"},"conversationId":{"type":"string"},"vars":{"type":"string","description":"JSON object of client variables, serialized as a string (multipart)."}}},"FlowMessage":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","description":"`assistant`, `user`, or `system`."},"content":{"type":"string"}},"additionalProperties":true},"FlowStepResponse":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["messages","state","expectingUserInput","done","creditsUsed"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/FlowMessage"},"description":"Messages produced by this step, in order."},"images":{"type":"array","items":{"type":"object","required":["dataUrl","mimeType","width","height","model"],"properties":{"nodeId":{"type":"string","description":"The Text-to-Image block that drew it."},"dataUrl":{"type":"string","description":"The picture itself, as a data URL (base64). It is not stored anywhere: keep it if you need it."},"mimeType":{"type":"string"},"width":{"type":"integer"},"height":{"type":"integer"},"model":{"type":"string","description":"The image model key that drew it."}}},"description":"Pictures drawn by Text-to-Image blocks in this step. Absent when the step drew none. They ride this response only and never appear in messages or state."},"videos":{"type":"array","items":{"type":"object","required":["url","mimeType","seconds","model","expiresAt"],"properties":{"nodeId":{"type":"string","description":"The Video block that made it."},"jobId":{"type":"string"},"url":{"type":"string","description":"A signed link to the clip. It works for 7 days, in any player; add `&download=1` for a file download."},"mimeType":{"type":"string"},"seconds":{"type":"integer"},"width":{"type":"integer"},"height":{"type":"integer"},"model":{"type":"string","description":"The video model key that made it."},"expiresAt":{"type":"string","format":"date-time","description":"After this the link and the clip are gone."}}},"description":"Clips made by Video blocks in this step. Absent when the step made none."},"pending":{"type":"object","required":["kind","jobId","etaSeconds","startedAt"],"properties":{"kind":{"type":"string","enum":["video"]},"jobId":{"type":"string"},"nodeId":{"type":"string"},"model":{"type":"string"},"seconds":{"type":"integer","description":"Length of the clip being made."},"etaSeconds":{"type":"integer","description":"Expected wait, from measured runs of this model."},"startedAt":{"type":"string","format":"date-time"}},"description":"Present while a Video block is making a clip. Show a loader with the estimate and call this endpoint again with `poll: true` and the same `state` every 5 seconds; the response that has no `pending` carries `videos` and the messages that followed."},"choices":{"type":"array","items":{"type":"string"},"description":"Buttons the waiting User Input block offers, for example [\"Generate\", \"Change something\"]. Send the label as `userInput`; free text is accepted too."},"state":{"type":"string","description":"Opaque state. Send it back on the next call."},"stateToken":{"type":"string","description":"Deprecated alias of `state`."},"conversationId":{"type":["string","null"],"description":"Server-managed conversation id, when the platform is keeping state for you."},"expectingUserInput":{"type":"boolean","description":"True when the scenario is waiting for the user's next input."},"expectingInputType":{"type":["string","null"],"enum":["text","audio","file",null],"description":"What kind of input is expected next."},"done":{"type":"boolean","description":"True when the run has finished; the state cannot be continued."},"creditSnap":{"type":["object","null"],"description":"Snapshot of the account's remaining credits.","additionalProperties":true},"creditsUsed":{"type":"number","description":"What this step cost, in credits (1,000 credits = 1 USD)."}}}}},"RagCollection":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"embeddingModel":{"type":"string"},"chunkSizeDefault":{"type":"integer"},"chunkOverlapDefault":{"type":"integer"},"fileCount":{"type":"integer"},"totalSizeBytes":{"type":"integer"},"chunkCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}},"RagFile":{"type":"object","required":["id","collectionId","name","status"],"properties":{"id":{"type":"string","format":"uuid"},"collectionId":{"type":"string","format":"uuid"},"name":{"type":"string"},"ext":{"type":"string"},"sizeBytes":{"type":"integer"},"status":{"type":"string","enum":["pending","processing","ready","failed"],"description":"Ingestion status."},"errorCode":{"type":["string","null"]},"errorMessage":{"type":["string","null"]},"chunkCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"processedAt":{"type":["string","null"],"format":"date-time"}}},"OAuthScope":{"type":"object","required":["scope","label","description","public","risk"],"properties":{"scope":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"public":{"type":"boolean","description":"False for scopes reserved to first-party apps."},"risk":{"type":"string","enum":["low","medium","high"]}}},"OAuthTokenRequest":{"type":"object","required":["grant_type","client_id","redirect_uri","code"],"properties":{"grant_type":{"type":"string","enum":["authorization_code"]},"client_id":{"type":"string"},"redirect_uri":{"type":"string","format":"uri","description":"Must match the redirect_uri used in the authorization request."},"code":{"type":"string"},"code_verifier":{"type":"string","description":"PKCE verifier (required for public clients)."},"client_secret":{"type":"string","description":"Confidential clients only."}}},"OAuthTokenResponse":{"type":"object","required":["access_token","token_type","scope","data"],"properties":{"access_token":{"type":"string","description":"The issued LetBe.ai API key (`lb_...`)."},"token_type":{"type":"string","const":"Bearer"},"scope":{"type":"string","description":"Space-separated granted scopes."},"data":{"type":"object","description":"Legacy envelope kept for existing integrations.","required":["apiKey","token_type","scope"],"properties":{"apiKey":{"type":"string"},"token_type":{"type":"string"},"tokenType":{"type":"string"},"scope":{"type":"string"},"apiKeyId":{"type":"string"},"client":{"type":"object","additionalProperties":true}}}}},"OAuthRegisterRequest":{"type":"object","required":["redirect_uris"],"properties":{"client_name":{"type":"string","maxLength":80,"description":"Shown to the user on the consent screen."},"redirect_uris":{"type":"array","items":{"type":"string","format":"uri"},"minItems":1,"maxItems":10,"description":"https URLs, loopback http URLs, or app schemes."},"scope":{"type":"string","description":"Space separated. Defaults to every MCP scope."},"token_endpoint_auth_method":{"type":"string","enum":["none"]},"grant_types":{"type":"array","items":{"type":"string","enum":["authorization_code","refresh_token"]}}}},"OAuthRegisterResponse":{"type":"object","properties":{"client_id":{"type":"string"},"client_name":{"type":"string"},"redirect_uris":{"type":"array","items":{"type":"string"}},"grant_types":{"type":"array","items":{"type":"string"}},"response_types":{"type":"array","items":{"type":"string"}},"token_endpoint_auth_method":{"type":"string"},"scope":{"type":"string"},"client_id_issued_at":{"type":"integer"}}},"OAuthRegisterError":{"type":"object","properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"OAuthDeviceResponse":{"type":"object","properties":{"device_code":{"type":"string"},"user_code":{"type":"string"},"verification_uri":{"type":"string"},"verification_uri_complete":{"type":"string"},"expires_in":{"type":"integer"},"interval":{"type":"integer"},"scope":{"type":"string"}}},"OAuthDeviceRequest":{"type":"object","properties":{"scope":{"type":"string","description":"Space separated. Defaults to every assistant scope."}}},"BlockType":{"type":"object","required":["type","version","name","category","nodeType","inputs","outputs","settingsSchema","fields"],"properties":{"type":{"type":"string","description":"The block type id every block in a scenario names as its blockKind."},"version":{"type":"integer"},"name":{"type":"string"},"labelKey":{"type":"string"},"detailKey":{"type":"string"},"category":{"type":"string"},"nodeType":{"type":"string","enum":["user","system","logic"]},"inputs":{"type":"array","items":{"type":"string"}},"outputs":{"type":"array","items":{"type":"string"},"description":"Port names; an edge from this block uses the handle out-<port>."},"minPlan":{"type":["string","null"],"description":"The plan needed to run it live; null means every plan."},"aiBuildable":{"type":"boolean"},"enabled":{"type":"boolean","description":"false: not offered yet. Saved blocks of this type still load; new ones are refused."},"deprecated":{"type":"boolean"},"inPalette":{"type":"boolean","description":"Offered in the portal's block palette."},"settingsSchema":{"type":"object","description":"JSON Schema of the settings this block takes at the node's top level. Anything else is refused on save."},"fields":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string"},"required":{"type":"boolean"},"note":{"type":"string"}}}}}}}},"x-mcp":{"url":"https://letbe.ai/api/mcp","transport":"streamable-http","auth":"OAuth 2.1 with dynamic client registration (RFC 7591) and PKCE, or a Bearer lb_ key","resourceMetadata":"https://letbe.ai/.well-known/oauth-protected-resource/api/mcp","docs":"https://portal.letbe.ai/docs/platform.md","playbook":"https://portal.letbe.ai/docs/build-with-me.md"}}