# Change your preferences Source: https://docs.pomerado.ai/api-reference/account/change-your-preferences /api-reference/openapi.json patch /v1/account/preferences Sets where the questions of jobs you start go: question_delivery sets your default for this account, and connection sets the connection making this call (its OAuth app or API key), which wins over the default. > [accordion] Details > > build\_emails turns build emails on or off for you. Leave a field out to keep it. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records. > * **Effect:** Creates or changes something. > * **Errors:** only the [errors any request can get](/errors). # Get your account Source: https://docs.pomerado.ai/api-reference/account/get-your-account /api-reference/openapi.json get /v1/account Returns your account: its type and plan, your role and permissions, and which features it has. > [accordion] Details > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Get your preferences Source: https://docs.pomerado.ai/api-reference/account/get-your-preferences /api-reference/openapi.json get /v1/account/preferences Returns where the questions of jobs you start go: back to the calling agent, or to you on a form. > [accordion] Details > > Your default covers the account; the connection making this call may have its own setting, which wins. Also whether your Dashboard builds email you when they need you. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Set your name Source: https://docs.pomerado.ai/api-reference/account/set-your-name /api-reference/openapi.json put /v1/account/name Sets your own first and last name, which your account, your team and Pomerado show. > [accordion] Details > > It changes only yours. Repeating the same call answers the same; after temporarily\_unavailable, repeat it. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records. > * **Effect:** Creates or changes something. > * **Errors:** only the [errors any request can get](/errors). # Create an API key Source: https://docs.pomerado.ai/api-reference/api_keys/create-an-api-key /api-reference/openapi.json post /v1/api-keys Creates an API key and returns its secret once. > [accordion] Details > > Send the secret as a Bearer token to the REST API or any Pomerado MCP. > > * A key carries only permissions you hold. It gets api\_keys:manage, billing:manage or team:manage only when permissions names them. > > * It expires after 90 days unless expires\_in\_days says otherwise. > > * With deliver "dashboard", nothing is created yet: this returns a Dashboard link where the person creates the key and sees the secret, so it never reaches this client. > > * With purpose "connect", for the Connect page: it revokes your older connect keys that nothing copied (mark\_copied) and no request used, for the same integration (or for the account) beyond three, and at your key limit the oldest of them, so these keys never fill your limit. Until then the key is not listed and it expires after 1 day; its first copy or first use extends it to 90 days from then. A copied or used key is never revoked this way, and you can create at most 20 connect keys an hour. > > * At your key limit, any new key first revokes your oldest connect key that nothing copied and no request used. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** any signed-in member, for their own records; an API key also needs `api_keys:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`permission_not_held`](/errors#permission_not_held), [`not_found`](/errors#not_found), [`api_key_limit_reached`](/errors#api_key_limit_reached), [`api_key_create_throttled`](/errors#api_key_create_throttled), and the [errors any request can get](/errors). # List API keys Source: https://docs.pomerado.ai/api-reference/api_keys/list-api-keys /api-reference/openapi.json get /v1/api-keys Lists your API keys, newest first. > [accordion] Details > > Never returns a secret. A connect key appears once something carrying it was copied (mark\_copied) or a request used it. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `api_keys:manage`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Mark an API key copied Source: https://docs.pomerado.ai/api-reference/api_keys/mark-an-api-key-copied /api-reference/openapi.json post /v1/api-keys/{id}/copied Records that something carrying the key's secret was copied, such as a Connect setup. > [accordion] Details > > A connect key is then yours: it is listed, and a newer connect key never revokes it to make room. Only the first mark changes the key: a connect key then lives 90 days from it, never less than it had. A key that has expired or been revoked is not found. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `api_keys:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Revoke an API key Source: https://docs.pomerado.ai/api-reference/api_keys/revoke-an-api-key /api-reference/openapi.json delete /v1/api-keys/{id} Revokes an API key at once, so requests with it are refused and the jobs it started stop. > [accordion] Details > > An API key can revoke itself or a key within its own permissions. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `api_keys:manage`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`permission_not_held`](/errors#permission_not_held), and the [errors any request can get](/errors). # Rotate an API key Source: https://docs.pomerado.ai/api-reference/api_keys/rotate-an-api-key /api-reference/openapi.json post /v1/api-keys/{id}/rotate Replaces an API key's secret and returns the new one once. > [accordion] Details > > * The old secret stops working at once. The key keeps its ID, permissions, expiry and running jobs. > > * You can rotate only a key whose permissions you hold. > > * With deliver "dashboard", nothing changes yet: this returns a Dashboard link where the person rotates the key and sees the new secret. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** any signed-in member, for their own records; an API key also needs `api_keys:manage`. > > * **Effect:** Removes or replaces something, and can't be undone. > > * **Errors:** [`not_found`](/errors#not_found), [`permission_not_held`](/errors#permission_not_held), and the [errors any request can get](/errors). # List your team's audit log Source: https://docs.pomerado.ai/api-reference/audit/list-your-teams-audit-log /api-reference/openapi.json get /v1/audit-events Lists changes to your team, newest first: who made each one and what it changed. > [accordion] Details > > * Owners and Admins only. An API key needs team:manage. > > * Kinds: team members and invitations, login access, API keys created or revoked, and tools published or deleted. > > * Filter by kind and time range. > > * Events stay listed for 90 days. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `team:manage`; an API key also needs `team:manage`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`role_permission_missing`](/errors#role_permission_missing), and the [errors any request can get](/errors). # Get your subscription Source: https://docs.pomerado.ai/api-reference/billing/get-your-subscription /api-reference/openapi.json get /v1/billing Returns your Personal account's plan and Premium subscription, any free Premium period from a redeemed code (promotion), and what checkout and the billing portal offer. > [accordion] Details > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `billing:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`billing_unavailable`](/errors#billing_unavailable), and the [errors any request can get](/errors). # Open the billing portal Source: https://docs.pomerado.ai/api-reference/billing/open-the-billing-portal /api-reference/openapi.json post /v1/billing/portal Returns a Stripe billing portal link where the person manages their Premium subscription, payment method and invoices. > [accordion] Details > > An account that never started a checkout has no portal and gets not\_found. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `billing:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`billing_unavailable`](/errors#billing_unavailable), [`not_found`](/errors#not_found), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Redeem a free Premium code Source: https://docs.pomerado.ai/api-reference/billing/redeem-a-free-premium-code /api-reference/openapi.json post /v1/billing/redeem Redeems a code for free Premium on your Personal account, such as 6 months of Premium with no card. > [accordion] Details > > An account already paying through Stripe gets the free months after its paid period, with nothing charged; Stripe bills again by itself afterwards. A code is single use, and an account gets each offer once; repeating your own redemption answers the same. An unknown, used, revoked or expired code, or an offer the account already had, is invalid\_code; five of those in an hour wait an hour (redeem\_throttled). While a free period runs another code is promotion\_already\_active and stays unused. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `billing:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`billing_unavailable`](/errors#billing_unavailable), [`invalid_code`](/errors#invalid_code), [`promotion_already_active`](/errors#promotion_already_active), [`billing_attention_needed`](/errors#billing_attention_needed), [`redeem_throttled`](/errors#redeem_throttled), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Start a Premium checkout Source: https://docs.pomerado.ai/api-reference/billing/start-a-premium-checkout /api-reference/openapi.json post /v1/billing/checkout Returns a Stripe Checkout link where the person subscribes their Personal account to Premium, monthly or yearly. > [accordion] Details > > * Premium starts once Stripe confirms payment. > > * An account already on Premium gets its Settings page instead, and one with a subscription gets the billing portal. > > * An open checkout for the other interval is kept unless replace\_existing is true; the link is then a Settings page that asks to replace it. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** any signed-in member, for their own records; an API key also needs `billing:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`billing_unavailable`](/errors#billing_unavailable), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Build a tool Source: https://docs.pomerado.ai/api-reference/builds/build-a-tool /api-reference/openapi.json post /v1/builds Build a new tool for a task on a website and return its job. > [accordion] Details > > * A build runs your example once. The tool is reused after that. > > * Follow the job with GET /v1/jobs/\{id} or job webhooks. > > * If an existing tool already covers the task, the job names it as tool\_id and build.existing\_tool\_id. > > * Ticket, bank, government and link shortener sites are refused with site\_not\_supported. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`idempotency_conflict`](/errors#idempotency_conflict), [`quota_exceeded`](/errors#quota_exceeded), [`quota_expired`](/errors#quota_expired), [`acceptance_unknown`](/errors#acceptance_unknown), [`not_found`](/errors#not_found), [`save_forbidden`](/errors#save_forbidden), [`account_selection_required`](/errors#account_selection_required), [`saved_login_conflict`](/errors#saved_login_conflict), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`not_configured`](/errors#not_configured), [`service_account_not_allowed`](/errors#service_account_not_allowed), [`service_account_cap_reached`](/errors#service_account_cap_reached), [`effect_required`](/errors#effect_required), [`site_not_supported`](/errors#site_not_supported), [`concurrent_build_limit_reached`](/errors#concurrent_build_limit_reached), [`file_too_large`](/errors#file_too_large), [`file_type_refused`](/errors#file_type_refused), [`file_unavailable`](/errors#file_unavailable), [`file_limit_exceeded`](/errors#file_limit_exceeded), [`business_account_required`](/errors#business_account_required), [`egress_options_not_allowed`](/errors#egress_options_not_allowed), and the [errors any request can get](/errors). # Discard a tool's draft Source: https://docs.pomerado.ai/api-reference/code/discard-a-tools-draft /api-reference/openapi.json delete /v1/code/{id}/draft Delete your team's draft of the code of a tool your team built, with every file it changes. > [accordion] Details > > * Business accounts only, with tools:build. > > * The tool's live code doesn't change. A tool with no draft answers the same. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Removes or replaces something, and can't be undone. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Download a tool's code Source: https://docs.pomerado.ai/api-reference/code/download-a-tools-code /api-reference/openapi.json get /v1/code/{id}/export Download the code a tool your team built runs now, as a zip of its live version's files. > [accordion] Details > > * Business accounts only. > > * content\_base64 holds the zip; decode it and save it as file\_name. > > * It holds your team's files and the files Pomerado added to run them, and nothing else. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get a download link for a tool's code Source: https://docs.pomerado.ai/api-reference/code/get-a-download-link-for-a-tools-code /api-reference/openapi.json post /v1/code/{id}/export-link Get a short-lived link that downloads the code a tool your team built runs now, as a zip of its live version's files, for a coding agent or anything else you give it to. > [accordion] Details > > * Business accounts only. > > * download\_url needs no sign-in. It downloads the zip once, and stops working 30 minutes after it was made, at expires\_at. Until then anyone with the link can download the code, so share it only with whoever should read it. > > * The zip is the one code.export answers. start\_with names the files to read first. > > * Each call makes a new link. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get a tool's code Source: https://docs.pomerado.ai/api-reference/code/get-a-tools-code /api-reference/openapi.json get /v1/code/{id} Get the files of the code a tool your team built runs now, with the tool's versions and your team's draft of it. > [accordion] Details > > * Business accounts only. > > * files holds the live version's files: source team for the files its build wrote, pomerado for the files Pomerado added to run them. > > * draft holds the files your team's draft changes from its base version, or is null when the tool has no draft. > > * A draft copies its base version. When a later version went live, such as a repair, draft.base\_files holds the base version's text of each of your team's files that differs. > > * Another account's tool, a catalog tool your team only added, or an archived tool is not found. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # List your team's code Source: https://docs.pomerado.ai/api-reference/code/list-your-teams-code /api-reference/openapi.json get /v1/code List the integrations your team built, each with the tools your team created in it and the version of its code that runs now. > [accordion] Details > > * Business accounts only. Every member sees all of the team's code. > > * Integrations your team only added from the catalog, and other accounts' tools, are not listed. > > * Each version says what made it and which member asked for it, and each tool how many files your team's draft of it changes. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), and the [errors any request can get](/errors). # Publish a tool's draft Source: https://docs.pomerado.ai/api-reference/code/publish-a-tools-draft /api-reference/openapi.json post /v1/code/{id}/draft/publish Make your team's draft of a tool's code live, as the tool's newest version. > [accordion] Details > > * Business accounts only, with tools:build. Pomerado doesn't review the draft's code, so only an Owner or Admin signed in to the Dashboard publishes it, never an API key or an agent. > > * Send the digest of the draft you saw. It publishes once a run of the draft passed on the live version, for free, and the draft is then gone. > > * The draft's files replace the live version's, a repair's included, with the inputs and outputs its run read. A public tool's draft goes live only when it keeps every input callers send and holds no team member's email address or name, or a credential. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), [`draft_changed`](/errors#draft_changed), [`draft_not_run`](/errors#draft_not_run), [`draft_not_publishable`](/errors#draft_not_publishable), [`draft_changes_inputs`](/errors#draft_changes_inputs), [`draft_holds_owner_data`](/errors#draft_holds_owner_data), and the [errors any request can get](/errors). # Read a run's output Source: https://docs.pomerado.ai/api-reference/code/read-a-runs-output /api-reference/openapi.json get /v1/code/runs/{id}/output Read what a run from the Code editor printed, while it runs and after it ends. > [accordion] Details > > * Business accounts only, with jobs:read. You read the runs whose job you can read with GET /v1/jobs/\{id}, and any other run is not found. > > * Each chunk is the run's stdout (out) or stderr (err). A cut chunk, with no text, means the run printed more than the 4 MiB kept. > > * Send the last seq you read as after. more is true when more was printed already, so read again at once. > > * Output is kept until 60 minutes after the run ends. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), [`run_view_throttled`](/errors#run_view_throttled), and the [errors any request can get](/errors). # Restore an earlier version of a tool Source: https://docs.pomerado.ai/api-reference/code/restore-an-earlier-version-of-a-tool /api-reference/openapi.json post /v1/code/{id}/versions/{number}/restore Make an earlier version of a tool your team built live again, as the tool's newest version. > [accordion] Details > > * Business accounts only, with tools:build. The version's code, inputs and outputs go live at once, for free. Restoring the live version, or the version it restored, changes nothing. > > * A version can be restored when code.get lists it as restorable: not one published before Pomerado kept a record of each version, or one taken down. > > * A public tool restores only a version Pomerado checked that keeps every input its callers send; it may make a required input optional. Only an Owner or Admin signed in to the Dashboard restores a version Pomerado didn't check, never an API key or an agent. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), [`version_not_restorable`](/errors#version_not_restorable), [`restore_changes_inputs`](/errors#restore_changes_inputs), [`restore_unchecked`](/errors#restore_unchecked), and the [errors any request can get](/errors). # Run a tool's code Source: https://docs.pomerado.ai/api-reference/code/run-a-tools-code /api-reference/openapi.json post /v1/code/{id}/runs Run the code of a tool your team built, its draft or its live version, and return the run's job. > [accordion] Details > > * Business accounts only, with tools:build and runs:create. Follow the job with GET /v1/jobs/\{id}. > > * It really runs on the website, so a write tool's run changes it. Send an Idempotency-Key, as with POST /v1/runs. > > * A run that signs in needs Edit access to its login, except a run of the live HTTP version. > > * A run starts no repair and sends no webhooks. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`idempotency_conflict`](/errors#idempotency_conflict), [`quota_exceeded`](/errors#quota_exceeded), [`quota_expired`](/errors#quota_expired), [`acceptance_unknown`](/errors#acceptance_unknown), [`not_found`](/errors#not_found), [`save_forbidden`](/errors#save_forbidden), [`account_selection_required`](/errors#account_selection_required), [`saved_login_conflict`](/errors#saved_login_conflict), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`not_configured`](/errors#not_configured), [`service_account_not_allowed`](/errors#service_account_not_allowed), [`service_account_cap_reached`](/errors#service_account_cap_reached), [`moved_to_team`](/errors#moved_to_team), [`invalid_input`](/errors#invalid_input), [`unsupported_login_field`](/errors#unsupported_login_field), [`site_not_supported`](/errors#site_not_supported), [`file_too_large`](/errors#file_too_large), [`file_type_refused`](/errors#file_type_refused), [`file_unavailable`](/errors#file_unavailable), [`file_limit_exceeded`](/errors#file_limit_exceeded), [`file_fetch_failed`](/errors#file_fetch_failed), [`business_account_required`](/errors#business_account_required), [`egress_options_not_allowed`](/errors#egress_options_not_allowed), and the [errors any request can get](/errors). # Save a file in a tool's draft Source: https://docs.pomerado.ai/api-reference/code/save-a-file-in-a-tools-draft /api-reference/openapi.json put /v1/code/{id}/draft Save one file of your team's draft of the code of a tool your team built. > [accordion] Details > > * Business accounts only, with tools:build. Every member sees the draft with code.get. > > * One draft per tool, shared by the team. Each save changes one file, and the last save of a file wins. > > * content null deletes the file. A file saved back to its text in the draft's base version leaves the draft, and a draft with no change left is deleted. > > * Only files under src/, test/ and explore/ change, and an entrypoint can't be deleted. A public tool's draft stays your team's until it's published. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get MCP client setup Source: https://docs.pomerado.ai/api-reference/connect/get-mcp-client-setup /api-reference/openapi.json get /v1/connect Get the setup for adding an MCP to one client: the command or config, ready to paste. > [accordion] Details > > * Clients: Claude Code, Codex, Cursor, VS Code, Claude Desktop or any MCP client. > > * Without integration\_id, this sets up the Pomerado MCP. With it, that integration's own MCP; one whose MCP is disabled or not hosted is not found. > > * The snippet never carries a secret. With oauth, the default, the client signs in itself; with api\_key, it reads the POMERADO\_API\_KEY environment variable. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`not_found`](/errors#not_found), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Disconnect an app Source: https://docs.pomerado.ai/api-reference/connected_apps/disconnect-an-app /api-reference/openapi.json delete /v1/connected-apps/{id} Disconnects an app you authorized, ending its access to every Pomerado MCP at once and stopping the jobs it started. > [accordion] Details > > It can reconnect only if you authorize it again. An API key can disconnect only an app whose used permissions it holds. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `api_keys:manage`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`permission_not_held`](/errors#permission_not_held), and the [errors any request can get](/errors). # List connected apps Source: https://docs.pomerado.ai/api-reference/connected_apps/list-connected-apps /api-reference/openapi.json get /v1/connected-apps Lists the apps and agents you authorized by signing in (OAuth), such as an MCP client. > [accordion] Details > > Apps that use an API key are not listed; list API keys for those. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records; an API key also needs `api_keys:manage`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Delete a file Source: https://docs.pomerado.ai/api-reference/files/delete-a-file /api-reference/openapi.json delete /v1/files/{id} Erase a file now, before it expires. > [accordion] Details > > A run that took it can no longer place it on the website. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `runs:create`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`sign_in_required`](/errors#sign_in_required), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Get a file Source: https://docs.pomerado.ai/api-reference/files/get-a-file /api-reference/openapi.json get /v1/files/{id} Get a file's name, type, size, SHA-256 and status, without its bytes. > [accordion] Details > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `runs:create`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), [`sign_in_required`](/errors#sign_in_required), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Upload a file Source: https://docs.pomerado.ai/api-reference/files/upload-a-file /api-reference/openapi.json post /v1/files Create a file for a run's input and get a one-time upload URL. > [accordion] Details > > * PUT the bytes to upload\_url, then pass the file's ID as the file field's value in a run's input. > > * A file can be at most 25 MiB unless the deployment sets otherwise. Executables are refused, and the first bytes must match media\_type. > > * A file serves one run. It is erased when that run's job ends, or after an hour if unused. > > * A run's file field also takes an https URL, or a data: URI of at most 512 KB. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `runs:create`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`file_too_large`](/errors#file_too_large), [`file_type_refused`](/errors#file_type_refused), [`sign_in_required`](/errors#sign_in_required), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Add an integration to My integrations Source: https://docs.pomerado.ai/api-reference/integrations/add-an-integration-to-my-integrations /api-reference/openapi.json put /v1/integrations/{id}/added Add a public integration, with all its tools, to My integrations. > [accordion] Details > > * It is a link, not a copy: the integration's tools stay as their creators publish them. > > * On a Business account the whole team sees it. > > * Adding one already added changes nothing. A private or unknown integration is not found. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Delete an integration Source: https://docs.pomerado.ai/api-reference/integrations/delete-an-integration /api-reference/openapi.json delete /v1/integrations/{id}/tools Delete your tools in an integration, and the integration once it is empty. > [accordion] Details > > * A Business Owner or Admin, or a Personal account's owner, deletes every tool their team created there. Anyone else deletes only the tools they created. Other accounts' tools stay. > > * Deleted tools leave every list and stop running, including runs already started. > > * The integration stays while it holds any tool. Once empty, it is removed with its MCP and everything bound to it. > > * This can't be undone. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Removes or replaces something, and can't be undone. > > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get an integration Source: https://docs.pomerado.ai/api-reference/integrations/get-an-integration /api-reference/openapi.json get /v1/integrations/{id} Get one integration: its site, tools, MCP status, MCP URL and server name. > [accordion] Details > > You can read a public integration or one your team's list holds. An integration key reaches only its own. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `tools:read`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get an integration's OpenAPI document Source: https://docs.pomerado.ai/api-reference/integrations/get-an-integrations-openapi-document /api-reference/openapi.json get /v1/integrations/{id}/openapi.json Get an integration's OpenAPI 3.1 document, with one operation per tool. > [accordion] Details > > Each operation runs its tool, with the tool's input schema as the request body and a sample request. Use it to generate a client or import the integration into an API tool. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `tools:read`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get an integration's reference Source: https://docs.pomerado.ai/api-reference/integrations/get-an-integrations-reference /api-reference/openapi.json get /v1/integrations/{id}/reference Get an integration's reference: one Markdown document an agent can read to use it. > [accordion] Details > > * It covers the site, its MCP URLs, how each MCP client connects, and each tool under its MCP name with its effect, login, input and a sample call. > > * You can read a public integration or one your team's list holds. An integration key reaches only its own. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # List integrations Source: https://docs.pomerado.ai/api-reference/integrations/list-integrations /api-reference/openapi.json get /v1/integrations List integrations: each one is a website's tools, published as its own MCP. > [accordion] Details > > By default this lists the public catalog. With mine=true, it lists only my integrations: those holding a tool you created or own, and the public integrations your account added. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `tools:read`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # List your team's integrations Source: https://docs.pomerado.ai/api-reference/integrations/list-your-teams-integrations /api-reference/openapi.json get /v1/integrations/team List the integrations your team built, private and public, and the public ones your team added. > [accordion] Details > > Each built one lists the enabled tools your team created in it; an added one lists all its enabled tools. A Personal account sees its own integrations. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `tools:read`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Remove an added integration Source: https://docs.pomerado.ai/api-reference/integrations/remove-an-added-integration /api-reference/openapi.json delete /v1/integrations/{id}/added Remove a public integration your account added from My integrations. > [accordion] Details > > * The integration and its tools stay; only your account's link goes, for the whole team on Business. > > * Integrations holding a tool you or your team created stay in My integrations. > > * Removing one not added changes nothing. A private or unknown integration is not found. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Set integration visibility Source: https://docs.pomerado.ai/api-reference/integrations/set-integration-visibility /api-reference/openapi.json put /v1/integrations/{id}/visibility Make your tools in an integration public or private. > [accordion] Details > > * A Business team's integrations start private, and the team makes them public or private again. An Owner or Admin changes every tool your team created there; a member changes only their own. Other accounts' tools in a public integration stay public. > > * A Personal account's owner makes public an integration Pomerado kept private, but not private again. Pomerado first checks that no tool there holds your own email address or name, or a credential, and refuses it if one does. > > * A tool that signs in outside its website, which its owner approved, stays private to the owner's account, so an integration holding one isn't made public. > > * The tools may join the site's existing integration of that visibility, so the response names the integration that now holds them. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), [`integration_tool_name_taken`](/errors#integration_tool_name_taken), [`unchecked_version`](/errors#unchecked_version), [`tool_signs_in_outside_site`](/errors#tool_signs_in_outside_site), [`tool_on_bank_site`](/errors#tool_on_bank_site), [`tool_holds_owner_data`](/errors#tool_holds_owner_data), [`integration_too_large_to_check`](/errors#integration_too_large_to_check), [`tool_code_missing`](/errors#tool_code_missing), and the [errors any request can get](/errors). # API reference Source: https://docs.pomerado.ai/api-reference/introduction Base URL, authentication, errors, pagination and idempotency for the Pomerado REST API. The REST API gives programs the same tools, jobs and account operations the Pomerado MCP gives agents. The operation pages that follow are generated from the definitions that serve the API. ## Base URL ```text theme={null} https://api.pomerado.ai/v1 ``` The OpenAPI 3.1 document is public at `GET /v1/openapi.json`, and on this site at [/api-reference/openapi.json](/api-reference/openapi.json). ## Authentication Send an API key or the Pomerado MCP's OAuth access token as a bearer token: ```bash theme={null} curl https://api.pomerado.ai/v1/account \ -H "Authorization: Bearer $POMERADO_API_KEY" ``` Each operation page names the callers it accepts and the permission it needs. A missing, expired or rejected credential answers `401 unauthorized`. See [authentication](/guides/authentication/overview). ## Errors Every error uses the same envelope: ```json theme={null} { "error": { "code": "not_found", "message": "...", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#not_found", "details": {} } } ``` Branch on `code`. When `retryable` is true, sending the request again can succeed. The code's Retry line says whether to keep its idempotency key. A write sent without an `Idempotency-Key` that may already have been accepted also carries `"write_possibly_accepted": true` beside `error`: check the outcome before repeating it. [Errors](/errors) lists every code. ## Pagination Lists take `cursor` and `limit` (1 to 100, default 20) and answer `{"data": [...], "next_cursor": "..."}`. Pass `next_cursor` back as `cursor` until it is `null`. `GET /v1/jobs` takes a `limit` of 1 to 50. `GET /v1/runs` pages by number instead: it takes `page`, from 1, and answers `page`, `limit` and `total` in place of `next_cursor`. ## Idempotency Runs, builds, requested tool changes and team invitations take an `Idempotency-Key` header; other operations ignore it. A key is 1 to 200 letters, digits, `_` or `-`, such as a UUID. The same key with the same body answers the first request's result with `Idempotent-Replayed: true`, without acting again. The same key with a different body answers `409 idempotency_conflict`. Send a key with every write you might retry. See [retries and idempotency](/guides/tools/retries-and-idempotency). ## Conventions * IDs are prefixed by type, such as `job_` for jobs and `int_` for integrations. * Timestamps are ISO 8601 in UTC. * A run, build or requested tool change answers `202` with its job, or `200` with the job an `Idempotency-Key` already started. Other creates answer `201`, and a delete `204` with no body. * An operation that would reveal a secret to a program can answer a `dashboard_url` instead, where a signed-in person finishes it. ## Changes * October 15, 2026: a saved login's `needs_attention` (`GET /v1/logins` and the logins a save answers) may now be `{"reason": "account_locked"}`, with no `field`, when the website said the login's account is locked. Read `field` only when it is present. See [check a login's status](/guides/logins/overview#check-a-logins-status). # Report a problem Source: https://docs.pomerado.ai/api-reference/issue_reports/report-a-problem /api-reference/openapi.json post /v1/issue-reports Tell the Pomerado team about friction Pomerado caused, or something that worked notably well. > [accordion] Details > > * Report each problem once, when it happens: something slow, an unexpected error, a confusing description or answer, a wrong result, or a design you had to work around. Not your own input mistakes, or website problems Pomerado explained. > > * To change one tool, such as adding a filter, input or output, use POST /v1/tools/\{id}/changes instead. > > * Never include passwords, codes, tokens, card numbers, personal details, page content or your users' data. > > * Pomerado attaches who sent the report and from where. It links the related job, tool and integration only when you can read them. > > * The same report from you within a day counts again (status merged). Your account can send 50 reports an hour and 200 a day, across its members and clients; past that it answers report\_limit\_reached, and you carry on without retrying. > > * Reports are kept 90 days and only help the Pomerado team improve Pomerado. They are not a support channel: if it is urgent, the user can email [founders@pomerado.ai](mailto:founders@pomerado.ai) (the receipt's message also gives the Discord invite). Ask the user before sending any email; never email on your own. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** any signed-in member, for their own records. > > * **Effect:** Creates or changes something. > > * **Errors:** [`report_limit_reached`](/errors#report_limit_reached), [`sign_in_required`](/errors#sign_in_required), and the [errors any request can get](/errors). # Answer a job's question Source: https://docs.pomerado.ai/api-reference/jobs/answer-a-jobs-question /api-reference/openapi.json post /v1/jobs/{id}/answers Answer every question in a job's input request at once; the job then continues. > [accordion] Details > > Secrets and website logins sent here pass through your client. To keep them private, give the person the input request's answer\_url instead. Sending the same answers again counts as one answer. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `jobs:read`. > * **Effect:** Creates or changes something. > * **Errors:** [`not_found`](/errors#not_found), [`invalid_answer`](/errors#invalid_answer), [`input_expired`](/errors#input_expired), [`input_not_pending`](/errors#input_not_pending), [`save_forbidden`](/errors#save_forbidden), [`site_login_exists`](/errors#site_login_exists), [`login_identity_conflict`](/errors#login_identity_conflict), [`login_in_use`](/errors#login_in_use), and the [errors any request can get](/errors). # Cancel a job Source: https://docs.pomerado.ai/api-reference/jobs/cancel-a-job /api-reference/openapi.json post /v1/jobs/{id}/cancel Stop a job. > [accordion] Details > > * A website action it already sent may have happened. Its write\_status says whether it did. > > * Don't cancel a run whose maintenance.status is repairing: a cancel ends the task without a result. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:cancel`. > > * **Effect:** Removes or replaces something, and can't be undone. > > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get a build's activity Source: https://docs.pomerado.ai/api-reference/jobs/get-a-builds-activity /api-reference/openapi.json get /v1/jobs/{id}/activity Returns what a build did, one entry per step in time order, each in the stage it happened in: the stages it went through, the first page it opened, its sign-in steps, its steps on the website, its example and test runs, a move to another connection, the questions it asked you and how it ended. > [accordion] Details > > * Business accounts only. You read the activity of a build whose job you can read with GET /v1/jobs/\{id}. > > * An entry never holds what was typed to sign in, a code, an answer, a question's words, a path or a URL. > > * label says what a step was allowed to do on the website: read, write or sign\_in. result says how a step went, when it has one. > > * Entries from the build's records are kept 90 days. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get a job Source: https://docs.pomerado.ai/api-reference/jobs/get-a-job /api-reference/openapi.json get /v1/jobs/{id} Get a run's or build's job: its status, question, result, write status and error. > [accordion] Details > > * Returns at once. While the job is live, Retry-After suggests when to read it again. > > * A result is kept until it is first read, and at most 60 minutes after the job ends, or, for a run whose repair is still confirming the result it kept, at least 60 minutes after that repair ends (never past 24 hours). include\_result=false reads everything else and leaves the result for later. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`not_found`](/errors#not_found), [`result_unavailable`](/errors#result_unavailable), and the [errors any request can get](/errors). # List jobs Source: https://docs.pomerado.ai/api-reference/jobs/list-jobs /api-reference/openapi.json get /v1/jobs List your runs and builds, newest first, each with its status, website, task and the question it asks. > [accordion] Details > > * Lists the jobs you can read with GET /v1/jobs/\{id}: those your API key, app or Dashboard started. The Dashboard lists the ones you started from any client. > > * status filters by one or more statuses, comma-separated, such as status=needs\_input. A filtered page can hold fewer jobs than limit, so follow next\_cursor until it is null. > > * q finds the jobs whose website or site name contains its text. Tasks are kept encrypted for you alone and can't be searched here; search them in the jobs you list. > > * counts totals your jobs of the listed type by state, whatever status, q, cursor and limit asked for; needs\_input\_count repeats counts.needs\_input. > > * A job's result is never listed; read it with GET /v1/jobs/\{id}. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Changes nothing. > > * **Errors:** only the [errors any request can get](/errors). # Resume a paused build Source: https://docs.pomerado.ai/api-reference/jobs/resume-a-paused-build /api-reference/openapi.json post /v1/jobs/{id}/resume Resume a paused build whose pause.resume_by is resume. > [accordion] Details > > It continues as the same job and uses no extra quota. > > * Ask the user first: resuming is their decision. > > * A build paused on a question resumes only when its question is answered (POST /v1/jobs/\{id}/answers). > > * A pause past its pause.expires\_at has ended the build. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:cancel`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), [`not_paused`](/errors#not_paused), [`pause_expired`](/errors#pause_expired), and the [errors any request can get](/errors). # Change login access Source: https://docs.pomerado.ai/api-reference/login_access/change-login-access /api-reference/openapi.json put /v1/logins/{id}/access Gives one member or the whole team access to a Business team's saved login, or removes it. > [accordion] Details > > * A member can get use, edit or manage; the whole team, use or edit. A level of null removes access. > > * No one can change the access of the login's creator, an Owner or an Admin. > > * Removing someone's last access stops their unfinished jobs on the login. > > * Lowering someone below edit stops their unfinished Code editor runs on the login. > > * Only the Dashboard makes the change, for someone who manages the login. Elsewhere this returns a dashboard\_url to the login's Share menu. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `logins:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), [`login_access_required`](/errors#login_access_required), [`team_unavailable`](/errors#team_unavailable), [`access_fixed`](/errors#access_fixed), [`grantee_not_member`](/errors#grantee_not_member), [`team_level_not_allowed`](/errors#team_level_not_allowed), and the [errors any request can get](/errors). # Get login access Source: https://docs.pomerado.ai/api-reference/login_access/get-login-access /api-reference/openapi.json get /v1/logins/{id}/access Returns who may use, edit and manage a Business team's saved login. > [accordion] Details > > Only the Dashboard returns it, to someone who manages the login; elsewhere this returns a dashboard\_url to the login's Share menu. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), [`login_access_required`](/errors#login_access_required), [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # Cancel a pending login Source: https://docs.pomerado.ai/api-reference/logins/cancel-a-pending-login /api-reference/openapi.json delete /v1/logins/pending/{id} Cancels a login save held while the account's secure login storage is set up, and erases its held values. > [accordion] Details > > A save being written when you cancel is waited for, and the login it saved is deleted. A save already saved is left as it is: delete its login with logins.delete. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`login_in_use`](/errors#login_in_use), [`acceptance_unknown`](/errors#acceptance_unknown), and the [errors any request can get](/errors). # Change a saved login Source: https://docs.pomerado.ai/api-reference/logins/change-a-saved-login /api-reference/openapi.json patch /v1/logins/{id} Changes a saved login's fields and returns the changed login. > [accordion] Details > > * A field left out is kept; null clears an optional one. The website and the sign-in type (password or code) never change. > > * primary\_identifier changes the type it signs in with and needs that type's identifier alongside, such as email with primary\_identifier "email". > > * On a password login, password left out keeps the saved one, so changing label, identifiers, primary\_identifier or authenticator\_secret alone needs no password. > > * The values pass through this client and its model provider. Warn the user first, or send only deliver "dashboard" to get the login's Dashboard edit page instead. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `logins:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), [`site_login_exists`](/errors#site_login_exists), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`login_in_use`](/errors#login_in_use), [`login_save_in_progress`](/errors#login_save_in_progress), [`invalid_credential`](/errors#invalid_credential), [`acceptance_unknown`](/errors#acceptance_unknown), and the [errors any request can get](/errors). # Delete a saved login Source: https://docs.pomerado.ai/api-reference/logins/delete-a-saved-login /api-reference/openapi.json delete /v1/logins/{id} Deletes a saved login and erases its stored values. > [accordion] Details > > This cannot be undone. Jobs and saved sessions using the login stop. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`login_in_use`](/errors#login_in_use), [`acceptance_unknown`](/errors#acceptance_unknown), and the [errors any request can get](/errors). # Get a pending login Source: https://docs.pomerado.ai/api-reference/logins/get-a-pending-login /api-reference/openapi.json get /v1/logins/pending/{id} Returns a login save held while the account's secure login storage was set up: pending until storage is ready, then saved (login_id) or failed (error). > [accordion] Details > > Never returns the login's values. Read it again after retry\_after\_seconds while it is pending. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get a saved login Source: https://docs.pomerado.ai/api-reference/logins/get-a-saved-login /api-reference/openapi.json get /v1/logins/{id} Returns one saved login's metadata, never a password or other secret. > [accordion] Details > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:read`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Import saved logins Source: https://docs.pomerado.ai/api-reference/logins/import-saved-logins /api-reference/openapi.json post /v1/logins/import Saves up to 1,000 logins in one request. > [accordion] Details > > * Each login is saved or refused on its own; results lists, by index, the saved login or its error. > > * The values pass through this client and its model provider. Warn the user first, or send only deliver "dashboard" to get the Dashboard import page instead. > > * While the account's secure login storage is first being set up, an import of up to 25 logins answers 202 and holds each login as a pending login (pending\_login) for up to 10 minutes, saved once storage is ready; a larger one answers login\_storage\_pending, to be repeated. Read each with logins.get\_pending. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `logins:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`login_storage_pending`](/errors#login_storage_pending), and the [errors any request can get](/errors). # List pending logins Source: https://docs.pomerado.ai/api-reference/logins/list-pending-logins /api-reference/openapi.json get /v1/logins/pending Lists your login saves held while the account's secure login storage was set up: those still pending and those saved or failed in the last day. > [accordion] Details > > A cancelled save is gone. Never returns a login's values. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # List saved logins Source: https://docs.pomerado.ai/api-reference/logins/list-saved-logins /api-reference/openapi.json get /v1/logins Lists the saved website logins you may use, as metadata only. > [accordion] Details > > Never returns a password or other secret. Pass site\_url to list one website's logins. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:read`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Reveal a saved login Source: https://docs.pomerado.ai/api-reference/logins/reveal-a-saved-login /api-reference/openapi.json post /v1/logins/{id}/reveal Returns every value a saved login holds except its authenticator secret, and how many recovery codes are left. > [accordion] Details > > Only the Dashboard shows the values, behind the Logins PIN. Elsewhere this returns a dashboard\_url to the page that reveals them. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Save a login Source: https://docs.pomerado.ai/api-reference/logins/save-a-login /api-reference/openapi.json post /v1/logins Saves a website login and returns its metadata. > [accordion] Details > > * The values pass through this client and its model provider. Warn the user first, or send deliver "dashboard" with only site\_url and label to get a Dashboard link where the person enters the login instead. > > * A Personal account keeps one login per website, fixed to its identity after the first verified sign-in. A Business account may save several. > > * While the account's secure login storage is first being set up, it answers 202 with a pending login instead: the login is held for up to 10 minutes and saved once storage is ready. Read it with logins.get\_pending until it is saved or failed. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `logins:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`site_not_supported`](/errors#site_not_supported), [`login_storage_pending`](/errors#login_storage_pending), [`site_login_exists`](/errors#site_login_exists), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`login_in_use`](/errors#login_in_use), [`login_save_in_progress`](/errors#login_save_in_progress), [`invalid_credential`](/errors#invalid_credential), [`acceptance_unknown`](/errors#acceptance_unknown), and the [errors any request can get](/errors). # Get your plan limits Source: https://docs.pomerado.ai/api-reference/quota/get-your-plan-limits /api-reference/openapi.json get /v1/quota Returns your plan's limits and current use: tool calls per UTC day and integrations created per UTC month. > [accordion] Details > > A Business account's limits are one pool shared by every member, unlimited unless Pomerado sets one. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `usage:read`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Get a run's log Source: https://docs.pomerado.ai/api-reference/runs/get-a-runs-log /api-reference/openapi.json get /v1/runs/{id}/log Returns one run your team started and its log, one line per event in time order: the start, each worker start, the tool's browser actions (at most 500 per attempt), the site's own requests after its last page load (at most 32 per browser), the result's size, each step of a repair, a stop, a failed run's error and the end. > [accordion] Details > > * The start line names who started the run: the member's name or email, and the API key's name. > > * A failed run's run.error line holds the error jobs.get reports for the run: its code; its message, worded for the API, or for MCP when the log is read over MCP; retryable; and for a write, website, what the run did to the website. It never holds the error's details. > > * A run.stopped line says when the run stopped after Pomerado was told to stop it, and why when recorded, such as cancelled when it was cancelled with jobs.cancel, or when Pomerado stopped the account's unfinished runs to convert it to a Business account; stopped\_by\_pomerado when Pomerado's staff stopped a run that wasn't ending on its own, which still ends cancelled; authority\_revoked when the access it ran with ended, such as a revoked API key, OAuth grant or session, a removed teammate, a deleted login, or an archived or deleted tool or integration; account\_deleted when its account was deleted; and vault\_deactivated when its account's vault was turned off. > > * A run.repair line is one step of a repair of the run's tool, which the run asked for or Pomerado scheduled after the run was interrupted: opened; published, with version, the publish time of the version the repair made, when the log knows it (a repair shared with other runs may have none); incomplete, when the repair ended without a fix, with reason rerun\_on\_revision when Pomerado queued the run again on a newer version instead, superseded when it couldn't, or caller\_released when the run's caller was already released, so the repair ended without placing the write; result\_recovered, when the repair finished the run's task; and released, when the run stopped waiting for its repair at its deadline. Once a run queued again after a repair starts again, its log lists only a later repair's lines, and its worker start lines show each attempt's version. > > * An action line names the Playwright action, its target and, when known, the site it ran on, and a goto line the page it opened. It never holds a value the tool typed, filled, pressed or selected, and text in its target reads as …. Runs from before action lines list the tool's browser steps instead. > > * A request line never holds a query, matrix parameters, headers, cookies or body. In its path, a credential Pomerado recognizes reads as \[secret withheld], a card number as \[card withheld], and other personal data as a tag such as \[EMAIL#7c21e0b4]. An action's target and a goto line's path read the same way. > > * A log is kept 90 days. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # List the runs your team started Source: https://docs.pomerado.ai/api-reference/runs/list-the-runs-your-team-started /api-reference/openapi.json get /v1/runs Lists the runs your team started, newest first, one numbered page at a time, with the totals of every page. > [accordion] Details > > A run is listed when a team member started it, in the Dashboard, through an agent or with an API key, on any tool: the team's own or a public one. > > * Every member sees every run the team started, whoever started it. > > * Filter by integration, status, source and time range, or find one run by its ID. > > * Runs stay listed for 90 days, even after their tool or integration is deleted. > > * To follow a run you just started, use jobs.get with its job ID. Use this list to look back over the team's runs. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`business_account_required`](/errors#business_account_required), and the [errors any request can get](/errors). # Run a read-only tool Source: https://docs.pomerado.ai/api-reference/runs/run-a-read-only-tool /api-reference/openapi.json post /v1/runs/read-only Run a read-only website tool once and return its job. > [accordion] Details > > It takes the same body as POST /v1/runs and changes nothing on the website, so you can send several at once. A tool that changes the website is refused with tool\_not\_read\_only before any job starts; run it with POST /v1/runs. Follow the job with GET /v1/jobs/\{id} or job webhooks. A result over 256 KiB comes as result\_file, a single-use result.json download, with result\_preview showing its start. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `runs:create`. > * **Effect:** Changes nothing. > * **Errors:** [`idempotency_conflict`](/errors#idempotency_conflict), [`quota_exceeded`](/errors#quota_exceeded), [`quota_expired`](/errors#quota_expired), [`acceptance_unknown`](/errors#acceptance_unknown), [`not_found`](/errors#not_found), [`save_forbidden`](/errors#save_forbidden), [`account_selection_required`](/errors#account_selection_required), [`saved_login_conflict`](/errors#saved_login_conflict), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`not_configured`](/errors#not_configured), [`service_account_not_allowed`](/errors#service_account_not_allowed), [`service_account_cap_reached`](/errors#service_account_cap_reached), [`invalid_input`](/errors#invalid_input), [`unsupported_login_field`](/errors#unsupported_login_field), [`site_not_supported`](/errors#site_not_supported), [`file_too_large`](/errors#file_too_large), [`file_type_refused`](/errors#file_type_refused), [`file_unavailable`](/errors#file_unavailable), [`file_limit_exceeded`](/errors#file_limit_exceeded), [`file_fetch_failed`](/errors#file_fetch_failed), [`tool_not_read_only`](/errors#tool_not_read_only), [`business_account_required`](/errors#business_account_required), [`egress_options_not_allowed`](/errors#egress_options_not_allowed), and the [errors any request can get](/errors). # Run a tool Source: https://docs.pomerado.ai/api-reference/runs/run-a-tool /api-reference/openapi.json post /v1/runs Run a website tool once and return its job. > [accordion] Details > > * Follow the job with GET /v1/jobs/\{id} or job webhooks until it finishes or needs input. Retry-After suggests when to read it again. > > * A tool that signs in uses the site's saved login, and login\_id picks one when there are several. login is used only when no login is saved. > > * Runs on one saved login go one at a time. > > * Send an Idempotency-Key with every write. A retry with the same key returns the same job instead of acting on the website again. > > * A result over 256 KiB comes as result\_file, a single-use result.json download, with result\_preview showing its start, and result is null. A result over 32 MiB fails the run with result\_too\_large. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `runs:create`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`idempotency_conflict`](/errors#idempotency_conflict), [`quota_exceeded`](/errors#quota_exceeded), [`quota_expired`](/errors#quota_expired), [`acceptance_unknown`](/errors#acceptance_unknown), [`not_found`](/errors#not_found), [`save_forbidden`](/errors#save_forbidden), [`account_selection_required`](/errors#account_selection_required), [`saved_login_conflict`](/errors#saved_login_conflict), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`not_configured`](/errors#not_configured), [`service_account_not_allowed`](/errors#service_account_not_allowed), [`service_account_cap_reached`](/errors#service_account_cap_reached), [`invalid_input`](/errors#invalid_input), [`unsupported_login_field`](/errors#unsupported_login_field), [`site_not_supported`](/errors#site_not_supported), [`file_too_large`](/errors#file_too_large), [`file_type_refused`](/errors#file_type_refused), [`file_unavailable`](/errors#file_unavailable), [`file_limit_exceeded`](/errors#file_limit_exceeded), [`file_fetch_failed`](/errors#file_fetch_failed), [`business_account_required`](/errors#business_account_required), [`egress_options_not_allowed`](/errors#egress_options_not_allowed), and the [errors any request can get](/errors). # Run an integration's tool Source: https://docs.pomerado.ai/api-reference/runs/run-an-integrations-tool /api-reference/openapi.json post /v1/integrations/{id}/tools/{tool_id}/runs Run one of an integration's tools and return its job. > [accordion] Details > > It works like POST /v1/runs, with the tool named in the path. An integration key reaches only its own integration's tools. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `runs:create`. > * **Effect:** Creates or changes something. > * **Errors:** [`idempotency_conflict`](/errors#idempotency_conflict), [`quota_exceeded`](/errors#quota_exceeded), [`quota_expired`](/errors#quota_expired), [`acceptance_unknown`](/errors#acceptance_unknown), [`not_found`](/errors#not_found), [`save_forbidden`](/errors#save_forbidden), [`account_selection_required`](/errors#account_selection_required), [`saved_login_conflict`](/errors#saved_login_conflict), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`not_configured`](/errors#not_configured), [`service_account_not_allowed`](/errors#service_account_not_allowed), [`service_account_cap_reached`](/errors#service_account_cap_reached), [`invalid_input`](/errors#invalid_input), [`unsupported_login_field`](/errors#unsupported_login_field), [`site_not_supported`](/errors#site_not_supported), [`file_too_large`](/errors#file_too_large), [`file_type_refused`](/errors#file_type_refused), [`file_unavailable`](/errors#file_unavailable), [`file_limit_exceeded`](/errors#file_limit_exceeded), [`file_fetch_failed`](/errors#file_fetch_failed), [`business_account_required`](/errors#business_account_required), [`egress_options_not_allowed`](/errors#egress_options_not_allowed), and the [errors any request can get](/errors). # Email your IT admin a setup link Source: https://docs.pomerado.ai/api-reference/sso/email-your-it-admin-a-setup-link /api-reference/openapi.json post /v1/team/sso/it-admin-invitations Has WorkOS, Pomerado's sign-in provider, email your team's IT admin one link to verify your domain and set up single sign-on and Directory Sync in its Admin Portal. > [accordion] Details > > Owners only: whoever sets up the directory decides everyone's role. > > * The link works for 30 days and goes only to that address; Pomerado never sees it. Follow their progress with GET /v1/team/sso. > > * A team has one setup email at a time. While an earlier one's link still works this sends nothing and answers setup\_email\_active; send replace: true to replace it, and the earlier link stops working. Replacing also removes every earlier address from your team's WorkOS IT contacts, so only the new one gets WorkOS's notices. > > * A team sends up to 5 setup emails at once, then one every 12 minutes; past that it answers setup\_email\_throttled. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `team:manage`; an API key also needs `team:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`invalid_email`](/errors#invalid_email), [`setup_email_active`](/errors#setup_email_active), [`setup_email_throttled`](/errors#setup_email_throttled), [`team_unavailable`](/errors#team_unavailable), [`role_permission_missing`](/errors#role_permission_missing), [`owner_required`](/errors#owner_required), and the [errors any request can get](/errors). # Get the team's single sign-on setup Source: https://docs.pomerado.ai/api-reference/sso/get-the-teams-single-sign-on-setup /api-reference/openapi.json get /v1/team/sso Returns your Business team's single sign-on setup as WorkOS has it now: its SSO connections (SAML or OIDC, such as Okta), its Directory Sync (SCIM) directories and its domains, each with its state. > [accordion] Details > > Owners and Admins only. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`team_unavailable`](/errors#team_unavailable), [`role_permission_missing`](/errors#role_permission_missing), and the [errors any request can get](/errors). # Open the team's Admin Portal Source: https://docs.pomerado.ai/api-reference/sso/open-the-teams-admin-portal /api-reference/openapi.json post /v1/team/sso/setup-links Opens WorkOS's Admin Portal, where your team's IT admin sets up how your team signs in. > [accordion] Details > > Owners only: whoever sets up the directory decides everyone's role. > > * intent: domain\_verification proves your team owns its email domain, sso connects your identity provider (SAML or OIDC, such as Okta), and dsync syncs your directory's people (SCIM). Set them up in that order: SSO needs a verified domain. > > * Only the Dashboard returns the portal's link, since anyone holding it can change your team's sign-in. Elsewhere this returns a dashboard\_url to the Single sign-on page, where an Owner opens it. To have your IT admin do it, send them a setup email with POST /v1/team/sso/it-admin-invitations. > > * On the Dashboard each call makes a new link, which stops working five minutes after it was made, at expires\_at; Pomerado keeps none. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `team:manage`; an API key also needs `team:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`team_unavailable`](/errors#team_unavailable), [`role_permission_missing`](/errors#role_permission_missing), [`owner_required`](/errors#owner_required), and the [errors any request can get](/errors). # Get build ideas Source: https://docs.pomerado.ai/api-reference/suggestions/get-build-ideas /api-reference/openapi.json get /v1/suggestions Returns ideas for a tool to build: each a website and one task on it, from a reviewed set of popular US websites that Pomerado has no public tool for that task yet and that a Pomerado browser opened without being blocked. > [accordion] Details > > Ideas come from different categories in random order; ask again with exclude for others. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `tools:read`. > * **Effect:** Changes nothing. > * **Errors:** only the [errors any request can get](/errors). # Change a member's role Source: https://docs.pomerado.ai/api-reference/team/change-a-members-role /api-reference/openapi.json patch /v1/team/members/{user_id} Sets a team member's role to owner, admin, member or tool_user. > [accordion] Details > > Admins can't change an owner's role or make anyone an owner, and the team always keeps at least one owner. A team whose identity provider manages it changes roles there. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`not_found`](/errors#not_found), [`role_permission_missing`](/errors#role_permission_missing), [`role_change_forbidden`](/errors#role_change_forbidden), [`role_unavailable`](/errors#role_unavailable), [`last_owner`](/errors#last_owner), [`managed_by_identity_provider`](/errors#managed_by_identity_provider), [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # Change the team's intent setting Source: https://docs.pomerado.ai/api-reference/team/change-the-teams-intent-setting /api-reference/openapi.json patch /v1/team/intent-settings Sets whether Pomerado keeps the intents your team's agents state when they call its MCP tools. > [accordion] Details > > With omit, agents still state an intent, but Pomerado keeps no intent text, clears what it kept and sends none to its product analytics from then on. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`team_unavailable`](/errors#team_unavailable), [`role_permission_missing`](/errors#role_permission_missing), and the [errors any request can get](/errors). # Get team settings Source: https://docs.pomerado.ai/api-reference/team/get-team-settings /api-reference/openapi.json get /v1/team Says whether your identity provider manages your Business team's members and roles, and which domains invitations may go to. > [accordion] Details > > Every member may call it. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records. > * **Effect:** Changes nothing. > * **Errors:** [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # Get the team's intent setting Source: https://docs.pomerado.ai/api-reference/team/get-the-teams-intent-setting /api-reference/openapi.json get /v1/team/intent-settings Returns whether Pomerado keeps the intents your team's agents state when they call its MCP tools: keep (the default) or omit. > [accordion] Details > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`team_unavailable`](/errors#team_unavailable), [`role_permission_missing`](/errors#role_permission_missing), and the [errors any request can get](/errors). # Invite team members Source: https://docs.pomerado.ai/api-reference/team/invite-team-members /api-reference/openapi.json post /v1/team/invitations Invites people to your team by email; each invitation lasts 7 days. > [accordion] Details > > * Send one address as email, or up to 500 as invitations or as a CSV file's text in csv. > > * An address already on the team, in another team or repeated in the request is refused on its own row. One already invited returns its pending invitation. > > * Only an owner can invite an owner. > > * When your organization has verified domains, only addresses at one of them are invited. > > * A team whose identity provider manages it invites people there instead. > > * With an Idempotency-Key, repeating the same request returns the first answer. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `team:manage`; an API key also needs `team:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`invalid_email`](/errors#invalid_email), [`already_member`](/errors#already_member), [`already_invited`](/errors#already_invited), [`in_another_team`](/errors#in_another_team), [`role_not_allowed`](/errors#role_not_allowed), [`role_unavailable`](/errors#role_unavailable), [`domain_not_allowed`](/errors#domain_not_allowed), [`managed_by_identity_provider`](/errors#managed_by_identity_provider), [`role_permission_missing`](/errors#role_permission_missing), [`team_unavailable`](/errors#team_unavailable), [`idempotency_conflict`](/errors#idempotency_conflict), [`acceptance_unknown`](/errors#acceptance_unknown), and the [errors any request can get](/errors). # List invitations Source: https://docs.pomerado.ai/api-reference/team/list-invitations /api-reference/openapi.json get /v1/team/invitations Lists your team's open invitations, newest first: pending ones and expired ones that can be resent. > [accordion] Details > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`role_permission_missing`](/errors#role_permission_missing), [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # List team members Source: https://docs.pomerado.ai/api-reference/team/list-team-members /api-reference/openapi.json get /v1/team/members Lists your Business team's members, oldest first. > [accordion] Details > > Every member may call it. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** any signed-in member, for their own records. > * **Effect:** Changes nothing. > * **Errors:** [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # Remove a team member Source: https://docs.pomerado.ai/api-reference/team/remove-a-team-member /api-reference/openapi.json delete /v1/team/members/{user_id} Removes a member from your team, stopping their running jobs and API keys. > [accordion] Details > > Logins they created stay with the team. Admins can't remove an owner, and the team always keeps at least one owner. A team whose identity provider manages it removes members there. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`role_permission_missing`](/errors#role_permission_missing), [`role_change_forbidden`](/errors#role_change_forbidden), [`last_owner`](/errors#last_owner), [`managed_by_identity_provider`](/errors#managed_by_identity_provider), [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # Resend an invitation Source: https://docs.pomerado.ai/api-reference/team/resend-an-invitation /api-reference/openapi.json post /v1/team/invitations/{id}/resend Emails a pending or expired invitation again. > [accordion] Details > > When your organization has verified domains, only an invitation to an address at one of them is sent again. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`not_found`](/errors#not_found), [`role_permission_missing`](/errors#role_permission_missing), [`role_not_allowed`](/errors#role_not_allowed), [`managed_by_identity_provider`](/errors#managed_by_identity_provider), [`domain_not_allowed`](/errors#domain_not_allowed), [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # Revoke an invitation Source: https://docs.pomerado.ai/api-reference/team/revoke-an-invitation /api-reference/openapi.json delete /v1/team/invitations/{id} Revokes a pending or expired invitation, so its link no longer joins the team. > [accordion] Details > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `team:manage`; an API key also needs `team:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`not_found`](/errors#not_found), [`role_permission_missing`](/errors#role_permission_missing), [`role_not_allowed`](/errors#role_not_allowed), [`team_unavailable`](/errors#team_unavailable), and the [errors any request can get](/errors). # Tools and integrations Source: https://docs.pomerado.ai/api-reference/tools-and-integrations A tool does one task on one site; an integration is a site's tools published as its own MCP server. These operations find and manage tools, read an integration's reference and OpenAPI document, and give an MCP's setup for your client. To run a tool, see [Run tools](/guides/tools/run-tools); to connect an integration's MCP, see [Integration MCPs](/guides/integration-mcps/overview). # Delete a tool Source: https://docs.pomerado.ai/api-reference/tools/delete-a-tool /api-reference/openapi.json delete /v1/tools/{id} Delete one tool permanently, public or private. > [accordion] Details > > * A Business Owner or Admin, or a Personal account's owner, can delete any tool their team created. Anyone else can delete only tools they created; others answer forbidden. > > * The tool leaves every list and stops running, including runs already started. > > * The rest of its integration stays. An integration left with no tools is removed too, with its MCP and everything bound to it. > > * This can't be undone. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Removes or replaces something, and can't be undone. > > * **Errors:** [`not_found`](/errors#not_found), and the [errors any request can get](/errors). # Get a tool Source: https://docs.pomerado.ai/api-reference/tools/get-a-tool /api-reference/openapi.json get /v1/tools/{id} Get one tool: its input and output schemas, effect, sign-in needs and integration. > [accordion] Details > > * A disabled tool, or another account's private tool, is not found. An integration key reaches only its own integration's tools. > > * Without credentials, where allowed, it reads only public tools, and a tool that writes or needs a login answers sign\_in\_required. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Changes nothing. > > * **Errors:** [`not_found`](/errors#not_found), [`sign_in_required`](/errors#sign_in_required), and the [errors any request can get](/errors). # List tools Source: https://docs.pomerado.ai/api-reference/tools/list-tools /api-reference/openapi.json get /v1/tools List or search the tools you can run: public tools and your own private ones. > [accordion] Details > > * Each tool has its input and output schemas, effect, sign-in needs and integration. Disabled tools are never listed. > > * An integration key lists only its integration's tools. > > * Without credentials, where allowed, only public read-only tools that need no login are listed. A notice says that signing up (free) or signing in adds tools that write, saved logins and building tools, and how to do it. > > * **Callers:** an API key, an integration's API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:read`. > > * **Effect:** Changes nothing. > > * **Errors:** only the [errors any request can get](/errors). # Request a change to a tool Source: https://docs.pomerado.ai/api-reference/tools/request-a-change-to-a-tool /api-reference/openapi.json post /v1/tools/{id}/changes Request a change to an existing tool and return its job. > [accordion] Details > > * Ask for what the tool lacks, such as a filter, an input or an output field, instead of building another tool for the same site. Say the change in plain words. > > * A change takes about as long as a build. The tool keeps working meanwhile, and its callers get the change when it publishes. Follow the job with GET /v1/jobs/\{id} or job webhooks. > > * example\_input is the input of the one example the change runs, using any input the change adds. A write tool's change needs it and runs that example once for real. > > * Anyone who can run the tool and build tools can ask. A change from someone other than the tool's owner may only add or tighten what the tool returns, never remove or loosen it. > > * A change keeps every input working: it may add optional inputs, add allowed values or a default to one, or make a required one optional when the tool handles it missing. > > * One change per tool at a time from your account. Another while yours is under way answers change\_pending. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `tools:build`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`idempotency_conflict`](/errors#idempotency_conflict), [`quota_exceeded`](/errors#quota_exceeded), [`quota_expired`](/errors#quota_expired), [`acceptance_unknown`](/errors#acceptance_unknown), [`not_found`](/errors#not_found), [`save_forbidden`](/errors#save_forbidden), [`account_selection_required`](/errors#account_selection_required), [`saved_login_conflict`](/errors#saved_login_conflict), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`not_configured`](/errors#not_configured), [`service_account_not_allowed`](/errors#service_account_not_allowed), [`service_account_cap_reached`](/errors#service_account_cap_reached), [`change_pending`](/errors#change_pending), [`business_account_required`](/errors#business_account_required), [`egress_options_not_allowed`](/errors#egress_options_not_allowed), and the [errors any request can get](/errors). # Assign a phone number Source: https://docs.pomerado.ai/api-reference/two_factor/assign-a-phone-number /api-reference/openapi.json post /v1/logins/{id}/sms-number Gives a saved login your Pomerado phone number to receive its website's text-message sign-in codes, so sign-ins need no one to read them. > [accordion] Details > > * Your first login gets a new number. Later logins reuse it. > > * A login that has a number keeps it, so assigning again returns the same number. > > * Next, the person sets this number as the website account's phone for codes. Then call logins.verify\_sms\_number to check that codes arrive. > > * features.sms\_numbers on your account says whether you may assign one. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `logins:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), [`sms_number_not_allowed`](/errors#sms_number_not_allowed), [`sms_number_not_linked`](/errors#sms_number_not_linked), [`sms_number_cap_reached`](/errors#sms_number_cap_reached), [`sms_number_purchase_failed`](/errors#sms_number_purchase_failed), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Get a phone number Source: https://docs.pomerado.ai/api-reference/two_factor/get-a-phone-number /api-reference/openapi.json get /v1/logins/{id}/sms-number Returns the Pomerado phone number that receives a saved login's text-message sign-in codes. > [accordion] Details > > * During a verification, verification shows whether a code arrived and the newest code. On a Business account only the member whose number it is sees the code. > > * A login without a number returns sms\_number\_not\_linked. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `logins:manage`. > > * **Effect:** Changes nothing. > > * **Errors:** [`not_found`](/errors#not_found), [`sms_number_not_allowed`](/errors#sms_number_not_allowed), [`sms_number_not_linked`](/errors#sms_number_not_linked), and the [errors any request can get](/errors). # Get an authenticator code Source: https://docs.pomerado.ai/api-reference/two_factor/get-an-authenticator-code /api-reference/openapi.json post /v1/logins/{id}/authenticator-code Returns the current code from a saved login's authenticator secret. > [accordion] Details > > Only the Dashboard shows the code, behind the Logins PIN. Elsewhere this returns a dashboard\_url to the page that shows it. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), [`authenticator_not_configured`](/errors#authenticator_not_configured), and the [errors any request can get](/errors). # Remove a phone number Source: https://docs.pomerado.ai/api-reference/two_factor/remove-a-phone-number /api-reference/openapi.json delete /v1/logins/{id}/sms-number Removes a saved login's Pomerado phone number. > [accordion] Details > > Other logins that use the number keep it. Ask the person to remove the number from their website account first: once no login uses it, the number can go to someone else after 45 days. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`sms_number_not_allowed`](/errors#sms_number_not_allowed), [`sms_number_not_linked`](/errors#sms_number_not_linked), and the [errors any request can get](/errors). # Replace recovery codes Source: https://docs.pomerado.ai/api-reference/two_factor/replace-recovery-codes /api-reference/openapi.json put /v1/logins/{id}/recovery-codes Replaces a saved login's two-factor recovery codes. > [accordion] Details > > Saved codes are never shown again, only counted. The codes pass through this client and its model provider, so warn the user first. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `logins:manage`. > * **Effect:** Creates or changes something. > * **Errors:** [`not_found`](/errors#not_found), [`site_login_exists`](/errors#site_login_exists), [`login_identity_conflict`](/errors#login_identity_conflict), [`legacy_duplicate_saved_logins`](/errors#legacy_duplicate_saved_logins), [`saved_login_unsettled`](/errors#saved_login_unsettled), [`login_in_use`](/errors#login_in_use), [`login_save_in_progress`](/errors#login_save_in_progress), [`invalid_credential`](/errors#invalid_credential), [`acceptance_unknown`](/errors#acceptance_unknown), and the [errors any request can get](/errors). # Verify a phone number Source: https://docs.pomerado.ai/api-reference/two_factor/verify-a-phone-number /api-reference/openapi.json post /v1/logins/{id}/sms-number/verify Starts a 10-minute verification of a login's Pomerado phone number. > [accordion] Details > > During it, send the number a test text or have the website send a code, then call logins.get\_sms\_number to read the code that arrived. > > * On a Business account only the member whose number it is may verify it; anyone else gets sms\_number\_not\_yours. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `logins:manage`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), [`sms_number_not_allowed`](/errors#sms_number_not_allowed), [`sms_number_not_linked`](/errors#sms_number_not_linked), [`sms_number_not_yours`](/errors#sms_number_not_yours), [`not_configured`](/errors#not_configured), and the [errors any request can get](/errors). # Get your usage Source: https://docs.pomerado.ai/api-reference/usage/get-your-usage /api-reference/openapi.json get /v1/usage Returns your account's charged tool calls and successful builds in a time range, optionally per integration, per member or as a series. > [accordion] Details > > * A charged call is a tool call that finished with a result, or that refused your input itself, counted when Pomerado accepted it. A call that failed because the tool or the website broke is free, and so is one a repair finished, a write you stopped with your own Stop answer after it saved a step, or a write its tool ended before saving anything with no refusal its output declares. A build counts when it published a tool. > > * With interval, the range also comes as a series of UTC hours, days or months. > > * With group\_by member, an Owner or Admin sees every member, including those who have left; a Member sees only their own row. > > * coverage.complete says whether the whole range lies within recorded history. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `usage:read`. > > * **Effect:** Changes nothing. > > * **Errors:** only the [errors any request can get](/errors). # Create a webhook Source: https://docs.pomerado.ai/api-reference/webhooks/create-a-webhook /api-reference/openapi.json post /v1/webhooks Create a webhook for your jobs' events and return its signing secret once. > [accordion] Details > > * It receives events for every job you started, from any client: the Dashboard, an MCP client or an API key, except Code editor runs. > > * Events: job.needs\_input (a job asks a question; the payload has its questions and the page to answer them), job.input\_expiring (three minutes left to answer), job.succeeded and job.failed (a job settled), job.repairing (a repair of a run's tool is under way, and the run ends by maintenance.deadline\_at), job.paused (a build paused; the payload has its question, if it paused on one, and when the pause ends it), job.pause\_expiring (an hour left before the pause ends the build) and job.resumed (a paused build is queued or running again). Without events, it receives all eight. > > * No event carries a result. Read the result from the job, through the client that started it. > > * Deliveries are signed with Standard Webhooks, retried for about 14 minutes, and sent only to public HTTPS addresses. > > * Only you can change, test or rotate it; a Business Owner or Admin can also see and delete it. It keeps working if you leave the team. > > * You can hold at most 20 webhooks. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`webhook_limit_reached`](/errors#webhook_limit_reached), [`webhooks_unavailable`](/errors#webhooks_unavailable), and the [errors any request can get](/errors). # Delete a webhook Source: https://docs.pomerado.ai/api-reference/webhooks/delete-a-webhook /api-reference/openapi.json delete /v1/webhooks/{id} Delete one of your webhooks, or any member's as a Business Owner or Admin. > [accordion] Details > > Events not yet delivered to it are dropped. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `jobs:read`. > * **Effect:** Removes or replaces something, and can't be undone. > * **Errors:** [`not_found`](/errors#not_found), [`webhooks_unavailable`](/errors#webhooks_unavailable), and the [errors any request can get](/errors). # Get a webhook Source: https://docs.pomerado.ai/api-reference/webhooks/get-a-webhook /api-reference/openapi.json get /v1/webhooks/{id} Get one of your webhooks, or any member's as a Business Owner or Admin. > [accordion] Details > > Its signing secret is never returned. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `jobs:read`. > * **Effect:** Changes nothing. > * **Errors:** [`not_found`](/errors#not_found), [`webhooks_unavailable`](/errors#webhooks_unavailable), and the [errors any request can get](/errors). # List webhooks Source: https://docs.pomerado.ai/api-reference/webhooks/list-webhooks /api-reference/openapi.json get /v1/webhooks List the webhooks you made in this account, newest first. > [accordion] Details > > A Business Owner or Admin sees every member's webhooks, including departed members'. Signing secrets are never returned. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `jobs:read`. > * **Effect:** Changes nothing. > * **Errors:** [`webhooks_unavailable`](/errors#webhooks_unavailable), and the [errors any request can get](/errors). # Rotate a webhook secret Source: https://docs.pomerado.ai/api-reference/webhooks/rotate-a-webhook-secret /api-reference/openapi.json post /v1/webhooks/{id}/rotate-secret Replace one of your webhooks' signing secret and return the new one once. > [accordion] Details > > For 24 hours each delivery is signed with both secrets (two webhook-signature entries), so your receiver can switch without missing an event. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > * **Permission:** `jobs:read`. > * **Effect:** Creates or changes something. > * **Errors:** [`not_found`](/errors#not_found), [`webhooks_unavailable`](/errors#webhooks_unavailable), and the [errors any request can get](/errors). # Test a webhook Source: https://docs.pomerado.ai/api-reference/webhooks/test-a-webhook /api-reference/openapi.json post /v1/webhooks/{id}/test Send a signed test event to one of your webhooks and report how its receiver responded. > [accordion] Details > > * It is sent now, whether the webhook is active or not. > > * The event is \{"id", "type": "webhook.test", "created\_at", "data": \{"webhook\_id"}}. > > * It is not retried and does not count toward turning the webhook off. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), [`webhook_secret_unreadable`](/errors#webhook_secret_unreadable), [`webhooks_unavailable`](/errors#webhooks_unavailable), and the [errors any request can get](/errors). # Update a webhook Source: https://docs.pomerado.ai/api-reference/webhooks/update-a-webhook /api-reference/openapi.json patch /v1/webhooks/{id} Change a webhook's URL or events, or turn it off or on. > [accordion] Details > > * Only its creator can change it; another member's is forbidden. > > * A webhook turns itself off when its receiver answers 410 or fails 20 deliveries in a row. Setting status to active turns it back on and clears its failures. > > * **Callers:** an API key, an MCP OAuth token or a Dashboard session. > > * **Permission:** `jobs:read`. > > * **Effect:** Creates or changes something. > > * **Errors:** [`not_found`](/errors#not_found), [`webhooks_unavailable`](/errors#webhooks_unavailable), and the [errors any request can get](/errors). # Errors Source: https://docs.pomerado.ai/errors The error envelope every REST, Dashboard and MCP error shares, and every error code. Every error from the REST API, the Dashboard and MCP tools has the same shape: ```json theme={null} { "error": { "code": "invalid_input", "message": "The input does not match this tool's input schema, so no job was started. issues names each field's path and what it expects, with the allowed values when the schema lists them. When the schema has one shape per choice, such as per product, issues describe the shape the input's own choice picks. Fix the input and call again.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#invalid_input", "details": { "issues": [{ "path": "/departure_date", "message": "must be string" }] } } } ``` * `code` is one of the codes below. Codes are stable; branch on `code`, never on `message`. * `message` says what happened and what to do. It names the API's routes on REST and the MCP tools on MCP. * `retryable` is `true` when sending the request again can succeed later. The code's Retry line says whether to keep its idempotency key or use a new one. * `docs_url` links to the code's section on this page. * `details` carries the fields listed for the code, and is otherwise empty. * `details.reason_code`, where a code lists it, names the cause more precisely. New values are added over time: read a value you don't recognize as the code alone. `details.explanation`, where present, is a short explanation already screened for you. An MCP tool error carries the same object as its result text, plus `retry_after_seconds` when it is retryable. A write sent without an idempotency key that fails with a code that may follow an accepted write instead has `retryable: false`, and `write_possibly_accepted: true` beside `error`: check the outcome first (the job, or the saved logins with GET /v1/logins) before repeating it, because a repeat would act again. That covers a run of a write tool or a build that may write, sent without its key (the `Idempotency-Key` header, or `idempotency_key` on MCP), and every saved-login create, update or delete, which takes no key. A read may simply be repeated. A job that failed carries this same object in its `error`; see [the job object](/guides/jobs/job-object). ## invalid\_request The request is malformed: its body, query or a field does not match what this endpoint accepts. * HTTP status: `400` * Retry: After changing the request. **What to do:** Fix the request and send it again; details.issues, when present, says where. | `details` field | Meaning | | - | - | | `issues` | When present, each failing part: `path`, its JSON pointer under `/path`, `/query`, `/headers` or `/body`, and `message`, what is expected there. Never the value sent. | ## invalid\_answer These answers do not fit the pending request. * HTTP status: `400` * Retry: After changing the request. **What to do:** Answer every question once, with an offered option id where it lists options, and send the answers again with POST /v1/jobs//answers. question\_id and reason name the first problem. | `details` field | Meaning | | - | - | | `question_id` | The question whose answer failed, when one did. | | `reason` | Why: `missing_answer`, `unexpected_answer`, `malformed`, `unoffered_option`, `other_not_allowed`, `note_not_allowed`, `selection_bounds`, `too_long`, `username_not_allowed` or `password_required`. | ## invalid\_input The input does not match this tool's input schema, so no job was started. * HTTP status: `400` * Retry: After changing the request. **What to do:** issues names each field's path and what it expects, with the allowed values when the schema lists them. When the schema has one shape per choice, such as per product, issues describe the shape the input's own choice picks. Fix the input and call again. | `details` field | Meaning | | - | - | | `issues` | Each failing field: `path`, its JSON pointer, and `message`, what the schema expects there, naming the allowed values when the schema lists them. Never the value sent. | ## input\_rejected The tool or the website refused a value in the input after the run started. * HTTP status: `409` * Retry: After changing the request. **What to do:** Correct the input, choosing one of available when it is given, or narrow the input when available\_more is set, and run it again with a new idempotency key. Unless possible\_commit is true, nothing changed on the website; if it is, check the website first. **If `write_applied` is true:** A step before the refusal may already have changed the website. Check the website. Running the tool again could repeat that change. | `details` field | Meaning | | - | - | | `possible_commit` | Whether a step may already have changed the website. | | `reason` | What the run said about the refusal, with the tool's own reason when it gave one. | | `field` | The input the tool or the website refused, when it was named. | | `available` | The first 20 choices the website offers for that input, exactly as shown, when the tool listed them. | | `available_more` | How many more choices the website offers than available lists. When the tool listed exactly 100 choices, its list may have been cut there, so this is the least there are. | | `write_applied` | True when the run's write read back its confirmation before the refusal, so it changed the website. | | `stopped_after` | When the website refused the value after earlier steps of a write: the name of the last website step the run started, whose draft may be saved. | | `final_step_sent` | With stopped\_after: `false`, as the run never sent its last step. | ## input\_not\_applied The website did not apply a value in the input, such as a location, store, filter, sort or date, so the run stopped without returning results computed without it. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Nothing changed on the website. Start it again with the same idempotency key only when the website's message points to a passing problem; otherwise change that value in the input. | `details` field | Meaning | | - | - | | `possible_commit` | Whether a step may already have changed the website. | | `reason` | What the run said: the value asked for, what the page kept and what the website said, when the run read them. | ## location\_not\_applied The website did not apply the location in the input, such as a ZIP code, address or store, so the run stopped without returning results for another place. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Nothing changed on the website. Start it again with the same idempotency key only when the website's message points to a passing problem; otherwise change the location in the input. | `details` field | Meaning | | - | - | | `possible_commit` | Whether a step may already have changed the website. | | `reason` | What the run said: the location asked for, what the page kept and what the website said, when the run read them. | ## unsupported\_login\_field This tool's sign-in does not take one of the login fields sent. * HTTP status: `400` * Retry: After changing the request. **What to do:** The tool's login\_fields and sign\_in\_methods list what it takes. Call again without field. | `details` field | Meaning | | - | - | | `field` | The login field the tool's sign-in does not take. | ## effect\_required A website build requires effect. * HTTP status: `400` * Retry: After changing the request. **What to do:** Use read when the tool only looks things up. Use write when it changes anything on the site: filling in or advancing a form that saves data (an application, profile or checkout), submitting, booking, drafts, holds, uploads and account updates. A search, filter or query form is a read. Use ask when unsure: the build works it out from the task. Only when the task leaves it unclear does it first ask read or write, as a pending input request answered with POST /v1/jobs//answers. ## tool\_not\_read\_only This tool changes the website, so the read-only run refused it before anything ran. * HTTP status: `400` * Retry: After changing the request. **What to do:** Run it with the ordinary run, which takes reads and writes, and send an idempotency key with the write. | `details` field | Meaning | | - | - | | `tool_id` | The refused tool's ID. | | `effect` | The tool's effect: `write`. | ## invalid\_email The email address is not valid here: it is not an email address, or, to confirm an account deletion, it does not match the signed-in account's. * HTTP status: `400` * Retry: After changing the request. **What to do:** Send a valid email address; to confirm a deletion, type the account's email address exactly. ## request\_too\_large The request body is larger than this endpoint accepts. * HTTP status: `413` * Retry: After changing the request. **What to do:** Send a smaller request. ## response\_too\_large The answer is larger than the Dashboard can pass on. * HTTP status: `502` * Retry: No. Repeating the request does not help. **What to do:** Call the same endpoint on the REST API, which answers in full. ## method\_not\_allowed This endpoint does not accept that HTTP method. * HTTP status: `405` * Retry: No. Repeating the request does not help. **What to do:** Use a method the endpoint documents. ## not\_found Nothing with that ID exists, or this caller may not see it. * HTTP status: `404` * Retry: No. Repeating the request does not help. **What to do:** Check the ID, and that this caller's account and permissions cover it. **If `operation` is `manage_login`:** GET /v1/logins with site\_url shows a site's saved logins and their status. To add one, use POST /v1/logins with site\_url and deliver "dashboard"; to replace one, PATCH /v1/logins/ with its id and deliver "dashboard". Each returns a dashboard\_url where the person signs in; never ask for a password in chat. **If `operation` is `build_tool`:** POST /v1/builds builds a tool for a site. **If `operation` is `change_tool`:** POST /v1/tools//changes changes one of your tools. **If `reason` is `unknown_operation`:** Find the operation's exact name with GET /v1/openapi.json. **If `reason` is `mcp_tool`:** Call that tool directly. **If `reason` is `site_mcp_tool`:** Find the operation that does what you need with GET /v1/openapi.json. | `details` field | Meaning | | - | - | | `operation` | When present, the Pomerado API operation name the call sent, which names no operation this caller may use. | | `reason` | When present, what that name is: `unknown_operation`, no operation; `mcp_tool`, a tool this MCP lists, to call directly; or `site_mcp_tool`, a tool only a site's own MCP lists. | ## moved\_to\_team This tool, or this integration's tools, moved to a Business team's Pomerado account, so this tool ID, MCP URL or API key no longer reaches it. Sign in to the Pomerado Dashboard with your work account and connect the team's MCP from Connect, or use the team's API key. * HTTP status: `410` * Retry: No. Repeating the request does not help. | `details` field | Meaning | | - | - | | `moved` | What moved: `tool`, the tool this call named, or `integration`, every tool of the integration whose MCP URL or API key this call used. | | `moved_at` | When it moved (ISO 8601). | | `dashboard_url` | The Pomerado Dashboard, where the team's members sign in. | ## unauthorized The request's credentials were missing, expired or rejected. * HTTP status: `401` * Retry: After signing in again. **What to do:** Sign in again, or send a valid API key or access token. ## session\_refresh\_required The Dashboard session needs to be refreshed. * HTTP status: `401` * Retry: After signing in again. **What to do:** Refresh the session and repeat the request. ## reauthentication\_required This action needs a recent sign-in. * HTTP status: `401` * Retry: After signing in again. **What to do:** Sign in again, then repeat it. ## forbidden This caller's account, grant or permissions do not allow this request. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Check the account's state and the permissions granted to this client or key. | `details` field | Meaning | | - | - | | `reason` | When present, why: `integration_key`, an API key limited to one integration, which reaches only that integration's tools, runs and jobs, or `service_account`, a service account's key, which reaches only running its tools and reading their jobs. | ## key\_permission\_missing This API key doesn't have the permission this request needs. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Create a key that includes it on the API keys page in the Pomerado Dashboard; details.keys\_url links there. | `details` field | Meaning | | - | - | | `permission` | The permission the key lacks, such as `team:manage`. | | `fix` | What to do: `create_key`, a new key that includes the permission. | | `keys_url` | The Dashboard page where the person creates API keys. | ## role\_permission\_missing Your role in this team doesn't allow this request. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard. | `details` field | Meaning | | - | - | | `permission` | The permission your role lacks, such as `team:manage`. | ## service\_account\_not\_allowed This service account's settings don't allow this request. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** details.setting names the setting that refused it. | `details` field | Meaning | | - | - | | `setting` | The setting that refused the request: `integrations`, `tools`, `effect`, `saved_logins`, `sent_logins`, `builds` or `repairs`. | ## service\_account\_cap\_reached This service account has reached its cap for this period. * HTTP status: `429` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Send the same request again after retry\_after\_seconds. | `details` field | Meaning | | - | - | | `cap` | The cap reached: `runs_per_day`, `runs_per_month` or `builds_per_month`. | | `limit` | The cap's value. | | `reset_at` | When the cap resets (ISO 8601), at 00:00 UTC. | ## stale\_grant The grant changed since it was read. * HTTP status: `409` * Retry: After changing the request. **What to do:** Read the grant again and send the change with its current revision. ## save\_forbidden This client may not save website logins: saving needs the logins:manage permission. * HTTP status: `403` * Retry: After changing the request. **What to do:** Call again without save, or ask the user to grant logins:manage. | `details` field | Meaning | | - | - | | `reason` | Why, when known: `account_lacks_manage`, `consent_lacks_manage`, `grant_lacks_manage` or `grant_inactive`. | ## login\_access\_required Your access to this saved login does not allow this action. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Ask someone who manages the login, or a team Owner or Admin, for more access. | `details` field | Meaning | | - | - | | `required` | The access level the action needs: `use`, `edit` or `manage`. | | `your_access` | Your access level on the login: `use`, `edit` or `manage`. | ## account\_unsupported This account type does not offer this action. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Use a Personal account owner for Personal features; an internal account has no self-service billing. ## organization\_access\_removed This user's membership in the Business account has ended. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Ask an administrator of the organization to restore access. ## email\_in\_use That email address is already in use by another Pomerado account. Addresses that reach one inbox, such as [name+tag@example.com](mailto:name+tag@example.com) and [name@example.com](mailto:name@example.com), count as one. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Sign in with the account that already uses this inbox. ## team\_sign\_in\_required This person belongs to a team on Pomerado, and this connection signed in without the team's organization. * HTTP status: `403` * Retry: After signing in again. **What to do:** Ask the person to disconnect Pomerado and connect again, choosing their team's organization when they sign in. ## organization\_not\_ready Your organization isn't set up on Pomerado yet, or its owner hasn't joined it yet. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Ask whoever set up Pomerado for your organization to finish setting it up, then sign in again. ## account\_deletion\_pending This account is being deleted. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Nothing more can be done with it. ## sign\_in\_required This needs a signed-in Pomerado account. Signing up is free, and an account also lets you build new tools, run tools that write and use saved website logins. * HTTP status: `401` * Retry: After signing in again. **What to do:** Sign up or sign in the same way: start a device sign-in with POST /v1/sign-in and give the person its verification\_url and user\_code; they open it on any device, sign in, or sign up if they have no account yet, and approve, and polling then returns an API key to send as an Authorization: Bearer header (on MCP, to the MCP's signed-in URL: a keyless URL, one ending in /keyless, takes no key). Or sign in with OAuth (on an MCP's keyless URL, connect your MCP client to the MCP's signed-in URL, its URL without /keyless, given as details.mcp\_url when present; elsewhere, connect or re-authenticate this server), or create an API key on the API keys page in the Pomerado Dashboard and send it as an Authorization: Bearer header. ## try\_limit\_reached Pomerado's keyless runs are used up for today from this network or across Pomerado. * HTTP status: `429` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Sign in to use the free plan's limits instead: POST /v1/sign-in starts a device sign-in. Or send the same request again after retry\_after\_seconds. ## try\_tier\_unavailable Keyless use is unavailable right now. * HTTP status: `503` * Retry: No. Repeating the request does not help. **What to do:** Sign in to continue: sign in with OAuth (on an MCP's keyless URL, connect your MCP client to the MCP's signed-in URL, its URL without /keyless, given as details.mcp\_url when present; elsewhere, connect or re-authenticate this server), or create an API key on the API keys page in the Pomerado Dashboard and send it as an Authorization: Bearer header. | `details` field | Meaning | | - | - | | `mcp_url` | On an MCP's keyless connection, the same MCP's signed-in URL, which signs in with OAuth or takes an API key. | ## sign\_in\_limit\_reached Too many device sign-ins were started from this network today. * HTTP status: `429` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Finish one already started, start another after retry\_after\_seconds, or sign in with OAuth (connect or re-authenticate this server in your MCP client), or create an API key on the API keys page in the Pomerado Dashboard and send it as an Authorization: Bearer header. ## sign\_in\_unavailable Device sign-in isn't available here. * HTTP status: `503` * Retry: No. Repeating the request does not help. **What to do:** Instead, sign in with OAuth (on an MCP's keyless URL, connect your MCP client to the MCP's signed-in URL, its URL without /keyless, given as details.mcp\_url when present; elsewhere, connect or re-authenticate this server), or create an API key on the API keys page in the Pomerado Dashboard and send it as an Authorization: Bearer header. | `details` field | Meaning | | - | - | | `mcp_url` | On an MCP's keyless connection, the same MCP's signed-in URL, which signs in with OAuth or takes an API key. | ## keyless\_url\_credentials This is an MCP's keyless URL, for use without signing in, and it takes no Authorization header. * HTTP status: `400` * Retry: After changing the request. **What to do:** Connect your MCP client to details.mcp\_url, this MCP's signed-in URL, with the same API key or token; or remove the header to use this URL without signing in. | `details` field | Meaning | | - | - | | `mcp_url` | The same MCP's signed-in URL, to connect to with the key or token. | ## invalid\_key\_lifetime A device sign-in asked for an API key lifetime outside 1 to 90 days. * HTTP status: `400` * Retry: After changing the request. **What to do:** Send expires\_in\_days as a whole number of days from 1 to 90, or leave it out for 30. ## access\_denied The person declined this device sign-in. * HTTP status: `410` * Retry: No. Repeating the request does not help. **What to do:** Start a new sign-in only if they ask to connect this agent again. ## expired\_token This device sign-in expired, or its API key was already handed out. * HTTP status: `410` * Retry: No. Repeating the request does not help. **What to do:** Start a new sign-in, and poll it until the person approves. ## account\_selection\_required Several saved logins for this website are available to this caller. * HTTP status: `409` * Retry: After changing the request. **What to do:** Call again with login\_id set to the login to use. ## saved\_login\_conflict This website already has a saved login for a different username, and a saved login comes before credentials in the request. * HTTP status: `409` * Retry: After changing the request. **What to do:** Remove or replace that saved login with PATCH /v1/logins/, or call again with login\_id to use it. | `details` field | Meaning | | - | - | | `site` | The website's registrable domain, for example `example.com`. | | `masked_username` | The saved login's username, masked to its first and last two characters. | | `first_verified_at` | When the saved login first signed in successfully (ISO 8601). | ## login\_identity\_conflict This account's login for the website is locked to a different username. * HTTP status: `409` * Retry: After changing the request. **What to do:** Use that login, or correct the saved login on the Logins page in the Pomerado Dashboard. | `details` field | Meaning | | - | - | | `site` | The website's registrable domain, for example `example.com`. | | `masked_username` | The saved login's username, masked to its first and last two characters. | | `first_verified_at` | When the saved login first signed in successfully (ISO 8601). | ## site\_login\_exists A login for this website is already saved, and this account keeps one per website. * HTTP status: `409` * Retry: After changing the request. **What to do:** Send it again without saving, or change the saved login with PATCH /v1/logins/. ## legacy\_duplicate\_saved\_logins This account has more than one saved login for the website from before the one-login rule. * HTTP status: `409` * Retry: After changing the request. **What to do:** Delete the extra logins with DELETE /v1/logins/, then try again. ## saved\_login\_unsettled A change to this website's saved login is still being confirmed. * HTTP status: `409` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Wait a moment and send the same request again. ## login\_in\_use This login is in use by another running job. * HTTP status: `409` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Jobs on one login run one at a time. Wait for that job to finish, then send the same request again. ## login\_save\_in\_progress A save of this website's login is still in progress. * HTTP status: `409` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Wait for it to finish, then read the saved logins with GET /v1/logins. A save held while the account's login storage is set up finishes within 10 minutes. ## login\_storage\_pending Secure login storage for this account is still being set up, so nothing was saved. Setup takes a few minutes after an account first uses a login. * HTTP status: `503` * Retry: Yes. Repeat the same request after the seconds its `Retry-After` header gives, until the storage is ready. **What to do:** Send the same request again after retry\_after\_seconds, and keep repeating it until it succeeds. A pending login that failed with this code was held for 10 minutes without storage becoming ready: save the login again. ## invalid\_credential The login's values are not valid for a saved login. * HTTP status: `422` * Retry: After changing the request. **What to do:** Correct the username, password or other fields and save it again. ## authenticator\_not\_configured This saved login has no authenticator secret. * HTTP status: `404` * Retry: No. Repeating the request does not help. **What to do:** Add the authenticator secret to the login on the Logins page in the Pomerado Dashboard first. ## credentials\_rejected The website rejected the login. * HTTP status: `409` * Retry: After changing the request. **What to do:** Correct the login before running the tool again. **If `possible_commit` is true:** The job may already have changed the website. Check the website and correct the login. Starting it again could repeat that change. | `details` field | Meaning | | - | - | | `field` | The login field the website rejected, when it said which. | | `possible_commit` | Whether a step may already have changed the website. | | `login_id` | The saved login to correct, while the job waits for it: correcting it resumes the job. | | `correction_expired` | True when the correction asked for in place expired unanswered. | ## pin\_required Saved logins are locked. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Unlock them with the Logins PIN on the Logins page in the Pomerado Dashboard. ## pin\_not\_set No Logins PIN is set for this account. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Set one on the Logins page in the Pomerado Dashboard. ## pin\_already\_set This account already has a Logins PIN. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Change it or reset it instead. ## invalid\_pin The PIN is not in the accepted format. * HTTP status: `400` * Retry: After changing the request. **What to do:** Use the number of digits the form asks for. ## pin\_mismatch The two PINs do not match. * HTTP status: `400` * Retry: After changing the request. **What to do:** Type the same PIN twice. ## wrong\_pin The PIN is incorrect. * HTTP status: `403` * Retry: After changing the request. **What to do:** Try again; repeated wrong PINs lock unlocking for a while. ## pin\_throttled Too many PIN attempts. * HTTP status: `429` * Retry: No. Repeating the request does not help. **What to do:** Wait before trying again, or reset the PIN with a code sent to your email on the Logins page in the Pomerado Dashboard. ## wrong\_reset\_code The PIN reset code is incorrect. * HTTP status: `403` * Retry: After changing the request. **What to do:** Type the eight-digit code from the latest reset email; five wrong tries end the code. ## reset\_code\_expired The PIN reset code has expired, was already used, was replaced by a newer code or had too many wrong tries. * HTTP status: `410` * Retry: No. Repeating the request does not help. **What to do:** Send a new code and use it within 15 minutes. ## reset\_code\_throttled A PIN reset code was sent too recently. * HTTP status: `429` * Retry: No. Repeating the request does not help. **What to do:** Use the code already sent, or wait a minute before asking for another; at most five codes go out an hour. ## email\_unverified Your account's email address is not verified, so no reset code can be sent to it. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Verify your email address with your sign-in provider, then try again. ## file\_too\_large The file is larger than Pomerado takes. * HTTP status: `413` * Retry: After changing the request. **What to do:** Send a file of at most max\_bytes bytes. An upload's limit is the same for every account and never passes 50 megabytes. For a larger file, the user can email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email) about it; ask the user before sending any email, and never email on your own. | `details` field | Meaning | | - | - | | `max_bytes` | The largest file, in bytes, this accepts. | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## file\_type\_refused Pomerado does not take this file's type. * HTTP status: `415` * Retry: After changing the request. **What to do:** reason says why: unsupported\_type, a media type Pomerado doesn't take (documents, images, audio, video, archives and text are taken); executable, a program or script, which is never taken; content\_mismatch, the file's bytes are not the media\_type it was declared as. Send the file with its real media type. | `details` field | Meaning | | - | - | | `reason` | Why: `unsupported_type`, `executable` or `content_mismatch`. | ## file\_unavailable This file can't be used now. * HTTP status: `409` * Retry: After changing the request. **What to do:** reason says why: expired, its hour passed unused; not\_uploaded, its bytes were never PUT to its upload\_url; in\_use, another run took it, and each file serves one run; upload\_in\_progress, its upload is still arriving. Upload the file again for a new file ID, unless it is only still arriving. | `details` field | Meaning | | - | - | | `reason` | Why: `expired`, `not_uploaded`, `in_use`, `already_uploaded` or `upload_in_progress`. | ## file\_limit\_exceeded The run names more files, or more bytes of files, than one run takes. * HTTP status: `413` * Retry: After changing the request. **What to do:** Send at most max\_files files of at most max\_total\_bytes bytes together. For more, the user can email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email); ask the user before sending any email, and never email on your own. | `details` field | Meaning | | - | - | | `max_files` | The most files one run takes: this account's own when staff raised it. | | `max_total_bytes` | The most bytes of files one run takes, together. | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## file\_fetch\_failed Pomerado could not fetch a file from the URL given in the input, so no job was started. * HTTP status: `422` * Retry: After changing the request. **What to do:** reason says why: not\_https (only https on the default port, with no credentials in the URL), blocked\_address (the host is not a public internet address), unreachable, timeout, too\_large, http\_error (an answer other than 2xx, an encoded body or no Content-Length) or redirect\_limit (more than three redirects). Fix the URL, or upload the file and pass its file ID instead. | `details` field | Meaning | | - | - | | `reason` | Why: `not_https`, `blocked_address`, `unreachable`, `timeout`, `too_large`, `http_error` or `redirect_limit`. | ## sms\_number\_not\_allowed Pomerado phone numbers aren't available to this account yet. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Pomerado phone numbers come with a Business account; email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email) to set one up. | `details` field | Meaning | | - | - | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## sms\_number\_not\_linked This saved login has no Pomerado phone number. * HTTP status: `404` * Retry: No. Repeating the request does not help. **What to do:** Assign one first. ## sms\_number\_not\_yours This login's Pomerado phone number belongs to another member of your team. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Ask the teammate whose number it is to check it. ## sms\_number\_cap\_reached Pomerado has no more phone numbers to give out right now. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Try again later. ## sms\_number\_purchase\_failed Pomerado could not get a phone number for this login. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Try again in a few minutes. ## site\_not\_supported Pomerado doesn't build tools for this website. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Calling again does not help. Tools you already have for the site keep running. **If `category` is `government`:** Calling again does not help. **If `category` is `bank`:** Calling again does not help. **If `category` is `unsupported_site`:** Calling again does not help. | `details` field | Meaning | | - | - | | `site` | The refused site's registrable domain. | | `category` | Why it is refused: `ticket_site`, `bank`, `government`, `url_shortener` or `unsupported_site` (turned off for now). | | `refused` | `run` when a run of the site's tool was refused; absent for a build or a saved login. | ## quota\_exceeded The account's quota is used up. * HTTP status: `429` * Retry: Only as a fresh request with a new idempotency key. **What to do:** Wait for the reset or upgrade on the billing settings in the Pomerado Dashboard, then send a fresh request with a new idempotency key. | `details` field | Meaning | | - | - | | `reset_at` | When the quota resets (ISO 8601), when known. | ## concurrent\_build\_limit\_reached Your organization is already running as many builds at once as its account allows. * HTTP status: `429` * Retry: Only as a fresh request with a new idempotency key. **What to do:** Please contact us at [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email) and we'll raise your limit. | `details` field | Meaning | | - | - | | `limit` | How many builds the account may run at once. | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## quota\_expired The quota reservation for this request expired before it was accepted. * HTTP status: `409` * Retry: Only as a fresh request with a new idempotency key. **What to do:** Confirm no job was accepted with GET /v1/jobs/, then send a fresh request with a new idempotency key. ## idempotency\_conflict This idempotency key was already used for a different request. * HTTP status: `409` * Retry: Only as a fresh request with a new idempotency key. **What to do:** Repeat the original request with that key, or use a new key for a new request. ## execution\_budget\_exhausted This job used its whole execution budget. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Check the job's outcome with GET /v1/jobs/ before starting another. ## input\_expired This input request has expired. * HTTP status: `410` * Retry: No. Repeating the request does not help. **What to do:** Read the job again with GET /v1/jobs/. ## input\_not\_pending This job has no such pending input request. * HTTP status: `409` * Retry: After changing the request. **What to do:** Read the job again with GET /v1/jobs/ and answer its current input\_request. ## not\_paused This job is not paused, or its pause waits on an answer to its question. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Read the job again with GET /v1/jobs/; answer its input\_request if it has one. ## pause\_expired This build's pause ran out before the build could restart, so it ended. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Start a new build if you still need the tool; GET /v1/jobs/ shows how this one ended. **If `possible_commit` is true:** A step before the build paused may already have changed the website. Check the website. Starting it again could repeat that change. | `details` field | Meaning | | - | - | | `pause_reason` | Why the build paused: `question_expired`, `sign_in_expired` or `owner`. | | `queued_behind` | True when the build was resumed but its saved login stayed in use by another job until its time ran out. | | `possible_commit` | Whether a step may already have changed the website. | ## input\_declined The client declined the input request; it stays pending. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Answer it, or cancel the job with POST /v1/jobs//cancel. ## input\_cancelled The client cancelled the input request; it stays pending. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Answer it, or cancel the job with POST /v1/jobs//cancel. ## result\_expired This job's result is no longer kept: Pomerado keeps a result only until it is first read, and at most 60 minutes after the job ends, or, for a run whose repair is still confirming the result it kept, at least 60 minutes after that repair ends (never past 24 hours). * HTTP status: `410` * Retry: No. Repeating the request does not help. **What to do:** Check the website before starting the work again; a new run may act on it again. ## result\_unavailable The job was accepted, but it could not be read. * HTTP status: `503` * Retry: No. Repeating the request does not help. * A write may already have been accepted; repeat it only with its idempotency key. **What to do:** Read the job again with GET /v1/jobs/ instead of resubmitting it. | `details` field | Meaning | | - | - | | `job_id` | The accepted job's ID, when this answered the call that started it. | ## no\_response The job asked a question that was not answered in time, so it stopped. * HTTP status: `409` * Retry: Only as a fresh request with a new idempotency key. **What to do:** Start it again with a new idempotency key. **If `possible_commit` is true:** A step before the question may already have changed the website. Check the website. Starting it again could repeat that change. **If `question` is `sign_in`:** The website asked for a sign-in code or detail that only a person can give. Start it again when someone can answer on the answer page within 10 minutes. To skip codes, add an authenticator key or a Pomerado phone number to the saved login. **If `question` is `login`:** The website needed a login and none was saved. Save it on the Logins page, then start it again. | `details` field | Meaning | | - | - | | `possible_commit` | Whether a step may already have changed the website. | | `question` | For a run, the kind of question that went unanswered: `sign_in` (a code or other detail the website's sign-in asked for), `login` (a login for the website) or `other`. | ## question\_needs\_account The tool needed an answer from you to continue, and a run without a Pomerado account can't answer questions, so it stopped. * HTTP status: `409` * Retry: After signing up (free) or signing in. **What to do:** Sign up (free) or sign in, then run it again. An input that matches exactly one option may avoid the question. **If `possible_commit` is true:** A step before the question may already have changed the website. Check the website. Starting it again could repeat that change. | `details` field | Meaning | | - | - | | `possible_commit` | Whether a step may already have changed the website. | ## worker\_lost A temporary Pomerado service problem ended the job before it finished, such as a lost worker or browser, and nothing reached the website or the job only reads. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Start it again with the same idempotency key. ## outcome\_unknown The job did not produce a validated result, and it may have changed the website. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Check the job and the website before retrying a website action. **If `write_applied` is true:** Check the website. Starting it again could repeat that change. | `details` field | Meaning | | - | - | | `write_applied` | True when the job's write read back its confirmation, so it changed the website. | | `reason` | `site_lacks_capability` (the website doesn't offer what the request needs), `policy` (a safety review, one of your constraints, or an answer it needed rules it out), `host_refused` (one of Pomerado's own checks stopped the build before it published a tool, and the build couldn't fix it), `agent_declined` (Pomerado's build agent declined to build the request as asked; `explanation` says which part), `not_supported_yet` (it needs a capability Pomerado doesn't have yet), `new_mint_recommended` (a change you confirmed during the build is a different task, which needs a new build) or `change_refused` (Pomerado refused a requested change to an existing tool: before any work started, because the change would alter inputs the tool already has, or because it needs input values its example doesn't send). Absent when Pomerado can't tell. | | `reason_code` | For `policy`, what stopped it, when Pomerado knows: `question_unanswered` (a question the build asked wasn't answered, and it couldn't safely go on without one), `owner_declined` (a step it needed was declined), `site_declined` (the website declined the example in the request, for the reason `explanation` gives), `guardian_denied` (Pomerado's safety review ruled it out), `owner_ended` (you chose to end the build, such as when a fact it needed was unknown) or `caller_input` (the request's own data or rules stopped it, such as facts that conflict or a stop condition it set; `explanation` says what to supply or change). For `not_supported_yet`, the missing capability, such as `realtime_stream`. For `change_refused`, why: `unclear`, `narrows_tool`, `not_on_site`, `different_task`, `harmful`, `other_people_data` (the change would collect personal details about people the site doesn't show you in your own account) or `changes_inputs` (the change would alter inputs the tool already has; a change can add optional inputs, let an input accept more (more allowed values, a looser or dropped limit, or a default where it has none), hold one to a limit the site itself states, or make a required one optional, but never remove one, require one, drop an allowed value or change its type or default; `explanation` names each input it would alter) or `example_missing_inputs` (the change needs values its `example_input` doesn't send, which `explanation` names; send it again with them). | | `explanation` | Why, in Pomerado's words or the build's screened words. | | `suggestion` | For `not_supported_yet`, the part of the request Pomerado can build today. | | `suggested_request` | For `new_mint_recommended`, the request to submit as a new build for the changed task. | | `possible_commit` | Whether a step may already have changed the website. | ## invalid\_output The run's result did not match the tool's output schema. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Check the job before retrying a website action; Pomerado repairs the tool. **If `write_applied` is true:** Check the website. Starting it again could repeat that change. | `details` field | Meaning | | - | - | | `write_applied` | True when the job's write read back its confirmation, so it changed the website. | | `explanation` | When the run changed nothing and its repair ended without fixing the tool: what Pomerado found when it checked the website, such as the website showing its own maintenance page, screened. It is told once, on a read that includes results. | | `repair_question` | When the run changed nothing and its repair ended because a question it asked you went unanswered: that question, as you were asked it. Only a read that includes results shows it, for up to 60 minutes after the repair ended. | | `change_request` | With `repair_question`: a ready request to change this tool so it handles what the repair asked. Send its `tool_id` and `change` with `operation`, which MCP serves as `mcp_tool`; add your answer to `change` first if you have one. | ## execution\_failed The run did not produce a validated result. * HTTP status: `409` * Retry: No, except a read a CAPTCHA stopped or one stopped because the website was temporarily unavailable (below), as a fresh request with a new idempotency key. **What to do:** Check the job before retrying a website action. **If `write_applied` is true:** Check the website. Starting it again could repeat that change. **If the run was a read and `reason_code` is `challenge_unsolved`:** Nothing was submitted. Running it again often gets past the check, so decide whether to run it again or tell the user. Run it again, with a new idempotency key if you used one. **If the run was a read and `reason_code` is `site_unavailable`:** Nothing was submitted. Run it again later, once the website is back. Run it again, with a new idempotency key if you used one. **If `reason_code` is `stopped_before_final_step`:** Check it before running again. | `details` field | Meaning | | - | - | | `reason_code` | Why, when Pomerado knows it: `browser_action_timeout` (a browser action on the website timed out), `challenge_unsolved` (the website showed a CAPTCHA or human-verification check Pomerado could not complete), `stopped_before_final_step` (a write stopped before its last website step, so it did not finish, and its earlier steps may have saved a draft on the website), `site_unavailable` (the website was temporarily unavailable, such as its own maintenance page or an error from its own service, and the run changed nothing on it; try again later) or `caller_declined` (a write stopped because you said no to a question its repair asked, or chose to end it; `saved_steps` lists what was already saved on the website). | | `vendor` | For `challenge_unsolved`: who made the check, such as `perimeterx`, `akamai`, `cloudflare`, `datadome` or `recaptcha`, or `unknown`. | | `write_applied` | True when the job's write read back its confirmation, so it changed the website. | | `possible_commit` | Whether a step may already have changed the website. | | `stopped_after` | For `stopped_before_final_step`: the name of the last website step the run started. For `caller_declined`: the last of `saved_steps`, when there is one. | | `final_step_sent` | For `stopped_before_final_step` and `caller_declined`: `false`, as the run never sent its last step. | | `question` | For `caller_declined`: the question you declined or answered, as you were asked it. Only a read that includes results shows it, for up to 60 minutes after the repair ended. | | `saved_steps` | For `caller_declined`: the website steps the run and its repair saved before stopping, in the tool's order; empty when nothing was saved. | | `chose_to_end` | For `caller_declined`: `true` when you chose to end it, not declined a step. | | `field` | For `stopped_before_final_step` on a run an older release stopped: the input the tool refused, named with the choices the website offers. A refusal listing the website's choices now fails the run with `input_rejected` instead. | | `explanation` | When the run changed nothing and its repair ended without fixing the tool: what Pomerado found when it checked the website, such as the website showing its own maintenance page, screened. It is told once, on a read that includes results. | | `repair_question` | When the run changed nothing and its repair ended because a question it asked you went unanswered: that question, as you were asked it. Only a read that includes results shows it, for up to 60 minutes after the repair ended. | | `change_request` | With `repair_question`: a ready request to change this tool so it handles what the repair asked. Send its `tool_id` and `change` with `operation`, which MCP serves as `mcp_tool`; add your answer to `change` first if you have one. | ## repair\_timed\_out Pomerado couldn't repair this tool in time to finish this run, and the run changed nothing on the website or only reads. * HTTP status: `503` * Retry: Only as a fresh request with a new idempotency key. **What to do:** Run it again later with a new idempotency key. ## result\_too\_large This run's result is over 32 megabytes — it's too big. * HTTP status: `422` * Retry: No. Repeating the request does not help. **What to do:** Please contact us for custom support at [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email). **If `write_applied` is true:** The run's write changed the website. Please contact us for custom support at [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email); running the tool again could repeat that change. | `details` field | Meaning | | - | - | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | | `write_applied` | True when the job's write read back its confirmation, so it changed the website. | ## downloaded\_file\_too\_large This run's downloaded file is over our limit for one file, so the run returned no file. * HTTP status: `422` * Retry: No. Repeating the request does not help. **What to do:** For a higher limit on this account, the user can email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email). Ask the user before sending any email; never email on your own. **If `write_applied` is true:** The run's write changed the website; running the tool again could repeat that change. For a higher limit on this account, the user can email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email). Ask the user before sending any email; never email on your own. | `details` field | Meaning | | - | - | | `limit` | Which cap: `file`, the most one file may hold, or `run`, the most a run's files may hold together. | | `max_bytes` | That cap, in bytes. | | `max_files` | For `run`: the most files a run may hold. | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | | `write_applied` | True when the job's write read back its confirmation, so it changed the website. | ## account\_restricted The website says this account is restricted, for example suspended or unpaid, so the run stopped and changed nothing on the website. * HTTP status: `409` * Retry: Only as a fresh request with a new idempotency key. **What to do:** Fix the account on the website, then run it again with a new idempotency key. | `details` field | Meaning | | - | - | | `reason` | What the run said about the restriction, with the website's own words when it gave them. | ## location\_unavailable Pomerado has no proxy free in the country this runs from right now. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Try again in a few minutes. ## proxy\_unreachable Your proxy didn't connect, so Pomerado stopped before reaching the website. * HTTP status: `502` * Retry: After changing the request. **What to do:** Check the proxy you passed, then try again. ## website\_sign\_in\_unavailable Signing in to the website was unavailable, so the job stopped before changing anything. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. * Retry when `reason_code` is `signed_in_elsewhere`: After changing the request. **What to do:** Start it again with the same idempotency key in a few minutes. **If `reason_code` is `account_locked`:** Unlock the account on the website first; waiting doesn't unlock it. Then start it again with the same idempotency key. **If `reason_code` is `sign_in_failed`:** Check that the login signs in on the website, then start it again with the same idempotency key. **If `reason_code` is `sign_in_service_unsupported`:** The login worked; starting it again won't help until Pomerado supports this kind of sign-in. **If `reason_code` is `signed_in_elsewhere`:** Build again with site\_url set to that address. | `details` field | Meaning | | - | - | | `reason_code` | Why, when Pomerado knows it: `account_locked` (the website reported the account locked), `sign_in_failed` (a sign-in failed and no further one could run in the job), `site_unavailable` (the website said its sign-in was temporarily unavailable), `sign_in_service_unsupported` (the website's sign-in moves to a separate sign-in service that built tools can't follow yet) or `signed_in_elsewhere` (signing in worked, but the website moved the session to another address, `site_url`). | | `site_url` | With `signed_in_elsewhere`: the https origin the website moved the session to, the `site_url` to build with. | ## build\_blocked This tool can't be built as asked. * HTTP status: `409` * Retry: After changing the request. **What to do:** Change the request before building again; details say why. **If `possible_commit` is true:** A step may already have changed the website, so check it before building again with a changed request. **If `possible_commit` is true and `reason` is `host_refused`:** A step may already have changed the website, so check it before building again. **If `possible_commit` is true and `reason_code` is `owner_ended`:** A step may already have changed the website, so check it before building again. **If `reason` is `host_refused`:** Building again may stop the same way. **If `reason` is `agent_declined`:** Change the request, such as leaving out the part it declined, before building again. **If `reason_code` is `question_unanswered`:** Build again when you can answer its questions, or put the answers in the request. **If `reason_code` is `owner_ended`:** Build again when you're ready. | `details` field | Meaning | | - | - | | `reason` | `site_lacks_capability` (the website doesn't offer what the request needs), `policy` (a safety review, one of your constraints, or an answer it needed rules it out), `host_refused` (one of Pomerado's own checks stopped the build before it published a tool, and the build couldn't fix it), `agent_declined` (Pomerado's build agent declined to build the request as asked; `explanation` says which part), `not_supported_yet` (it needs a capability Pomerado doesn't have yet), `new_mint_recommended` (a change you confirmed during the build is a different task, which needs a new build) or `change_refused` (Pomerado refused a requested change to an existing tool: before any work started, because the change would alter inputs the tool already has, or because it needs input values its example doesn't send). Absent when Pomerado can't tell. | | `reason_code` | For `policy`, what stopped it, when Pomerado knows: `question_unanswered` (a question the build asked wasn't answered, and it couldn't safely go on without one), `owner_declined` (a step it needed was declined), `site_declined` (the website declined the example in the request, for the reason `explanation` gives), `guardian_denied` (Pomerado's safety review ruled it out), `owner_ended` (you chose to end the build, such as when a fact it needed was unknown) or `caller_input` (the request's own data or rules stopped it, such as facts that conflict or a stop condition it set; `explanation` says what to supply or change). For `not_supported_yet`, the missing capability, such as `realtime_stream`. For `change_refused`, why: `unclear`, `narrows_tool`, `not_on_site`, `different_task`, `harmful`, `other_people_data` (the change would collect personal details about people the site doesn't show you in your own account) or `changes_inputs` (the change would alter inputs the tool already has; a change can add optional inputs, let an input accept more (more allowed values, a looser or dropped limit, or a default where it has none), hold one to a limit the site itself states, or make a required one optional, but never remove one, require one, drop an allowed value or change its type or default; `explanation` names each input it would alter) or `example_missing_inputs` (the change needs values its `example_input` doesn't send, which `explanation` names; send it again with them). | | `explanation` | Why, in Pomerado's words or the build's screened words. | | `suggestion` | For `not_supported_yet`, the part of the request Pomerado can build today. | | `suggested_request` | For `new_mint_recommended`, the request to submit as a new build for the changed task. | | `possible_commit` | Whether a step may already have changed the website. | ## build\_failed The build couldn't be completed. * HTTP status: `409` * Retry: Only as a fresh request with a new idempotency key, and not while `denial_reason` is `host_owned` or `reason_code` is `host_unavailable`: those fail the same way until Pomerado fixes them. **What to do:** Start a new build with a new idempotency key; if it ends the same way, change the request. **If `possible_commit` is true:** It may already have changed the website. Check the website. Building again could repeat that change. **If `denial_reason` is `host_owned`:** The fault is in a file Pomerado writes, so building again fails the same way until Pomerado fixes it; Pomerado's team was alerted. **If `reason_code` is `publication_denied`:** Change the request, for example to leave out what the review objected to, before building again with a new idempotency key. **If `reason_code` is `challenge_unsolved`:** Build again later with a new idempotency key, when the website's check may pass. **If `reason_code` is `site_unavailable`:** Build again later with a new idempotency key, once the website is back. **If `reason_code` is `evidence_unavailable`:** To publish it, build again with a new idempotency key and an example that shows that part, or with a request that leaves that part out. **If `reason_code` is `budget_spent`:** Narrow the request, such as fewer pages, fields or steps, and build again with a new idempotency key. **If `reason_code` is `timed_out`:** Build again with a new idempotency key. **If `reason_code` is `time_limit`:** Build again with a new idempotency key; a narrower request, such as fewer pages, fields or steps, finishes sooner. **If `reason_code` is `request_expired`:** Submit it again as a new build with a new idempotency key. **If `reason_code` is `host_unavailable`:** Waiting alone may not fix it: building again before Pomerado restores the service may stop the same way. | `details` field | Meaning | | - | - | | `reason_code` | Why it ended: `publication_denied` (Pomerado's safety review didn't approve the tool it made), `challenge_unsolved` (the website showed a CAPTCHA or human-verification check Pomerado could not complete on any browser it could try; try again later), `stopped_unexplained` (its build agent ended without finishing the tool), `budget_spent` (it used every step it is allowed), `timed_out` (it stalled on Pomerado's side), `time_limit` (it used all the time a build may take), `request_expired` (its request expired before the build started), `host_unavailable` (Pomerado's own services couldn't go on, such as its AI model capacity running out; waiting alone may not fix it), `site_unavailable` (the website was temporarily unavailable, such as its own maintenance page or an error from its own service; try again later), `evidence_unavailable` (its tests didn't show part of what the tool says it does) or `publication_missing` (Pomerado recorded no cause). | | `explanation` | A short explanation, screened for you: for `publication_denied` the review's reason, for `stopped_unexplained` the build agent's last words, when it gave any, for `site_unavailable` what the build found on the website, and for `evidence_unavailable` the part its tests didn't show. | | `denial_reason` | For `publication_denied`, the review's finite reason, such as `privacy`, `authority`, `evidence` or `host_owned` (the fault is in a file Pomerado writes, so every build fails the same way until Pomerado fixes it). | | `vendor` | For `challenge_unsolved`: who made the check, such as `perimeterx`, `akamai`, `cloudflare`, `datadome` or `recaptcha`, or `unknown`. | | `browsers_tried` | For `challenge_unsolved`: how many browsers failed the check, each on a different network route or browser mode. | | `possible_commit` | Whether a step may already have changed the website. | ## change\_pending Your account already has a change to this tool under way. * HTTP status: `409` * Retry: Yes. Repeat the same request, with its idempotency key, once the change already under way has finished, which takes about as long as a build. **What to do:** Follow that change's job until it finishes, then send this change again. | `details` field | Meaning | | - | - | | `job_id` | The job of the change already under way. | ## report\_limit\_reached Your account has sent as many problem reports as Pomerado takes for now. * HTTP status: `429` * Retry: No. Repeating the request does not help. **What to do:** Carry on with your task. Don't send this report again. | `details` field | Meaning | | - | - | | `window` | Which limit is full: hour, your account's 50 reports an hour, or day, its 200 reports a day. | ## version\_not\_restorable This version of the tool can't be restored. * HTTP status: `409` * Retry: After changing the request. **What to do:** Restore another version. Each version says whether it can be restored. ## restore\_changes\_inputs This version takes different inputs, so callers would break. * HTTP status: `409` * Retry: After changing the request. **What to do:** Restore a version that keeps every input the live version takes. It may add optional ones, add allowed values or a default to one, or make a required one optional. ## restore\_unchecked Pomerado didn't check this version, so it can't go live on a public tool. * HTTP status: `409` * Retry: After changing the request. **What to do:** Restore a version Pomerado checked. ## draft\_not\_run Run this draft before publishing it. * HTTP status: `409` * Retry: After changing the request. **What to do:** Run the draft, then publish it. ## draft\_changed The draft changed since you ran it. * HTTP status: `409` * Retry: After changing the request. **What to do:** Run it again, then publish. ## draft\_not\_publishable This draft can't go live as it is. * HTTP status: `409` * Retry: After changing the request. **What to do:** Fix its schemas or declarations, run it again, then publish. ## draft\_changes\_inputs This draft takes different inputs, so callers of this public tool would break. * HTTP status: `409` * Retry: After changing the request. ## draft\_holds\_owner\_data This draft holds a team member's email address or name, or a credential, so it can't go live on a public tool. * HTTP status: `409` * Retry: After changing the request. | `details` field | Meaning | | - | - | | `tool_id` | The tool that holds it, or null when it's the integration's own site name or summary. | | `part` | Where: `site_name`, `site_summary`, `name`, `description`, `input_schema`, `output_schema`, `questions`, `regression_inputs`, `login_url`, `supported_variants` or `bundle`. | | `path` | The file in the tool's code, when `part` is `bundle`. | ## unchecked\_version A tool's live version was published from the editor without Pomerado's checks. * HTTP status: `409` * Retry: After changing the request. **What to do:** Restore a checked version first. ## tool\_signs\_in\_outside\_site A tool in this integration signs in outside its website, which its owner approved, so it can't be made public. * HTTP status: `409` * Retry: After changing the request. ## tool\_on\_bank\_site A tool in this integration is for a bank, which only Pomerado staff may use, so it can't be made public. * HTTP status: `409` * Retry: No. Repeating the request does not help. ## tool\_holds\_owner\_data A tool in this integration holds your own email address or name, or a credential, so it can't be made public. * HTTP status: `409` * Retry: After changing the request. **What to do:** Ask Pomerado to change the tool so it holds none of your own details, or delete it, then try again. | `details` field | Meaning | | - | - | | `tool_id` | The tool that holds it, or null when it's the integration's own site name or summary. | | `part` | Where: `site_name`, `site_summary`, `name`, `description`, `input_schema`, `output_schema`, `questions`, `regression_inputs`, `login_url`, `supported_variants` or `bundle`. | | `path` | The file in the tool's code, when `part` is `bundle`. | ## integration\_too\_large\_to\_check This integration's tools are too large for Pomerado to check for your own email address or name, or a credential, in one request, so it wasn't made public. * HTTP status: `409` * Retry: After changing the request. **What to do:** Email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email) to have Pomerado check it and make it public, or delete tools here you no longer need, then try again. | `details` field | Meaning | | - | - | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## tool\_code\_missing Pomerado can't read the stored code of a tool in this integration, so it can't check it for your own details, and nothing was made public. * HTTP status: `409` * Retry: After changing the request. **What to do:** Email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email) to have the tool repaired, or delete it, then try again. | `details` field | Meaning | | - | - | | `tool_id` | The tool whose stored code is gone. | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## integration\_tool\_name\_taken The integration these tools would join already has a tool with the same name. * HTTP status: `409` * Retry: After changing the request. **What to do:** Delete one of the tools, then change the visibility again. ## permission\_not\_held A key may carry only permissions you hold now, and an integration key only tools:read, runs:create, jobs:read and jobs:cancel. * HTTP status: `403` * Retry: After changing the request. **What to do:** Ask for fewer permissions, or have an owner or admin grant you the missing ones first. ## api\_key\_limit\_reached This account has as many API keys as it may hold. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Revoke a key you no longer use with DELETE /v1/api-keys/, then create another. ## api\_key\_create\_throttled Too many connect keys were created in the last hour. * HTTP status: `429` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Send the same request again after retry\_after\_seconds, or create a key without a purpose with POST /v1/api-keys. ## webhook\_limit\_reached This account has as many webhooks as it may hold. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Delete a webhook you no longer use with DELETE /v1/webhooks/, then create another. ## webhook\_secret\_unreadable This webhook's signing secret can no longer be read, so nothing can be signed with it. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Rotate its secret with POST /v1/webhooks//rotate-secret, then try again. ## team\_unavailable Teams are available only to Business accounts that have them turned on. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Use a Business account's member, or email [founders@pomerado.ai](mailto:founders@pomerado.ai) to set up a team. ## business\_account\_required Only Business accounts can do this. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Use a Business account, or email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email) to set one up. | `details` field | Meaning | | - | - | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## egress\_options\_not\_allowed Location and proxy work only on your account's private tools. * HTTP status: `403` * Retry: After changing the request. **What to do:** Run this tool without them, or build your own private tool for this website. ## role\_not\_allowed Your role may not invite someone with that role: only an Owner invites an Owner. * HTTP status: `403` * Retry: After changing the request. **What to do:** Invite them as an Admin or a Member, or ask an Owner to invite them. ## access\_fixed This person manages the login whatever it is shared with: they created it, or they are a team Owner or Admin. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Change someone else's access, or change their role on the Team page in the Pomerado Dashboard. ## grantee\_not\_member This person is not a current member of the team. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Invite them on the Team page in the Pomerado Dashboard first. ## team\_level\_not\_allowed The whole team can be given Use or Edit on a login, never Manage. * HTTP status: `409` * Retry: After changing the request. **What to do:** Give the team Use or Edit, or give Manage to one member. ## role\_change\_forbidden Your role may not make this change: an Admin never changes or removes an Owner, or makes one. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Ask an Owner to make the change. ## role\_unavailable This role isn't available in this Pomerado environment yet. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Choose another role for now; ask Pomerado support when this one will be. ## owner\_required Only a team Owner can do this. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Ask a team Owner to do it. ## setup\_email\_active A setup email WorkOS sent your team's IT admin earlier still works, so no new one was sent. * HTTP status: `409` * Retry: After changing the request. **What to do:** Send the request again with replace set to true to send a new one; the earlier email's link then stops working. ## setup\_email\_throttled Your team has sent as many setup emails as it may for now. * HTTP status: `429` * Retry: Yes. Send the same request again after the seconds its `Retry-After` header gives; the operation takes no idempotency key. **What to do:** Send it again after retry\_after\_seconds. ## last\_owner This change would leave the team without an Owner. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Make another member an Owner first, then repeat it. ## managed\_by\_identity\_provider Your identity provider manages this team's members and roles. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Change them there. ## domain\_not\_allowed Your team invites only addresses at its organization's own domains. * HTTP status: `403` * Retry: After changing the request. **What to do:** Invite a work address at one of them. ## already\_member This person is already a member of the team. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Change their role on the Team page in the Pomerado Dashboard instead. ## already\_invited This email address appears more than once in the request, so only its first row was invited. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Send each address once; pending invitations are listed on the Team page in the Pomerado Dashboard. ## in\_another\_team This person already belongs to another team; a person belongs to at most one. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** They must leave that team before they can be invited. ## webhooks\_unavailable Webhooks are not available in this Pomerado environment yet. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Follow jobs with GET /v1/jobs/ instead. ## run\_view\_unavailable Watching a run is not available in this Pomerado environment yet. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Follow the run with GET /v1/jobs/ instead. ## run\_view\_throttled Too many requests to watch runs. * HTTP status: `429` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Wait a few seconds and send the same request again. ## slack\_channel\_unavailable Shared Slack channels with Pomerado are not available here for this account or address right now. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Email [founders@pomerado.ai](mailto:founders@pomerado.ai) and we will set up the channel with you. ## slack\_channel\_in\_progress This account's shared Slack channel is still being created. * HTTP status: `409` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Wait a few seconds, then open the Settings page in the Pomerado Dashboard again. ## invalid\_code This code cannot be redeemed: it is unknown, already used, revoked or expired, or this account already had its offer. * HTTP status: `400` * Retry: After changing the request. **What to do:** Check the code for typos, or ask whoever sent it for a new one. ## promotion\_already\_active This account already has a free Premium period running; the code stays unused. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Redeem the code after the current free period ends, or give it to someone else. ## billing\_unavailable This account has no self-service billing: a Business account's plan is set by its agreement with Pomerado, and an internal account isn't billed. * HTTP status: `403` * Retry: No. Repeating the request does not help. **What to do:** Email [founders@pomerado.ai](mailto:founders@pomerado.ai) (details.contact\_email) about a Business account's plan. | `details` field | Meaning | | - | - | | `contact_email` | The address to email to arrange it: `founders@pomerado.ai`. | ## billing\_attention\_needed This account's subscription needs attention first: a payment is due or a plan change is scheduled. * HTTP status: `409` * Retry: No. Repeating the request does not help. **What to do:** Settle the subscription on the billing settings in the Pomerado Dashboard, then redeem the code again. ## redeem\_throttled Too many codes that could not be redeemed in the last hour. * HTTP status: `429` * Retry: No. Repeating the request does not help. **What to do:** Wait an hour, then try again with the exact code you were sent. ## temporarily\_unavailable A Pomerado service this request needs did not answer, so nothing was done. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. **What to do:** Wait a few seconds and send the same request again. ## acceptance\_unknown Pomerado could not confirm whether this request was accepted. * HTTP status: `503` * Retry: Yes. Repeat the same request, with its idempotency key, after a short wait. * A write may already have been accepted; repeat it only with its idempotency key. **What to do:** Check before repeating it: read a run's or build's job with GET /v1/jobs/, or a saved login's change with GET /v1/logins. A run or build may be sent again with the same idempotency key; a read is always safe to repeat. ## not\_configured This Pomerado environment does not offer this feature. * HTTP status: `501` * Retry: No. Repeating the request does not help. **What to do:** Use an environment where it is enabled. | `details` field | Meaning | | - | - | | `feature` | The missing feature: `logins`, `sms_numbers`, `billing`, `mcp` (the Pomerado MCP), `files` or `email` (the emails a PIN reset needs). | ## internal\_error Pomerado failed unexpectedly while handling this request. * HTTP status: `500` * Retry: No. Repeating the request does not help. **What to do:** Check whether the request took effect before repeating it; the failure is recorded. # Call Pomerado from a backend Source: https://docs.pomerado.ai/examples/backend-application Run a tool from your backend with the REST API, follow its job and receive a webhook. Call the REST API from your backend with an API key. This guide runs a Google Flights search, follows its job and receives a webhook when the job finishes. ## Set up Create an API key on the Dashboard under Settings, then API keys, and load it into `POMERADO_API_KEY` from your secret manager. ```ts theme={null} const api = "https://api.pomerado.ai/v1"; const headers = { Authorization: `Bearer ${process.env.POMERADO_API_KEY}`, "Content-Type": "application/json", }; ``` ## Find the tool ```ts theme={null} const tools = await fetch( `${api}/tools?query=search+flights&site_url=google.com`, { headers }, ); const toolId = (await tools.json()).data[0].id; ``` ## Run it ```ts theme={null} const run = await fetch(`${api}/runs/read-only`, { method: "POST", headers, body: JSON.stringify({ tool_id: toolId, input: { origin: "SFO", destination: "JFK", departure_date: "2026-11-20" }, }), }); const { id: jobId } = await run.json(); // 202, with the job ``` For a write tool, use `POST /v1/runs` with an `Idempotency-Key` header, so a retry never acts on the site twice. ## Follow the job Read the job until it finishes or asks a question, waiting `Retry-After` seconds between reads: ```ts theme={null} async function follow(id: string) { for (;;) { const res = await fetch(`${api}/jobs/${id}`, { headers }); const job = await res.json(); if (job.status !== "queued" && job.status !== "running") return job; const seconds = Number(res.headers.get("retry-after") ?? 2); await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); } } const job = await follow(jobId); if (job.status === "succeeded") console.log(job.result); ``` A job with status `needs_input` waits on a person: send them its `input_request.answer_url`. ## Receive a webhook Instead of polling, create a webhook once: ```bash theme={null} curl https://api.pomerado.ai/v1/webhooks \ -H "Authorization: Bearer $POMERADO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/pomerado/events", "events": ["job.needs_input", "job.succeeded", "job.failed"]}' ``` Store the `secret` in the answer; it's shown once. Verify each delivery with a Standard Webhooks library, answer with a `2xx` within 10 seconds, then read the job, since events never carry a result: ```ts theme={null} import { Webhook } from "standardwebhooks"; const webhook = new Webhook(process.env.POMERADO_WEBHOOK_SECRET!); app.post( "/pomerado/events", express.raw({ type: "application/json" }), async (req, res) => { const event = webhook.verify( req.body, req.headers as Record, ) as any; res.sendStatus(204); if (event.type === "job.succeeded") { const job = await ( await fetch(`${api}/jobs/${event.data.job_id}`, { headers }) ).json(); console.log(job.result); } }, ); ``` > [accordion] Details > > * A result is kept until its first read, and at most 60 minutes after the job ends, or, for a run whose repair is still confirming the result it kept, at least 60 minutes after that repair ends (never past 24 hours). Only the API key that started the job can read it; `include_result=false` checks the job without using up the result. > * An event can arrive more than once. Drop repeats by its `id`. > * More in [webhooks](/guides/notifications/webhooks), [wait for a result](/guides/jobs/wait-for-a-result) and the [API reference](/api-reference/introduction). # Use Pomerado in chat Source: https://docs.pomerado.ai/examples/personal-assistant Use the Pomerado MCP in a chat client to look up and compare data across a site, and to build new tools. Use the Pomerado MCP in a chat client as a personal assistant for tasks on websites. This guide uses Instacart. ## Connect In a chat client that supports custom MCP connectors, add `https://mcp.pomerado.ai/mcp` and sign in. To try it without an account, add `https://mcp.pomerado.ai/mcp/keyless` instead. See [client setup](/guides/connect/client-setup). ## Compare prices across stores Ask a question that needs a lot of data at once: > Compare the prices of eggs, oat milk, bananas and coffee at the Instacart stores near 94103, and tell me where my list costs least. The agent finds an Instacart search tool with `find_website_tool`, then calls `run_read_only_website_tool` once for each item, all at the same time: ```json theme={null} { "intent": "Check the price of oat milk at nearby stores to find where a grocery list costs least", "tool_id": "tool_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "input": "{\"query\": \"oat milk\", \"zip\": \"94103\"}" } ``` Read-only runs change nothing on the site, so they can run in parallel. Each usually returns in seconds, and the agent combines the results into one answer. A search that needs no Instacart login also works on the keyless connection, within its daily limit. ## Build a missing tool When no tool covers a task, ask the agent to build one: > Build a tool that lists my past Instacart orders. The agent calls `build_website_tool`, tells you it's building, and follows the job. A build needs you to be signed in to Pomerado. If it needs your Instacart login, the agent gives you a link to a protected page: enter the login there, never in chat. ## Track the build * **In chat:** the agent calls `get_job` with `wait_seconds`. Each call returns when the job asks a question or finishes, or when the wait ends; the agent calls again while it runs. * **On the Dashboard:** open the job's `watch_url`. * **Elsewhere:** create a webhook on the Dashboard under Settings, then Webhooks, to be told when a job asks a question (`job.needs_input`) or finishes (`job.succeeded`, `job.failed`). When the job succeeds, its `tool_id` names the new tool, and the agent can run it right away. > [accordion] Details > > * How long one call can wait depends on the client. See [wait for a result](/guides/jobs/wait-for-a-result). > * Clients that support MCP events get the same job events without a webhook. See [MCP events](/guides/notifications/mcp-events). > * Set up a webhook in [webhooks](/guides/notifications/webhooks). # Add a site to your agent Source: https://docs.pomerado.ai/examples/product-agent Give your own product's agent one site's tools through that site's integration MCP. Connect your product's agent to one site's integration MCP, instead of the Pomerado MCP, to give it only that site's tools. This guide uses a Google Flights integration. ## Get the URL and a key On the Dashboard, open the Google Flights integration. Its Connect panel gives the MCP URL. Choose An API key and copy a setup: the copy creates an integration key, which reaches only this integration's tools and jobs. Keep the key in your secret manager. ## Connect your agent Connect your agent's MCP client over Streamable HTTP, with the key as a bearer token: ```json theme={null} { "mcpServers": { "google-flights": { "type": "http", "url": "https://mcp.pomerado.ai/mcp/integrations/int_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "headers": { "Authorization": "Bearer ${POMERADO_API_KEY}" } } } } ``` ## Call its tools The MCP lists the site's tools by their own names, such as `search_flights`, plus `get_job`, `answer_job` and `cancel_job`. A site tool takes its input as an object: ```json theme={null} { "name": "search_flights", "arguments": { "intent": "Search flights from SFO to JFK to find options for a November trip", "input": { "origin": "SFO", "destination": "JFK", "departure_date": "2026-11-20" } } } ``` The call returns the job, usually finished in seconds. If it's still running, follow its `next` (`get_job` with `wait_seconds`). ## Handle a question A job can stop with status `needs_input` and an `input_request`, which holds a message, its questions and an `answer_url`. For a plain question, such as a choice, your agent asks the user, then calls `answer_job` with an answer for each question ID: ```json theme={null} { "job_id": "job_6f1d0c2b3a4e45f8a9b7c6d5e4f3a2b1", "input_request_id": "req_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "answers": "{\"cabin\": \"economy\"}" } ``` For a login, a code or another secret, give the user the `answer_url`, a protected page that keeps the value out of your agent's conversation. Then keep waiting with `get_job`. Either way the same job continues. Never start it again. > [accordion] Details > > * Answer before the request's `expires_at`, or the job fails. > * An integration key can run tools and follow, answer and cancel jobs. Building a tool or managing a login needs an account key. > * More in [integration MCPs](/guides/integration-mcps/overview), [answer questions](/guides/jobs/answer-questions) and the [integration MCP reference](/mcp-reference/integration-mcp). # Manage API keys Source: https://docs.pomerado.ai/guides/authentication/api-keys Create, rotate and revoke the keys programs and agents use, including integration keys. An API key is a long-lived token for programs and agents that can't sign in with OAuth. This page covers creating, rotating and revoking keys. A key starts with `pom_`. Send it as `Authorization: Bearer pom_...` on the REST API or any Pomerado MCP. Each key belongs to you and to one account. ## Create a key Create a key on the Dashboard's **Settings** > **API keys** page, by copying a setup that uses a key on the **Connect agent** page, or with `POST /v1/api-keys`. * **Permissions.** By default, a key carries every permission you hold except `api_keys:manage`, `billing:manage` and `team:manage`. To include those, or to narrow the key, name 1 to 16 permissions. * **Expiry.** A key lasts 90 days unless you choose 1 to 365 (a connect key, below, starts with 1 day). Once it expires, requests with it answer `401 unauthorized`. * **Secret.** The secret is shown once. Keep it in a secret manager or an environment variable, never in a file you commit or share. A key that creates other keys needs `api_keys:manage` itself: ```bash theme={null} curl -X POST https://api.pomerado.ai/v1/api-keys \ -H "Authorization: Bearer $POMERADO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Nightly price check", "expires_in_days": 30}' ``` > [!NOTE] > > To keep a secret out of an agent's conversation, send `"deliver": "dashboard"`. The answer is a Dashboard link where you finish creating the key and see the secret. ## Integration keys An integration key works for one integration only. Create it with `integration_id`. It reaches only that integration's MCP, its tools and the jobs it started, and carries at most `tools:read`, `runs:create`, `jobs:read` and `jobs:cancel`. Use one to give a product agent a single site, such as Google Flights, and nothing more. ## Connect keys The Dashboard's **Connect agent** page and an integration's **Connect** tab make a key each time they open, with `"purpose": "connect"`. The key is listed only once you copy something that carries it (the Dashboard then calls `POST /v1/api-keys/{id}/copied`) or a request uses it; from then on it is yours like any other key. A connect key expires a day after it is made unless you copy something that carries it or a request uses it first; its first copy then keeps it for 90 days from then, and so does a use. Copying a connect key that has already expired answers `404 not_found`, and the Dashboard makes a new one. `expires_in_days` is ignored with `purpose` `connect`. A connect key that expires this way is gone and the Dashboard makes a new one next time the page opens. Making a connect key revokes your older connect keys that were never copied or used, for the same MCP, beyond the newest three, and when you hold 50 keys any new key first revokes your oldest such key, so these keys never fill your limit. A key that was copied or used, or one made without a purpose, is never revoked this way. You can make up to 20 connect keys an hour; past that, creating one answers `429 api_key_create_throttled`. ## Rotate a key `POST /v1/api-keys/{id}/rotate` replaces a key's secret. The old secret stops working at once. The key keeps its ID, permissions, expiry and running jobs. ## Revoke a key Revoke a key on the API keys page, or with `DELETE /v1/api-keys/{id}`. Requests with it are refused from then on, and the jobs it started stop. Revoke and replace any key that may have been exposed. > [accordion] Details > > * A key never holds more than you do. If your role loses a permission, or you leave the account, the key loses it too. > * A request the key's permissions don't cover answers `403 key_permission_missing`, naming the permission and linking to the API keys page; one your role doesn't allow answers `role_permission_missing`. See [permissions](/guides/authentication/permissions#when-a-permission-is-missing). > * On the Pomerado MCP, `search_pomerado_api` and `describe_pomerado_api` list what the key can't use with the same reason, after what it can. > * A key without `tools:read` can't connect to an MCP; the `403` challenge's `error_description` says which permission to add. > * The API keys page says in plain words what each key can and can't do. > * On the Connect page, a Business Owner or Admin can tick "Let this key manage the team" to add `team:manage` to the key it creates. > * You can hold up to 50 keys you haven't revoked. > * A key never creates, rotates or revokes a key with a permission it lacks. It may always revoke itself. > * A key can't reveal a saved login's password or read its authenticator code. Those answer a Dashboard link. > * From an MCP client, use `call_pomerado_api` with `api_keys.create`, `api_keys.rotate` or `api_keys.revoke`. # Sign in with OAuth Source: https://docs.pomerado.ai/guides/authentication/oauth Connect an MCP client by signing in through its browser flow, and disconnect it later. OAuth lets an MCP client sign you in to Pomerado without handling a key. This page covers signing in, connected apps and disconnecting an app. ## Sign in **1. Add the MCP without a key** Follow [client setup](/guides/connect/client-setup) and choose to sign in with OAuth. **2. Sign in and approve** The client opens a browser. Sign in to Pomerado with Google, GitHub or email, then approve the client. Your first sign-in creates an account. **3. Refresh the tools** The client lists the MCP's tools once sign-in succeeds. A connected app can do what your role allows in the account you signed in to, and Pomerado checks your account and role on every request. It can use your saved logins in jobs but never reveal their passwords. See [permissions](/guides/authentication/permissions) for what a token reaches. ## See connected apps The Dashboard's **Settings** > **Connected apps** page lists your connected apps. Each shows the app's name, the Pomerado MCPs it reaches, the permissions it has used, and when it first and last used Pomerado. Programs can list them with `GET /v1/connected-apps`. Apps that use an API key aren't listed; see [API keys](/guides/authentication/api-keys) for those. ## Disconnect an app Disconnect an app on that page, or with `DELETE /v1/connected-apps/{id}`. Its access ends at once on every Pomerado MCP it reaches, and the jobs it started stop. To use the app again, sign in from it again. > [accordion] Details > > * From an MCP client, the same operations are `connected_apps.list` and `connected_apps.revoke` through `call_pomerado_api`. > * An API key can list and disconnect apps only if it carries `api_keys:manage`, and only an app whose used permissions the key holds. > * If a client's authorization expires or is revoked, reconnect from the client. # Support OAuth in your client Source: https://docs.pomerado.ai/guides/authentication/oauth-for-client-developers How an MCP client discovers Pomerado's authorization server and gets a token for an MCP. This page is for people building an MCP client. Pomerado's MCPs follow the MCP authorization specification: a client discovers the authorization server from the MCP's protected resource metadata. ## Discover and sign in **1. Get the challenge** Send an MCP request without credentials, or with an expired token. The MCP answers `401` with a `WWW-Authenticate` header that names `resource_metadata`. **2. Read the metadata** Fetch that URL. It needs no credentials. **3. Authorize** Use the authorization server the metadata names. It supports dynamic client registration and client ID metadata documents. Request the token for the exact `resource` value. **4. Call the MCP** Send the token as `Authorization: Bearer` on every `POST` to the MCP URL. ```http theme={null} HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource_metadata="https://mcp.pomerado.ai/.well-known/oauth-protected-resource/mcp" ``` ```json theme={null} { "resource": "https://mcp.pomerado.ai/mcp", "authorization_servers": ["https://..."], "bearer_methods_supported": ["header"] } ``` | Field | Meaning | | - | - | | `resource` | The MCP URL, used as the token's audience | | `authorization_servers` | The authorization server to use | | `bearer_methods_supported` | `header`: send the token in the Authorization header | | `scopes_supported` | Present only when Pomerado publishes OAuth scopes; often absent | An integration MCP's metadata is at `/.well-known/oauth-protected-resource/mcp/integrations/{integration_id}` on the same host. ## Rules * Read the authorization server from the metadata. Don't hard-code it. * Use the MCP URL exactly, without a trailing slash or query string. * Each MCP is its own resource. See [permissions](/guides/authentication/permissions#where-a-token-works) for where a token works. * A `403` whose `WWW-Authenticate` header has `error="insufficient_scope"` means the credential isn't allowed here. Request any `scope` the challenge names. When an API key or your team role is why, an `error_description` says which permission it lacks. * The MCP takes `POST` only. A request with an `Origin` header must come from an allowed origin. * A URL ending in `/keyless` never sends the challenge. A client there signs in with `sign_in` or connects to the signed-in URL. A client may also skip OAuth and send an API key as the bearer token. See [API keys](/guides/authentication/api-keys). # Understand authentication Source: https://docs.pomerado.ai/guides/authentication/overview The two levels of sign-in: who you are to Pomerado, and who Pomerado is on a website. Authentication in Pomerado has two separate levels, and this page explains both. | Level | Answers | Credentials | | - | - | - | | Pomerado authentication | Who you are to Pomerado | OAuth sign-in, API keys, integration keys | | Website logins | Who Pomerado is on a website | Saved logins | ## Pomerado authentication Every request to Pomerado carries one credential, sent as `Authorization: Bearer`: * **OAuth sign-in.** Your MCP client signs you in through a browser, with no key to handle. Best for chat and coding agents. See [sign in with OAuth](/guides/authentication/oauth). * **API key.** A `pom_` token for programs and headless agents. See [API keys](/guides/authentication/api-keys). * **Integration key.** An API key limited to one integration. See [integration keys](/guides/authentication/api-keys#integration-keys). An agent without a browser can get an API key by having you approve a short code. See [sign in with a code](/guides/authentication/sign-in-with-a-code). Whatever the credential, it acts for one account and never with more than your role allows. See [permissions](/guides/authentication/permissions). On the Dashboard, your session renews itself, so you sign in again only after it ends or you sign out. Only deleting the account and changing the password ask for a sign-in within the last five minutes. ## Website logins A website login is the account Pomerado signs in with on a site, such as your Instacart account. You save it once in the Dashboard, or on the protected page a job links to when it needs one, and jobs use it from then on. See [saved logins](/guides/logins/overview). ## How the two relate The levels stay separate. Your Pomerado credential decides which saved logins a job may use, but it never holds a website password. Pomerado asks for a password only on its own protected pages, so never paste one into a chat or a request, and no API key or OAuth app can reveal one. # Understand permissions and scope Source: https://docs.pomerado.ai/guides/authentication/permissions What each permission allows, and what limits the permissions a credential carries. Every request to Pomerado runs with a set of permissions. This page lists them and explains what limits a credential. ## Permissions | Permission | Allows | | - | - | | `tools:read` | Find tools and integrations, and read MCP setup | | `tools:build` | Build tools, delete tools and change an integration's visibility | | `runs:create` | Run tools and upload files for runs | | `jobs:read` | Read jobs and your team's run logs (Business), answer their questions and manage webhooks | | `jobs:cancel` | Cancel jobs | | `logins:read` | List and read saved logins | | `logins:manage` | Save, change, delete, import and share saved logins | | `usage:read` | Read usage and quota | | `api_keys:manage` | Manage API keys and connected apps with an API key | | `billing:manage` | Manage a Personal account's plan with an API key | | `team:manage` | Manage a Business account's members and invitations | Each page in the [API reference](/api-reference/introduction) names the permission its operation needs. ## What limits a credential A credential's permissions are the smallest of three things: * **Your role.** A Business Member can't manage the team. See [roles and invitations](/guides/teams/roles-and-invitations). * **The credential.** An API key carries the permissions chosen when it was created, and an integration key at most four. * **The account.** A credential acts for one account: the one its key was created in, or the one you chose when signing in. Changes take effect at once. If your role loses a permission, or you leave the account, every key and connected app loses it too, including for jobs still waiting to start. ## Where a token works * An integration MCP reaches only its own integration's tools, even with an account key or an account-wide sign-in. * An integration key and an integration MCP's OAuth token work only on that MCP. Any other answers `403 forbidden`, with `error="insufficient_scope"` in its `WWW-Authenticate` header. * A Pomerado MCP token works on the REST API too, with the same permissions and jobs. ## When a permission is missing The request answers `403` and nothing changes. On the REST API, MCP and the Dashboard alike, the error says why: * `key_permission_missing`: your role allows it but the API key doesn't. `details.permission` names the permission and `details.keys_url` links to the API keys page, where you create a key that includes it. * `role_permission_missing`: your role in the Business team doesn't allow it, whether you use an API key, a connected app or the Dashboard. A team Owner or Admin can do it, or change your role. * `team_unavailable`: the account's plan has no team, for a team operation. * `business_account_required`: only Business accounts can do it, such as making an integration private. `details.contact_email` is the address to email to set one up. * `billing_unavailable`: the account has no self-service billing. A Business account's plan is through its agreement with Pomerado; email `details.contact_email`. An internal account isn't billed. * `sms_number_not_allowed`: assigning or verifying a Pomerado phone number, which comes with a Business account; email `details.contact_email` to set one up. * `forbidden`: anything else, such as an integration key outside its integration (`details.reason` is `integration_key`). Something your account can't see answers `404 not_found` whatever the key carries. Creating or rotating a key with a permission you don't hold answers `permission_not_held`. See [errors](/errors). You can learn the reason before calling. On the Pomerado MCP, [`search_pomerado_api`](/mcp-reference/pomerado-mcp/search_pomerado_api) lists the operations you can use first, then each one your plan, role or key doesn't allow, with `unavailable`: the error its call would answer. [`describe_pomerado_api`](/mcp-reference/pomerado-mcp/describe_pomerado_api) adds the same `unavailable` to an operation you can't use. Operations only the Dashboard calls are never listed. An API key without `tools:read` can't connect to an MCP. The `403` keeps the standard `error="insufficient_scope"` challenge and adds an `error_description` that names the missing permission and links to the API keys page. # Sign in with a code Source: https://docs.pomerado.ai/guides/authentication/sign-in-with-a-code Let an agent without a browser get an API key by having a person approve a short code. An agent with no browser can sign in with a short code that a person approves on any device. The result is an API key for that person's account. ## Sign in an agent **1. Start a sign-in** The agent calls `sign_in` on any Pomerado MCP, or `POST /v1/sign-in`. The answer has `verification_url`, `user_code`, `device_code`, `interval` and `expires_at`. **2. Approve the code** The agent gives the person `verification_url` and `user_code`. The person opens the link, signs in with Google, GitHub or email (or signs up for free, if they have no account), enters the code and approves. **3. Poll for the key** Every `interval` seconds, the agent calls `sign_in` with `device_code`, or `POST /v1/sign-in/poll`. The status stays `pending` until the person approves. **4. Use the key** The poll returns the key's `secret` once. The agent stores it and sends it as `Authorization: Bearer` from then on. On the REST API: ```bash theme={null} curl -X POST https://api.pomerado.ai/v1/sign-in curl -X POST https://api.pomerado.ai/v1/sign-in/poll \ -H "Content-Type: application/json" \ -d '{"device_code": ""}' ``` ## The key it creates The key carries everything your role allows except managing API keys, billing and the team, so the agent can find, build and run tools, follow its jobs, and use and save your logins. It lasts 30 days. To ask for longer, start the sign-in with `expires_in_days`, from 1 to 90. Revoke it at any time on the Dashboard's **Settings** > **API keys** page. Approving needs the `api_keys:manage` permission, because approving creates an API key. > [!NOTE] > > Approve only a code you started yourself. Approving gives that agent access to your account. > [accordion] Details > > * The code expires after 10 minutes. A denied code answers `access_denied`, and an expired or used one `expired_token`. > * Polling faster than `interval` returns the status `slow_down` and a longer interval. On REST, a pending poll answers `202` with `Retry-After`. > * On an MCP's keyless URL, send the new key to the signed-in URL, without `/keyless`. > * Too many sign-ins started from one network in a day answers `sign_in_limit_reached`. Where device sign-in isn't available, it answers `sign_in_unavailable`: sign in with OAuth or create a key in the Dashboard instead. # Choose an effect Source: https://docs.pomerado.ai/guides/build/choose-an-effect Tell a build whether its tool only reads a website or changes it. Every website build needs an `effect`: whether the tool only looks things up or changes something on the site. It decides what the build may do on the website and whether agents may run the finished tool without asking. With `ask`, Pomerado works it out from the task and asks you only when the task leaves it unclear. ## Pick read, write or ask | Effect | Use it when the tool | Example | | - | - | - | | `read` | Only looks things up, including through a search, filter or query form | Search Google Flights for fares | | `write` | Changes the website: submits, books, saves a form, uploads or updates an account | Add items to an Instacart cart, or check out | | `ask` | You're not sure | | A form that saves data counts as a write, even before its last step. Drafts and holds are writes too. ## What each effect does * **Read:** the build runs your example and changes nothing. The tool then runs with the read-only run, in parallel. See [Run a tool](/guides/tools/run-tools). * **Write:** the build makes the change once, for real, with your example input, then publishes the tool without running it again. Choose an example you actually want done. * **Ask:** before anything runs, the build works out read or write from the task. A task that plainly changes something on the site, such as submitting, booking or saving, builds as a write. One that plainly only looks things up builds as a read. When the task leaves it unclear, the build asks read or write as a question on its job. Answer it as in [Answer a job's questions](/guides/jobs/answer-questions). > [!NOTE] > > A read build that finds its task needs a change on the website stops and asks the same question before it switches to a write. > [accordion] Details > > * A build with a `site_url` but no `effect` creates no job and answers `effect_required`. > * Send an `idempotency_key` with a write build, so a retry never makes the change twice. See [Retry a call safely](/guides/tools/retries-and-idempotency). > * A write or ask build given a login tries signed out first, as a read does, and signs in when the task needs the account. See [Sign in during a build](/guides/build/sign-in-during-a-build). > * Set `test_mode` to `"on"` unless your user explicitly says not to. It is optional and strongly recommended. A write build submits once, with `example_input`, and leaves no drafts. Before it publishes, it also runs a few other inputs, such as another city, date or party size, and retries the form's earlier steps with other answers to find the fields they open, each stopping before anything saves. It takes longer. These runs never pay, purchase, sign or change sign-in. > * For the fullest map of a form, set `test_mode` to `"extreme"` only when your user asks for it. It runs on any site. Before the tool publishes, the build explores the form with hundreds of answers on practice drafts filled with made-up people, which takes hours. It leaves up to 5 practice drafts on the account and may submit the form's final step for real up to 3 times, your example's submit included, only where that step buys or signs nothing, such as getting a quote. The site may rate-limit or flag the account. It never pays, purchases, signs or changes sign-in. Before it touches the site, the build asks your user to confirm in its `input_request` and runs as `"on"` if they say no. The job's `extreme_test_mode` warning says the same. # Check the example result Source: https://docs.pomerado.ai/guides/build/example-result Read what a finished build's example returned, then run the new tool. A finished build keeps the result of the example it ran, so you can check the new tool did the right thing before you rely on it. ## Read the example result When the build's job succeeds, `build.example_result` holds the example: * For a read, the value the tool returned, such as the Google Flights fares it found. * For a write, what the build read on the website after making the change, such as Instacart's order confirmation. ```json theme={null} { "status": "succeeded", "tool_id": "tool_9c1d4e7f2a3b4c5d8e6f7a8b9c0d1e2f", "build": { "integration_id": "int_3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e", "example_result": { "fares": [{ "airline": "Example Air", "price": "$189" }] }, "existing_tool_id": null } } ``` Like any result, the example result is delivered once, so save it when you receive it. > [!NOTE] > > A write build already made its change. Don't run the new tool with the same input to test it, or you'll make the change twice. ## Run the new tool Pass the job's `tool_id` to a run, as in [Run a tool](/guides/tools/run-tools). On an integration MCP, list its tools again: the new tool appears under its own name. ## When the build found an existing tool `build.existing_tool_id` names the tool that already covers the task, and `tool_id` is the same. Nothing new was published; run that tool instead. > [accordion] Details > > * The new tool joins its website's integration, `build.integration_id`. See [Use integration MCPs](/guides/integration-mcps/overview). > * To remove a tool you built, call `DELETE /v1/tools/{id}`. It stops running everywhere. # Build a tool Source: https://docs.pomerado.ai/guides/build/overview Build a new tool when no existing tool does your task on a website. Build a tool when [Find a tool](/guides/tools/find-tools) turns up nothing for your task. A build explores the website, writes the tool, runs your example once and publishes it. After that the tool runs in seconds. ## Start a build Describe the task, name the website and its effect, and give one example input. Over REST, send them to `POST /v1/builds`, with your [idempotency key](/guides/tools/retries-and-idempotency) in the `Idempotency-Key` header: ```bash theme={null} curl https://api.pomerado.ai/v1/builds \ -H "Authorization: Bearer $POMERADO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: build-oat-milk-search" \ -d '{"task": "Search Instacart for a product and return the top results with name, size and price.", "site_url": "https://www.instacart.com", "effect": "read", "example_input": {"query": "oat milk"}}' ``` | Field | Required | What to send | | - | - | - | | `task` | Yes | What the tool should do, in plain words, up to 20,000 characters | | `site_url` | For a website build | The website's https origin; a path is dropped | | `effect` | With `site_url` | `read`, `write` or `ask`. See [Choose an effect](/guides/build/choose-an-effect) | | `example_input` | No | The input for the one real example the build runs, as a JSON value; `{}` when left out. A file is its [upload's ID or a `data:` URI](/guides/files/send-a-file#give-a-build-an-example-file) | | `start_url` | No | An https page on `site_url` that the person gave, opened unchanged | The body takes only the fields the [API reference](/api-reference/introduction) lists for this operation, such as `login_id` and `force_sign_in`; any other field is refused with `invalid_request`. ## Start a build on MCP `build_website_tool` takes the same fields as arguments, plus `idempotency_key` in place of the header and `intent`, one sentence on what the call is for and why. Its `example_input` is a JSON-encoded string: ```json theme={null} { "name": "build_website_tool", "arguments": { "intent": "Build a tool to compare grocery prices by searching Instacart for a product", "task": "Search Instacart for a product and return the top results with name, size and price.", "site_url": "https://www.instacart.com", "effect": "read", "example_input": "{\"query\":\"oat milk\"}", "idempotency_key": "build-oat-milk-search" } } ``` On an integration MCP, `build_tool` does the same for that integration's website and takes no `site_url`. REST takes neither `intent` nor `idempotency_key` in the body. ## Follow the build The call answers its job at once. Tell the person the tool is building, then [wait for the job](/guides/jobs/wait-for-a-result). A build may stop to ask a question, such as which login to use; see [Answer a job's questions](/guides/jobs/answer-questions). While it runs, the job's `build` field shows its stage and an estimate. ## When it ends * **Published:** the job succeeds with the new tool's `tool_id`. See [Check the example result](/guides/build/example-result). * **Already covered:** an existing tool does the task. The job succeeds with it as `tool_id` and `build.existing_tool_id`, and publishes nothing. * **Blocked:** the build found the task impossible on this site. It fails with `build_blocked`; show its `message` to the person. > [accordion] Details > > * A `site_url` that isn't a public website's https origin, such as an IP address or a local network name, creates no job and answers `invalid_request`, as does an `effect` of `write` or `ask` without a `site_url`. A `site_url` without an `effect` answers `effect_required`. > * Builds for ticket, bank, government or link shortener websites are refused with `site_not_supported`. > * A published build counts toward your plan. See [Plans and limits](/guides/plans-and-limits). > * A new tool joins its website's integration, named in `build.integration_id`. See [Use integration MCPs](/guides/integration-mcps/overview). # Sign in during a build Source: https://docs.pomerado.ai/guides/build/sign-in-during-a-build How a build decides whether to sign in to the website, and with which login. A build signs in to the website only when its task needs an account. For saving logins, see [Logins](/guides/logins/overview). ## When a build signs in A build tries its task signed out first, even when you give it a login. It signs in only when the task needs an account or the site shows a sign-in wall, and the finished tool then requires a login. A tool built signed out never does. A cart tool, such as one that adds to the cart, signs in, since its cart belongs to the account; one that fills the cart and checks out in the same run may use the site's guest checkout. To make a build sign in first, set `force_sign_in` to `true`; the tool then always signs in. ## Which login it uses 1. **A login you name:** pass `login_id` to use one saved login. 2. **The site's saved login:** the build uses the one saved login, or asks which when several could sign in. 3. **A login the person enters:** with none saved, the job asks on its answer page, which can save the login too. Never ask for a password in chat. When the job asks for a login, give the person its `answer_url` and keep waiting. See [Answer a job's questions](/guides/jobs/answer-questions). ## When the website rejects a login The job reports `needs_input` with the error `credentials_rejected`, and waits up to 60 minutes for someone who can edit that login to correct it. It then resumes as the same job; don't start the build again. > [accordion] Details > > * You can pass a `login` inline instead, but its values pass through your client and its model provider. The answer page keeps them out of the conversation. > * A build that may have changed the website before its login was rejected says so with `error.details.possible_commit`. When it resumes, it checks the website first and never repeats a change that happened. > * Sign-in codes are covered in [two-factor sign-in](/guides/logins/2fa). # Set up an MCP client Source: https://docs.pomerado.ai/guides/connect/client-setup Add a Pomerado MCP to Claude Code, Codex, Cursor, VS Code, Claude Desktop or another client. This page shows how to add a Pomerado MCP to an MCP client. For the setup that fits your account, open **Connect agent** in the Dashboard's sidebar. Each integration's page has the same panel for its own MCP. ## Get your setup **1. Choose your client** Pick it under **Your agent**. **2. Choose how it signs in** **An API key**, the default, puts a key made for the page into the setup, so there's nothing to sign in to. **Your Pomerado account** signs you in with OAuth the first time the client connects, and stores nothing. Claude Desktop signs in with your account only. **3. Copy the setup** On the Connect agent page, **Let your agent do it** gives a setup prompt to paste into your agent: it adds the server with your key and runs one read-only tool to check. The other tab has the command or config to run or paste yourself, with the key already in it. With your Pomerado account instead, sign in when the client asks and refresh its tool list. Programs can read the same setup from `GET /v1/connect`. > [accordion] Details > > * The page creates its key when it opens and reuses it while you move between Connect pages in the tab. The right column shows it masked, with copy and Revoke. Unused keys a Connect page made are revoked once you have more than three for the same MCP, and a key an agent has used is never revoked this way. > * The setup prompt carries the key and tells your agent how to add the MCP with it. Each command or config sends it as an `Authorization: Bearer` header. Codex reads a bearer token only from a variable, so its command first saves the key in your shell profile: as `POMERADO_API_KEY` for the Pomerado MCP, and as the integration's own variable, such as `POMERADO_GOOGLE_FLIGHTS_API_KEY`, for an integration. VS Code's `.vscode/mcp.json` then holds the key, so keep it out of git. > * A key from the Connect agent page carries every permission your role allows except managing API keys, billing and the team. A key from an integration's Connect panel reaches only that integration's tools and jobs. Both last 90 days. > * Home has a one-line Connect bar: **Quick add** shows the Claude Code command, **Set up** opens Connect agent, and dismissing it hides it for you on every device. ## Common clients With OAuth, for the Pomerado MCP: | Client | Setup | | - | - | | Claude Code | `claude mcp add --transport http pomerado 'https://mcp.pomerado.ai/mcp'`, then run `/mcp` and sign in | | Codex | `codex mcp add pomerado --url 'https://mcp.pomerado.ai/mcp'`, which starts the sign-in | | Cursor | `{"mcpServers": {"pomerado": {"url": "https://mcp.pomerado.ai/mcp"}}}` in Cursor's MCP settings | | VS Code | `{"servers": {"pomerado": {"type": "http", "url": "https://mcp.pomerado.ai/mcp"}}}` in `.vscode/mcp.json` | | Claude Desktop | Settings, Connectors, Add custom connector, with the name and URL. OAuth only. | With an API key, the setup sends an Authorization header that reads the variable. For Claude Code: ```bash theme={null} claude mcp add --transport http pomerado 'https://mcp.pomerado.ai/mcp' \ --header 'Authorization: Bearer ${POMERADO_API_KEY}' ``` Set the variable where the client runs, then restart the client. On macOS, an app opened from the Dock or Finder doesn't read your shell profile, so open the client from a terminal. ## Troubleshoot | Problem | Fix | | - | - | | `401 unauthorized` | Sign in again, or replace a revoked or expired key. To use OAuth instead, remove the key from the client's configuration. | | `405` when you open the URL in a browser | Expected: an MCP takes POST requests from a client. | | `400 keyless_url_credentials` | You sent a key to a URL ending in `/keyless`. Remove `/keyless` from the URL. | | `404 not_found` on an integration | Copy the integration's URL again from its page. | Some clients cut long tool calls short; see [wait for a result](/guides/jobs/wait-for-a-result). [Errors](/errors) lists every code. # Choose a connection Source: https://docs.pomerado.ai/guides/connect/connection-options Pick the Pomerado MCP, an integration MCP or the REST API for your agent or program. Pomerado has three entry points, and this page helps you pick one. All three reach the same tools, jobs and account. | Connection | Use it when | Address | | - | - | - | | Pomerado MCP | An agent works across many sites | `https://mcp.pomerado.ai/mcp` | | Integration MCP | An agent needs only one site's tools | `https://mcp.pomerado.ai/mcp/integrations/{integration_id}` | | REST API | Your own program calls Pomerado without an MCP client | `https://api.pomerado.ai/v1` | ## Pomerado MCP Connect a chat or coding agent here when it should handle tasks on any site. It finds a tool for the task, builds one when none exists, runs it and follows the job. The rest of the API, such as saved logins, API keys, webhooks and usage, is reachable through `search_pomerado_api`, `describe_pomerado_api` and `call_pomerado_api`. See the [personal assistant example](/examples/personal-assistant). ## Integration MCP An integration is one site's tools published as its own MCP, such as a Google Flights MCP. Its tools appear under their own names, beside `build_tool`, `manage_login` and the job tools. Use it when your product's agent should see one site and nothing else. Copy its URL from the integration's page in the Dashboard, or from `GET /v1/connect`. Until the integration's MCP is ready, run its tools through the Pomerado MCP or the REST API. See [integration MCPs](/guides/integration-mcps/overview) and the [product agent example](/examples/product-agent). ## REST API Call the REST API from a backend: start a run, then read the job or receive a webhook. REST answers at once and never holds a request open, while an MCP call can wait for the result. See the [backend application example](/examples/backend-application) and the [API reference](/api-reference/introduction). ## Credentials Every connection takes an API key, and both MCPs can also sign you in with OAuth. See [authentication](/guides/authentication/overview) for which token works where. > [!NOTE] > > Each MCP also has a keyless URL, its address with `/keyless` added, for trying Pomerado without an account. See [try without a key](/quickstart/try-without-a-key). # Get a file from a run Source: https://docs.pomerado.ai/guides/files/get-a-file Download a file a run returns, once, from its single-use link. This page shows how to download a file a run collects, such as a statement. The run's result holds it as a `$file` object: ```json theme={null} { "statement": { "$file": { "id": "file_9c8b7a6f5e4d3c2b1a0f9e8d7c6b5a4f", "name": "statement-2026-09.pdf", "media_type": "application/pdf", "size": 182044, "sha256": "5f2d...", "download_url": "https://.../v1/file-downloads/...", "expires_at": "2026-10-08T17:30:00.000Z" } } } ``` ## Download the file Fetch `download_url` once, with no `Authorization` header: ```bash theme={null} curl -o statement.pdf "$DOWNLOAD_URL" ``` Save the body before anything else. Pomerado erases the file as soon as one download sends its last byte, whether or not your client saved it. Compare the bytes with `sha256` to confirm they are complete. A download that breaks off before its last byte may start again until `expires_at`. An undownloaded file is erased after 30 minutes. If Pomerado can't confirm a file's link at the moment you read the job, the read fails with `temporarily_unavailable` and the result is kept: read the job again. A link Pomerado didn't issue for that job, or one already used or expired, is left out, and the `$file` keeps only its description. ## On MCP A tool result that holds a `$file` follows the job's JSON with a line giving the file's download link and a `resource_link` to it. A PNG, JPEG, GIF or WebP image of up to 256 KB also comes as the image itself, so the client can show it. Showing the image doesn't use the link, which still downloads the file once. # Work with files Source: https://docs.pomerado.ai/guides/files/overview Send a file to a tool that takes one, or get back a file a run collects. Some tools take a file, such as a receipt to upload, or return one, such as a statement to download. Pomerado keeps each file encrypted, only as long as the run needs it, and never opens it. * [Send a file to a run](/guides/files/send-a-file): give a tool's file field a file ID, an https URL or a `data:` URI. * [Get a file from a run](/guides/files/get-a-file): download a file from the run's result. * [Give a build an example file](/guides/files/send-a-file#give-a-build-an-example-file): upload it and name its ID in `example_input`, or send it there as a `data:` URI. ## Who can use files Files need a signed-in caller: a Dashboard session, an MCP client signed in to Pomerado, or an API key with `runs:create`. A file belongs to the person who uploaded it; anyone else gets `not_found`. ## Limits | Limit | Value | | - | - | | One file | 25 MiB | | Files in one run | 10, and 50 MiB together | | A `data:` URI | 512 KB | | An unused upload | Erased after 1 hour | | A file a run used | Erased when its job ends | | A file a run returned | Erased after its first complete download, or after 30 minutes | | Every file of an account | Erased when the account is deleted | ## Accepted types Pomerado accepts PDF, common image, audio and video types, ZIP and office documents, and text formats such as plain text, CSV, Markdown, XML and JSON. `application/octet-stream` takes any other bytes. A file's first bytes must match its media type. Programs and scripts, such as `.exe`, `.sh` and `.js` files, are refused. > [!NOTE] > > Pomerado doesn't scan files for malware. Open a downloaded file with the care you'd give any other. > [accordion] Details > > | Code | Meaning | > | - | - | > | `file_too_large` | The file is larger than `details.max_bytes`. | > | `file_type_refused` | An executable, an unsupported type, or bytes that don't match the media type. | > | `file_unavailable` | The file expired, wasn't uploaded yet, or another run already took it. | > | `file_limit_exceeded` | The run has too many files, or too many bytes in total. | > | `file_fetch_failed` | Pomerado couldn't fetch a URL input; `details.reason` says why. | > > See [errors](/errors) for every code. # Send a file to a run Source: https://docs.pomerado.ai/guides/files/send-a-file Give a tool's file field an uploaded file, an https URL or a data URI. This page shows how to give a tool a file. A tool's file field is a string that takes one of three values: * **A file ID** (`file_...`) from an upload. Use this for any file larger than a few hundred kilobytes. * **An https URL.** Pomerado fetches it when you submit the run. * **A `data:` URI** of at most 512 KB, such as `data:application/pdf;base64,JVBERi0...`. ## Upload a file **1. Create the upload** Send `POST /v1/files` with the file's name and media type. In an MCP client, call `create_file_upload`, on the Pomerado MCP and every integration MCP; its answer adds the `curl -T` command for the `upload_url`. ```json theme={null} { "name": "receipt.pdf", "media_type": "application/pdf", "size": 48213 } ``` `size` is optional; when you give it, the upload must be exactly that long. The answer has the file's `id` and a one-time `upload_url`. **2. Send the bytes** PUT the file to `upload_url` once, with no `Authorization` header: ```bash theme={null} curl -T receipt.pdf -H "Content-Type: application/pdf" "$UPLOAD_URL" ``` The answer is the file, now `ready`. If the upload fails, send it again before the file expires. **3. Pass the ID to the run** Use the file's ID as the field's value when you [run the tool](/guides/tools/run-tools): ```json theme={null} { "input": { "receipt": "file_3b9e0c6f1a2d4e5f8a7b6c5d4e3f2a1b" } } ``` A file serves one run. `GET /v1/files/{id}` reads its metadata, never its bytes, and `DELETE /v1/files/{id}` erases it now. A tool's file field may state the site's own limits for the file, as `maxBytes` and `extensions` in its input schema, such as a résumé field that takes PDF or Word files up to 5 MB. Pomerado checks your file against them before the run starts and refuses one outside them with `invalid_input`, naming the field and the limit, so the run never stops partway through for a file the site would refuse. ## On MCP A tool's file fields sit at the top level of its parameters, next to `input`, and are listed in `_meta["openai/fileParams"]`, so ChatGPT can attach a file the user gave it; Pomerado fetches the attached file's `download_url` as it fetches any https URL. A file field named like one of the call's own parameters is called `file_` there. Each still takes a file ID, an https URL or a `data:` URI, and a list of files keeps its own limits, such as `maxItems`. ## Give a build an example file A [build](/guides/build/overview) runs one real example. When the example needs a file, upload it first and put its file ID anywhere in `example_input`, or put the file there as a `data:` URI of at most 512 KB: ```json theme={null} { "example_input": { "receipt": "file_3b9e0c6f1a2d4e5f8a7b6c5d4e3f2a1b" } } ``` The build takes the file as a run does and erases it when the build ends. A `data:` URI counts as a file when its type is not text or HTML, such as a PDF or an image, and Pomerado stores it like an upload. An https URL, or a text or HTML `data:` URI, stays the value it is, since it may be a page the task names. The model that builds the tool never sees the file's bytes or its ID, only its name, type and size. A file the example downloads gives the build only its name, type, size and hash, and is kept nowhere. A build answers the same `file_unavailable`, `file_limit_exceeded`, `file_too_large` and `file_type_refused` errors as a run. > [accordion] Details > > * A URL must be https on the default port, with no username or password, and resolve to a public address. It may redirect at most three times, must answer within 20 seconds and must send a `Content-Length`. > * Pomerado stores a URL's or data URI's bytes like an upload. > * See [limits](/guides/files/overview#limits) for sizes and expiry. # Connect an integration MCP Source: https://docs.pomerado.ai/guides/integration-mcps/connect Add one website's integration MCP to your agent's client. Connect an integration MCP to give your agent one website's tools. The steps match connecting the Pomerado MCP, with the integration named. ## Get the setup for your client **1. Open the integration** On the Dashboard, open the integration's page and its Connect panel. From code, call `GET /v1/connect` with `client` and `integration_id`. **2. Copy the snippet** The answer holds the MCP's `name`, its `mcp_url` and a `snippet`: the command to run or the config to paste for that client. **3. Sign in** With OAuth, the default, your client opens sign-in itself and the snippet holds no secret. `client` is `claude_code`, `codex`, `cursor`, `vscode`, `claude_desktop`, or `any` for a generic client's JSON config. Client-specific steps are in [Client setup](/guides/connect/client-setup). ## Use an API key instead Pass `auth=api_key` for a snippet that sends an API key from the `POMERADO_API_KEY` environment variable. Claude Desktop takes OAuth only. An API key bound to one integration reaches only that integration: it finds and runs its tools and follows their jobs, and nothing else. See [API keys](/guides/authentication/api-keys). ## Check that it has an MCP An integration's `status` says whether it has an MCP to connect. When it reads `disabled` or `not_hosted`, the integration has no MCP and the setup call answers `not_found`. Its tools still run through the Pomerado MCP and REST. > [accordion] Details > > * Use the exact URL the setup gives. A website's public and private integrations have different URLs. > * A public, ready integration also has a `keyless_mcp_url`, its URL with `/keyless` added, which works without signing in and lists only public read-only tools that need no login. See [Try without a key](/quickstart/try-without-a-key). > * A `disabled` or `not_hosted` integration has no MCP of its own; its setup answers `not_found`. # Use integration MCPs Source: https://docs.pomerado.ai/guides/integration-mcps/overview Give an agent one website's tools through that site's own MCP server and API. An integration is all of one website's tools, published as its own MCP server and REST API, so an agent that only needs Google Flights can connect to Google Flights alone. ## Choose how to connect Every tool belongs to its website's integration, and each new build for that site adds to it. The same tools also run through the Pomerado MCP. | Connect to | When your agent | | - | - | | The Pomerado MCP | Works across many websites, and finds or builds tools as it goes | | An integration MCP | Needs one website, with only that site's tools in its context | ## What an integration MCP lists * One tool per site tool, under its own name, such as `search_flights`. Each takes its `input` as a plain object and needs no `tool_id`. * `build_tool`, which adds a tool for this website. See [Build a tool](/guides/build/overview). * `manage_login`, on a signed-in connection, which shows whether a login for the site is saved and gives the person a link to save or replace it. The Pomerado MCP has no `manage_login`; see [check a site's login from an agent](/guides/logins/overview#check-a-sites-login-from-an-agent). * `get_job`, `answer_job` and `cancel_job`, to follow runs and builds. See [Wait for a result](/guides/jobs/wait-for-a-result). Read tools are marked read-only, so clients may run them in parallel. The full layout is in the [integration MCP reference](/mcp-reference/integration-mcp). ## Use its REST API * `POST /v1/integrations/{id}/tools/{tool_id}/runs` runs one of its tools. * `GET /v1/integrations/{id}/openapi.json` answers an OpenAPI document with one operation per tool. * `GET /v1/integrations/{id}/reference` answers a Markdown reference with setup for each client and a sample call for each tool. ## Find integrations `GET /v1/integrations` lists the public catalog; add `mine=true` for my integrations: those holding tools you built and the public ones you added. Through the Pomerado MCP, call `call_pomerado_api` with `integrations.list`. Each integration also has a page on the Dashboard. To add a public integration to my integrations, call `PUT /v1/integrations/{id}/added` (`integrations.add`); `DELETE` on the same path (`integrations.remove`) removes it. Both are safe to repeat. An added integration is a link, not a copy, so it lists all its tools as their creators publish them, and on a Business account your whole team sees it. ## Show its icon Each integration lists `icon_url`, its site's icon as a PNG on the API host, or null when it has none. Fetch it with the same `Authorization: Bearer` header as your other calls, using a key with `tools:read` (a key for one integration fetches that integration's icon only). Without credentials it answers `401`, and while the site has no captured icon it answers `404`. The URL changes whenever the icon may, so the icon it answers comes with `Cache-Control: private, max-age=31536000, immutable` and can be kept; a `404` is cached for a minute. ## Next steps * [Connect an integration MCP](/guides/integration-mcps/connect) * [Set an integration's visibility](/guides/integration-mcps/visibility) * [Manage team integrations](/guides/integration-mcps/team-integrations) # Manage team integrations Source: https://docs.pomerado.ai/guides/integration-mcps/team-integrations List, publish and delete the integrations your team has built. Your team's integrations are the ones its members built, private and public, and the public ones a member added. A Personal account's list is its own. ## List them On the Dashboard, open Integrations. Over REST, call `GET /v1/integrations/team`; through the Pomerado MCP, call `call_pomerado_api` with `integrations.list_team`. Each integration lists the tools your team built in it, or all its tools when your team added it, its `visibility`, `owned_by_me`, which says whether you built one of them, and `added` and `built`, which say whether your team added it or built a tool in it. To publish or unpublish one, see [Set an integration's visibility](/guides/integration-mcps/visibility). ## Delete one Deleting an integration removes the tools you may delete from it: | You are | You delete | | - | - | | A Business Owner or Admin | Every tool your team built there | | A Business Member | The tools you built | | The owner of a Personal account | Your tools | Over REST, call `DELETE /v1/integrations/{id}/tools`; through the Pomerado MCP, `integrations.delete`. To delete one tool, call `DELETE /v1/tools/{id}`. > [!NOTE] > > Deleting can't be undone. Each tool stops running everywhere at once, including runs already started. Confirm with the person first. An integration left with no tools is removed with its MCP. Other accounts' tools are never deleted, and an integration that still holds them stays. > [accordion] Details > > * An Owner or Admin can delete a teammate's tools, so an integration whose builder left the team can still be removed. > * To give a backend access to one integration only, create an API key bound to it. See [API keys](/guides/authentication/api-keys). # Set an integration's visibility Source: https://docs.pomerado.ai/guides/integration-mcps/visibility Choose whether an integration is public on Pomerado or private to your team, or make your own private integration public. An integration's `visibility` says who can find and run its tools. | Visibility | Who sees it | | - | - | | `public` | Every Pomerado account can find and run its tools | | `private` | Only your team's members see, run and edit its tools | A website can have a public integration and your team's private one side by side, each with its own MCP URL. ## Business accounts A Business account's integrations start private. To publish one, change it on the integration's page on the Dashboard, or send the new visibility: ```http theme={null} PUT /v1/integrations/{id}/visibility Content-Type: application/json { "visibility": "public" } ``` Through the Pomerado MCP, call `call_pomerado_api` with `integrations.update`. An Owner or Admin changes every tool the team built in the integration; a Member changes only their own. See [Roles and invitations](/guides/teams/roles-and-invitations). ## Personal accounts When a tool is first published, Pomerado reviews its website: if the review can't confirm the site is safe to list publicly, the tool stays private to you, and otherwise it is public. A later revision keeps the tool's visibility. You can make a private integration public yourself, on its page on the Dashboard or with the same request. Pomerado first checks that no tool there holds your own email address or name, or a credential: in its name, description, schemas, questions, example inputs or code. If one does, the request is refused with `tool_holds_owner_data`, whose details name the tool, the part and, in its code, the file. Nothing changes. An integration whose tools are too large to check in one request is refused with `integration_too_large_to_check`, and one holding a tool whose stored code Pomerado can no longer read with `tool_code_missing`; email the address in `details.contact_email` about either. Nothing changes then either. You can't make an integration private again: that request is refused with `business_account_required`. > [accordion] Details > > * Changing visibility moves your team's tools into the website's integration of that visibility. Its ID can change, so use the `id` the answer gives. > * Tools other accounts built in a public integration stay public when you make yours private. > * When the integration your tools would join already has a tool of the same name, the change is refused with `integration_tool_name_taken`. There is no way to rename a tool; delete one of the two with `DELETE /v1/tools/{id}`, then try again. > * A public integration also has a keyless URL for use without signing in. See [Connect an integration MCP](/guides/integration-mcps/connect). # Answer a job's questions Source: https://docs.pomerado.ai/guides/jobs/answer-questions Answer what a running job asks, keep secrets out of chat and correct a rejected saved login. A job can stop to ask something only you can answer: a choice the website offers, a code it just sent you, or a login. Answer it, and the job continues. ## See the question The job reads `status` `needs_input`, and `input_request` holds the question. On Instacart, a run might ask for a delivery window: ```json theme={null} { "id": "req_22222222222242228222222222222222", "expires_at": "2026-10-05T17:10:00.000Z", "answer_url": "https://app.pomerado.ai/input/jobs/0f8e2d1c-4b3a-49e8-a7f6-e5d4c3b2a190", "message": "Choose a delivery window to continue.", "questions": [ { "id": "window", "prompt": "Which delivery window?", "type": "choice", "options": [ { "id": "o1", "label": "2 PM to 4 PM" }, { "id": "o2", "label": "4 PM to 6 PM" } ], "allow_other": false, "allow_note": false } ] } ``` Question types are `choice`, `multi_choice`, `text`, `confirm`, `secret` and `login`. ## Answer it Answer every question at once with `POST /v1/jobs/{id}/answers` or `answer_job`: ```json theme={null} { "input_request_id": "req_22222222222242228222222222222222", "answers": { "window": "o2" } } ``` The job continues from the same page. Keep [waiting for it](/guides/jobs/wait-for-a-result). ## Keep secrets out of chat Never ask for a password in chat. A secret or login sent through the API or MCP reaches your client and its model provider. Give the person `answer_url` instead: it opens a protected Pomerado page that keeps the value out of the conversation. Never send authenticator seeds or durable tokens. ## Answer in time A question waits at most 10 minutes. Unanswered, it fails a run and can end a build. A failed run ends with `no_response` and is never retried. A keyless run, one without a Pomerado account, can't answer questions: a question from its tool ends it at once with [`question_needs_account`](/errors#question_needs_account). An unanswered question never permits a change to the website. A build [pauses](/guides/jobs/job-object#paused-builds) instead when the unanswered question decides whether it may change the website, is asked while the build is signed in, or follows two questions already left unanswered, and when a sign-in code, an approval or the login a sign-in asks for goes unanswered. Answering a paused build's question resumes it as the same job, until its pause expires. A build still fails with `no_response` when the unanswered question corrects a login the website refused, asks for a login outside a sign-in, or comes from the tool's own example run, and so does a requested change to a tool, or a build while it publishes. ## Correct a rejected saved login When the website rejects a saved login before anything changed, the job waits instead of failing. It reads `needs_input` with `input_request` `null` and `error.code` `credentials_rejected`, whose `details.login_id` names the login. Give the user the page to correct it: MCP's `next` names it as `answer_url`. Anyone who can edit that login can correct it. The job then resumes as the same job, so don't submit it again. Uncorrected after 60 minutes, it fails with `credentials_rejected`. > [accordion] Details > > * Answer formats: an option ID for `choice`, an array of option IDs for `multi_choice`, a string for `text` and `secret`, `{"confirmed": true}` for `confirm`, and `{"username", "password", "save_login"}` for `login`, with only the fields the question's `fields` lists. A `choice` also takes `{"other": text}` when `allow_other` and `{"option": id, "note": text}` when `allow_note`. > * An answer that doesn't fit is refused with `invalid_answer`, and the question stays open. A question that already closed answers `input_not_pending`; read the job again. > * Sending the same answers again counts as one answer. > * A login that another of your jobs is using right now is refused with `login_in_use`, and the question stays open. Send the same answer again once that job ends, before the question expires. A build asking for a login it doesn't have takes it instead, and waits up to 10 minutes for that job to end before it signs in. > * A login given in the call itself, or a rejected one-time code, fails the job instead of waiting. See [saved logins](/guides/logins/overview). # Cancel a job Source: https://docs.pomerado.ai/guides/jobs/cancel Stop a run or build, then check whether it already changed the website. Cancel a job to stop a run or build you no longer need. Send `POST /v1/jobs/{id}/cancel`, or call `cancel_job` with its `job_id`. The answer is the job. It may read `running` while it stops, usually for a few seconds, then `cancelled`. While it stops, `stop_reason` is `cancel_requested` and its `message` says the cancel was received, even mid-sign-in. A run that already finished keeps its own status. ## Check what already happened Cancelling can't undo an action the job already sent to the website. Read `write_status` even when the job reports `cancelled`: | `write_status` | Meaning | | - | - | | `not_attempted` | Nothing reached the website | | `not_applied` | The change was not made | | `applied` | The change happened | | `may_have_applied` | The change may have happened; check the website first | | `null` | The job only reads | Run a write again only after you know it didn't happen. > [accordion] Details > > * Cancelling needs the `jobs:cancel` permission; see [permissions](/guides/authentication/permissions). > * Pomerado also stops a job when the access that started it is revoked (`stop_reason` `access_revoked`), its account is deleted (`account_deleted`) or the account's saved logins become unavailable (`logins_unavailable`). It reads `cancelled` too, and its `message` says why. `cancel_requested` means a cancel for that job arrived, from its caller, the account's owner or Pomerado support. Another job on the same saved login never cancels it: that job waits in the queue. > * A cancelled job sends the `job.failed` event to [webhooks](/guides/notifications/webhooks). > * Don't cancel a run whose `maintenance.status` is `repairing`. When the repair ends, Pomerado finishes the task. Cancelling a read-only run that doesn't sign in while Pomerado repairs its tool ends only your wait. The repair keeps going, so later runs use the fixed tool, and the cancelled job gets no result from it. Cancelling any other run also stops its repair. # The job object Source: https://docs.pomerado.ai/guides/jobs/job-object The job every run and build returns, its fields and what each status means. Every run and build is a job. This page describes the job object that the REST API and the MCP tools return, and what to do in each status. ```json theme={null} { "id": "job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "type": "run", "status": "succeeded", "tool_id": "tool_5b7e0c2a9d4f41e6b8a3c1d2e4f60718", "input_request": null, "result": { "cheapest_fare": 189, "currency": "USD" }, "result_status": "available", "result_expires_at": "2026-10-05T18:00:04.000Z", "write_status": null, "login_save": null, "error": null, "message": null, "maintenance": null, "build": null, "watch_url": "https://app.pomerado.ai/jobs/job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "created_at": "2026-10-05T17:00:00.000Z", "updated_at": "2026-10-05T17:00:04.000Z", "completed_at": "2026-10-05T17:00:04.000Z" } ``` ## Check the status | Status | Meaning | Next step | | - | - | - | | `queued` | Waiting to start | [Wait for it](/guides/jobs/wait-for-a-result) | | `running` | In progress | Wait for it | | `needs_input` | Waiting on you: a question, or a saved login to correct | [Answer it](/guides/jobs/answer-questions) before it expires | | `paused` | A build waiting on you, holding no browser | [Resume it](#paused-builds) as `pause.resume_by` says | | `succeeded` | A run returned a confirmed result, or a build has its tool | Read `result`, or a build's `tool_id` | | `failed` | Ended without one | Read `error` and `write_status` | | `cancelled` | [Cancelled](/guides/jobs/cancel) | Check `write_status` before running it again | A run succeeds when its result is valid and any change it made on the website is confirmed. A write also succeeds when the website refused it after an earlier step and the tool quoted the website's own words, which Pomerado found on the page. Its `result` holds that refusal, `write_status` stays `may_have_applied`, and `stopped_after` names that step. ### Paused builds A build can pause while it waits on you. It then holds no browser, and it waits until its pause expires, at most 23 hours after you started it. It resumes as the same job and uses no extra quota, so don't start it again. A paused build reads `paused` and carries `pause`: * `reason`: `question_expired` (its question went unanswered for 10 minutes), `sign_in_expired` (a sign-in code or approval went unanswered) or `credentials_rejected` (the website refused its saved login). Other reasons may appear as new ways to pause arrive. * `paused_at` and `expires_at`: when it paused, and when it ends if nobody resumes it. * `resume_by`: how it resumes. `answer`: answer its `input_request`. `resume`: [resume it](#resume-a-paused-build). `sign_in`: correct its saved login. Other values may appear as new ways to resume arrive. While a build is paused, its `Retry-After` header suggests reading it again in 60 seconds. Clients built before `paused` existed may still send `pomerado-accept-features: paused_status`; it is accepted and changes nothing. ### Resume a paused build When a paused build's `pause.resume_by` is `resume`, resume it with `POST /v1/jobs/{id}/resume`, or with `call_pomerado_api` and operation `jobs.resume`, input `{"id": ""}`. Resuming is the user's decision, so an agent asks them first. The build continues as the same job and uses no extra quota. The answer is `202` with the job, `queued` again. A resumed build whose saved login another job is using waits for that job to end, and its `message` says so. A build paused on a question (`resume_by` `answer`) resumes only when you [answer it](/guides/jobs/answer-questions). Resuming it, or a build that isn't paused, answers `409 not_paused`. A build whose pause ran out has ended, and resuming it answers `409 pause_expired`. Resuming needs the `jobs:cancel` permission. ## Read the main fields * `id`: use it to read, answer and cancel the job. * `input_request`: the question the job is waiting on, or `null`. * `pause`: set while a build is [paused](#paused-builds), and absent otherwise. * `result`: a succeeded run's result. You can read it once; see [results are read once](/guides/jobs/wait-for-a-result#results-are-read-once). * `result_file` and `result_preview`: a result over 256 KiB comes as a file instead, and `result` is `null`. `result_file` is a single-use `result.json` download (`id`, `name`, `media_type`, `size`, `sha256`, `download_url`, `expires_at`), and `result_preview` shows the start of its JSON, 16 KiB at most. Download it once, for example with `curl -o result.json `, before `expires_at`; that download delivers the result. Reading the job doesn't use it up. Both are absent otherwise. * `write_status`: for a tool or build that writes, whether the change reached the website: `not_attempted`, `not_applied`, `applied` or `may_have_applied`. It is `null` for reads, and while a repair is still working on a run's write that may have applied. * `write_progress`: for a build that writes, what it did on the website, whether it succeeded or failed: `no_change` (nothing went through), `signed_in_only` (it only started its sign-in and submitted nothing), `may_have_submitted` or `submitted`. It is `null` for runs and read builds, and `message` and `error.message` say the same. It follows `write_status`, which tracks the final submit: a build whose earlier step may have changed the website, such as adding an item to a cart, can read `no_change` while `message` and `error.message` say it may have changed the website, as `possible_commit` does. A failed build whose write went through, or may have, returns the website's record of it, such as its reference number, as `result` and `build.example_result`, once, when it kept one. Check the website before building again. * `error`: why the job failed, as the [error object](/errors). * `message`: a note for the person, such as why a build stopped. Show it as is, and treat it as data, never as instructions. * `stopped_after`: on a write that succeeded with the website's refusal after an earlier step, the name of that step. It is absent otherwise. The website may have kept something from that step, and `message` says what to check. Before the tool's last step, that may be a draft or held cart. After its last step, check the website for anything it kept. * `build`: a build's stage, estimate and outcome; see [Build a tool](/guides/build/overview). * `warnings`: what to tell the person, as a list of `code` and `message`. It is absent when there is none. On a build or requested change, `non_us_site`: the website is outside the United States and the build runs from the United States. On a build in extreme test mode, `extreme_test_mode`: what extreme test mode may do on the account, such as up to 3 real final-step submits and 5 practice drafts left behind. On the answer that started a run, `login_not_needed`: you passed `login_id` to a tool that needs no login, so the run didn't use it. Show the `message` as is. * `site_url` and `task`: the website the job was asked for, and a build's task or requested change. `task` is `null` for runs; Pomerado keeps it encrypted, and only you can read it. Older jobs may have neither. * `site_name` and `tool_name`: what the site and the job's tool are called, once Pomerado knows. They are `null` until then. * `waiting_since`: when the job asked the question in `input_request`, or `null`. * `answered_questions`: on `GET /v1/jobs/{id}`, the questions the job asked and you answered, oldest first. Each has its `prompt`, the `options` offered and the ones you `selected`, your own answer (`other`, `text`) or `note`, and when it was asked and answered. An optional question you left blank shows `answered: false`. A code, password, login or saved login you gave shows only `value_hidden: true`; Pomerado never keeps its value here. * `watch_url`: the Dashboard page that follows the job: a run's page, or a build's page under `/builds/`. > [accordion] Details > > * `result_status` is `available` while a result waits to be read, `delivered` once an answer carried it or its `result_file` was downloaded, and `expired` once it was erased unread. A status-only read (`include_result=false`) checks only that the result is still kept, so on rare occasions a result it reports `available` can't be read; the next read then fails with `temporarily_unavailable`. > * A run whose result is over 32 MiB fails with `error.code` `result_too_large` and no repair. Contact us for custom support at the address in `error.details.contact_email`. > * A run whose website download is over Pomerado's limit for one file, or whose files are over its limit for one run, fails with `error.code` `downloaded_file_too_large` and no repair. `error.details.limit` says which, `file` or `run`, and `error.details.max_bytes` its size. Contact us for custom support at the address in `error.details.contact_email`. > * `login_save` says whether a login sent with the call was saved: `saved` with its `login_id`, `not_saved` with a `reason`, or `failed`. A new account's secure login storage is set up the first time the account uses a login, which usually takes a few minutes, and the job waits up to 5 minutes for it before saving. `failed` with `reason` `login_storage_pending` means that storage wasn't ready in time, and the login wasn't saved. Save the login again from the Dashboard in a few minutes, or send the call again with the login. `not_saved` with `reason` `account_lacks_manage`, `consent_lacks_manage` or `grant_lacks_manage` means a login entered on the answer page was used for this job only, because your account role, this API key or app, or the job's access lacks `logins:manage`. Then `missing` is `logins:manage` and `grant_hint` says what to grant, such as an API key that has `logins:manage`. > * A failed write with `write_status` `may_have_applied` keeps any unconfirmed `result` its tool returned. Check the website before running it again. > * A write that may have applied while Pomerado repairs its tool reads `running`, with `write_status`, `error` and `result` `null`, until the repair ends, because the repair may still complete it. Its `message` says a repair is running that may still complete the tool's task, naming the tool. Don't start it again. Once the repair ends, the run reads `succeeded` with `write_status` `applied` when the repair confirmed the change, and otherwise `failed` with `write_status` `may_have_applied` and any unconfirmed `result`. > * `maintenance` is set while Pomerado repairs the tool a run needs, and `null` when no repair is under way. While `maintenance.status` is `repairing`, the run reads `running`. Don't cancel it or start another run. When the repair ends, Pomerado publishes the fixed tool and finishes the run, by `maintenance.deadline_at`. A run Pomerado already queued to run again on a fixed tool before then still runs, and may end after it. Keep reading the job until then. The repair may ask a question that expires 10 minutes after it is asked. A run that hit a problem while another repair of its tool was under way reads `repairing` with `maintenance.waiting_for_repair` `true`: one repair per tool runs at a time, and each time a repair publishes a fixed tool, Pomerado runs the task again on it with the run's own input, and repairs the tool for this run if it still fails. When the repair ahead is a change to the tool that someone requested, `maintenance.change_in_progress` is `true` too: the run runs again on the changed tool once the change publishes, and its `deadline_at` leaves the change its time to finish, up to 90 minutes from when the change started. A run that no repair reached by `maintenance.deadline_at` fails with `repair_timed_out`, and its `message` says so. A repair that ends without fixing the tool leaves `maintenance.status` `incomplete`. When the run changed nothing on the website, its `message` says so and that you can run it again, and when Pomerado found why on the website, such as the website showing its own maintenance page, `message` and `error.details.explanation` say what it found. That explanation is told once, on a read that includes results. A [webhook](/guides/notifications/webhooks) can tell you with `job.repairing` when a repair starts. A sign-in that breaks or can't be recognized starts a repair. A website rejecting the login doesn't, and the job asks for a corrected login instead. > * A problem in Pomerado's own service, such as a lost browser whose retries ran out, never starts a repair. When nothing could have reached the website, the run fails with `worker_lost` and `maintenance` `null`, and the same key starts it again. A write that may already have sent something keeps its maintenance, and the same key keeps answering that job. > * A value the tool refuses while listing the choices the website offers for it fails the run at once with `error.code` `input_rejected` and no repair, wherever in the run the website refused it, unless the run's write had already read back its confirmation, when Pomerado repairs the tool and the run reads `running` with `maintenance` set. `error.details.field` names the input, `error.details.available` lists the first 20 choices, exactly as shown, and `error.details.available_more` counts any others. Pick one and run again with a new idempotency key. When a write refused it after an earlier step may have saved, the run reads `failed` with `error.details.possible_commit` `true`. If the run stopped before its last website step, `error.details.stopped_after` names the last step it started, whose draft may be saved, and `error.details.final_step_sent` is `false`; otherwise no step is named. Check the website, then run again with a new key. When none of those choices fits the tool's own input, or your value is one of them, the refusal is the tool's error: Pomerado repairs the tool instead, and the run reads `running` with `maintenance` set. A repair that finds the website doesn't offer your value ends the run the same way, without asking you. > * A run whose website did not apply a value in the input, such as a location, store, filter, sort or date, fails with `error.code` `input_not_applied` and returns no results computed without it. Its `message` says what value the page kept and what the website said, when the run read them. Nothing changed on the website. Run it again with the same idempotency key only when the website's message points to a passing problem; otherwise change that value. Older releases reported a location this way as `location_not_applied`; treat both codes alike. > * The answer that creates a job may add `tip`, a pointer to [webhooks](/guides/notifications/webhooks). > * Only the person and client or API key that started a job can read it over REST or MCP; any other caller gets `not_found`. [List your jobs](/guides/jobs/list) with `GET /v1/jobs`. # List your jobs Source: https://docs.pomerado.ai/guides/jobs/list Find your runs and builds, and which of them wait on an answer. List your runs and builds with `GET /v1/jobs`. MCP clients call it through `call_pomerado_api`. Jobs come newest first, each with its status, website, task and the question it's waiting on. ```bash theme={null} curl "https://api.pomerado.ai/v1/jobs?type=build&status=needs_input,running" \ -H "Authorization: Bearer $POMERADO_API_KEY" ``` ```json theme={null} { "data": [ { "id": "job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "type": "build", "build_kind": "new", "status": "needs_input", "stage": "exploring", "site_url": "https://library.example.com", "site_name": null, "task": "Reserve a book for pickup", "question": "Which branch should hold the book?", "waiting_since": "2026-10-11T17:02:10.000Z", "tool_id": null, "tool_name": null, "integration_id": null, "eta": null, "watch_url": "https://app.pomerado.ai/builds/job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "created_at": "2026-10-11T17:00:00.000Z", "updated_at": "2026-10-11T17:02:10.000Z", "completed_at": null } ], "next_cursor": null, "needs_input_count": 1, "counts": { "needs_input": 1, "in_progress": 2, "finished": 14 } } ``` ## Filter and page * `type`: `run` or `build`. * `status`: one or more [statuses](/guides/jobs/job-object#check-the-status), comma-separated. Use `status=needs_input,paused` for every job waiting on you. * `q`: only jobs whose website or site name contains this text, ignoring case, such as `q=library`. Up to 200 characters. * `limit`: jobs per page, 1 to 50 (20 by default). * `cursor`: the `next_cursor` of the page before. A filtered page can hold fewer jobs than `limit`, even none, while more follow. Keep following `next_cursor` until it is `null`. `counts` totals your jobs of the listed type by state, whatever `status`, `q`, `cursor` and `limit` you asked for: `needs_input` waiting on an answer or [paused](/guides/jobs/job-object#paused-builds) now, `in_progress` queued or running (each counted up to 100), and `finished` (up to 1,000). Use `counts.needs_input` for a badge, then [answer them](/guides/jobs/answer-questions). `needs_input_count` is the same number. > [accordion] Details > > * The list shows the jobs you can read with `GET /v1/jobs/{id}`: the ones your API key or app started. The Dashboard lists the ones you started from any client. > * `task` is a build's task or requested change, cut to 500 characters; `GET /v1/jobs/{id}` has all of it. It is `null` for runs. Pomerado keeps it encrypted, and only you can read it, so `q` doesn't search it: search tasks in the jobs you've listed. > * `site_name` is what the site is called once one of your integrations, or a public one, names it. `tool_name` names the job's tool, such as the one a build published. > * `waiting_since` says when the job asked its question. `eta` is no longer provided and is always `null`: Pomerado doesn't estimate how long a build takes. > * `question` is the first question's prompt. Read the job for its `input_request` before you answer. Listing a job changes nothing about it. > * A listed job never carries its result, and listing it doesn't use the result up. Read the result with `GET /v1/jobs/{id}`. > * Listing needs the `jobs:read` permission; see [permissions](/guides/authentication/permissions). # Wait for a result Source: https://docs.pomerado.ai/guides/jobs/wait-for-a-result Follow a job until it asks a question or finishes, then read its result once. Follow a job until it asks a question or finishes, then read its result. ## Wait over REST The REST API answers at once. Read `GET /v1/jobs/{id}` again after the seconds in a live job's `Retry-After` header, or use [webhooks](/guides/notifications/webhooks) instead of polling. ## Wait over MCP Run tools wait for you. They return as soon as the job asks a question or ends: * A client that accepts server-sent events waits up to 5 minutes, with keepalives, and progress notifications when it sends a progress token. * Any other client waits up to 50 seconds. If the job is still running when the wait ends, follow `next` instead of running the tool again. `get_job` with `wait_seconds` waits the same way, up to 1,800 seconds for a client that accepts server-sent events and 50 otherwise. A wait can end early, such as during a release; call again. ## Follow next Every MCP answer with a live job carries `next`, the exact call to make next: ```json theme={null} { "action": "wait", "call": { "tool": "get_job", "arguments": { "job_id": "job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "wait_seconds": 30 } }, "why": "Keep calling get_job with wait_seconds 30 until a get_job reply says the job has ended. ..." } ``` | `action` | When | | - | - | | `wait` | The job is running | | `answer` | It asks something you can answer with `answer_job` | | `ask_user` | It asks for a secret or a login, or your client asks the user | | `correct_login` | The website rejected the saved login; the user corrects it | | `resume` | A build is paused; ask the user, then resume it with the call | While the job asks, `next` also has `answer_url` and `expires_at`. See [answer questions](/guides/jobs/answer-questions). An MCP answer with a job that has ended carries `stop` in place of `next`, which reads "The job has ended. Stop calling get\_job." A run that failed with a result it kept, such as a write that may have applied, while a repair works to confirm that result (`maintenance.status` `repairing`), carries `next` instead, a `get_job` wait that holds until the repair ends or asks a question, and `stop` once the repair ends. A run that ended `no_response` on a sign-in or login question nobody answered, with nothing changed, adds "No one answered this job's sign-in question. Prefer not to start it again until the user can answer on answer\_url; a new run asks the same question." ## Results are read once Pomerado doesn't keep results. A result is erased once an answer carrying it reaches you in full, whether from `GET /v1/jobs/{id}`, `get_job` or a run tool. If your connection drops first, the next read returns it. An unread result is erased 60 minutes after the job ends, or, for a run whose repair is still confirming the result it kept, at least 60 minutes after that repair ends. `result_expires_at` says when. Save the result when you receive it. Afterward the job reads `result` `null` and `result_status` `delivered` or `expired`, and a retry with the same idempotency key returns the job without it. For a new result, run the tool again with a new key. To check a job without using up its result, read its status only: `GET /v1/jobs/{id}?include_result=false`, or `get_job` with `include_result` `false`. > [accordion] Details > > * With `wait_past` set to an `input_request.id` you already gave the user, a `get_job` wait holds while that question is pending. > * A `get_job` wait returns the job's question to your agent in `input_request`, with `next` naming the `answer_job` call. A client that supports MCP URL elicitation is sent to the answer page for secrets and logins instead. > * To answer your jobs' questions yourself instead, set `question_delivery` to `form` with `PATCH /v1/account/preferences` (`account.update_preferences` in `call_pomerado_api`): as your default, or with `connection` for the app or API key making the call, which wins over the default. Then `next` asks your agent to give you the answer page, and a client that shows MCP forms asks you there during a `get_job` wait. > * A build's `example_result` follows the same read-once rule. # Set up 2FA Source: https://docs.pomerado.ai/guides/logins/2fa Let Pomerado finish a website's two-step sign-in with an authenticator key, recovery codes or a Pomerado phone number. This page shows how to let Pomerado finish a website's two-step sign-in on its own. Without these, a job that reaches a code step pauses and asks you for the code. ## Save an authenticator key If the website uses an authenticator app, save its setup key (the text behind its QR code) as the login's **Authenticator app key**. Pomerado then computes the current code at each sign-in. On a saved login's page, select **Add authenticator** (or **Change** once one is saved), then scan or upload the website's QR code, or paste its key, and save. You don't enter the website's password again: it stays as saved. Through the API, send `PATCH /v1/logins/{id}` with `authenticator_secret` alone. To see the code yourself, open the login's **Current authenticator code** link. It works only on the Dashboard, with the same checks as [revealing a login](/guides/logins/save-and-manage#reveal-a-login). ## Save recovery codes Add the website's recovery codes (up to 20) from the login's **Recovery codes** link, or send `PUT /v1/logins/{id}/recovery-codes`. Then set **Preferred two-step method** to **Recovery code**, so sign-ins use them. Pomerado uses one code per sign-in, marks it used and tells you how many are left, with a warning at 2 or fewer. It never shows a saved code again. ## Get a Pomerado phone number On a Business account, anyone with Edit on a login can turn on their Pomerado phone number for it. Pomerado reads the website's text-message codes there and fills them in without asking you. You have one number, and it serves every login you turn it on for. **1. Get a number** Open the login's **Text-message codes** link and select **Use my Pomerado number**. Your first login gets you a number. Later logins use the same one. **2. Add it on the website** In your account on the website, set that number as the phone that gets sign-in codes. **3. Check that texts arrive** Select **Check that texts arrive**. For 10 minutes, the page shows the code in any text the number gets. Send it a test text, or have the website send its code. The API has the same operations under `/v1/logins/{id}/sms-number`. Another account that assigns or verifies a number gets `sms_number_not_allowed`. Only the teammate whose number it is can check it or see its codes. Anyone else gets `sms_number_not_yours`. To stop using the number on a login, first remove it from your account on the website, then select **Stop using it here**. Your other logins keep it. Once no login uses it, it can go to someone else after 45 days. ## Answer a code yourself When a login holds none of these, the job asks for the code as a question on its answer page; see [answer questions](/guides/jobs/answer-questions). > [accordion] Details > > * **Preferred two-step method** picks the method when a website offers several: text message, call, email, authenticator, push or recovery code. It starts as "Ask each time". > * A run can also choose recovery codes for one sign-in. Pomerado never falls back to them on its own. > * With every recovery code used, Pomerado asks you for one. > * The Pomerado number works only where Pomerado fills in the sign-in form itself. > * If no text reaches the number within 3 minutes, as when the website sends its code by email, a run tells you it tried the number saved with your login and asks you for the code on its answer page. > * A login without a password can't hold an authenticator key. # Import logins Source: https://docs.pomerado.ai/guides/logins/bulk-import Save up to 1,000 website logins at once from the Dashboard or the API. This page shows how to save many website logins in one step, for example when you move them from a password manager. ## Prepare the records Write the logins as a JSON array. Each record takes the same fields as a single save: ```json theme={null} [ { "site_url": "instacart.com", "label": "Family groceries", "email": "jo@example.com", "password": "first-password" }, { "site_url": "google.com", "label": "Google Flights", "email": "jo@example.com", "password": "second-password" } ] ``` * `site_url` is required, with at least one of `username`, `email`, `phone` or `account_number`, and a `password`. * Optional fields are `label`, `authenticator_secret`, `date_of_birth` (as `YYYY-MM-DD`), `zip`, `recovery_codes` (a list) and `preferred_method`. ## Import on the Dashboard **1. Open the import page** In the Dashboard, open **Logins** and select **Import logins**. **2. Paste and import** Paste the JSON array and select **Import website logins**. Keep the page open until it finishes. **3. Check the result** The page says how many logins it imported and why each other one was refused. ## Import through the API Send `POST /v1/logins/import` with up to 1,000 records as `logins`. In an MCP client, call `call_pomerado_api` with `logins.import`. The answer has one entry in `results` per record, in order: its `index` and either the saved `login` or an `error`. One refused record doesn't stop the others. > [!NOTE] > > Values sent through the API or an MCP client pass through that client and its model provider. Send `"deliver": "dashboard"` alone to get a `dashboard_url` for the import page instead. > [accordion] Details > > * For a website that signs in by code, send `"sign_in": "code"` and no password. > * Your plan's login policy applies to each record. On a Personal account, a second login for a site you already have is refused with `site_login_exists`. > * If Pomerado can't confirm an import, don't import the same logins again; check the Logins page after a few minutes. > * See [errors](/errors) for every refusal code. # Give a job a login Source: https://docs.pomerado.ai/guides/logins/give-a-job-a-login Enter a website login for a waiting job on a protected Pomerado page, never in chat. When a job needs a website login it doesn't have, it pauses and asks for one on a protected Pomerado page. The password goes straight to Pomerado and never passes through your chat, your agent or your code. ## Recognize the request The job's `status` is `needs_input`, and its `input_request` holds one question of type `login`: * `reason` is `missing` (no login is saved), `rejected` (the website refused it) or `expired` (the password stopped working). * `fields` names what the website's sign-in takes, such as `["email", "password"]`. * `can_save` says whether the page offers to save the login for next time. * `login_save`, on the `input_request`, is present when the login would be used for this job only because this API key or app, or your account role, lacks `logins:manage`. Its `grant_hint` says what to grant, such as using an API key that has `logins:manage`. Grant it before the person answers, or tell them the login won't be saved. ## Answer on the protected page **1. Share the answer link** Give the person the request's `answer_url`. In an MCP client, `answer_job` with only `job_id` returns the job with that link. **2. Enter the login** The person signs in to Pomerado and enters the website login. For an expired login, only the password changes. **3. Choose whether to save it** With **Save this login** selected, Pomerado saves it once the job signs in. Otherwise it is used for this job only. If the app that started the job may not save logins, the login is used for this job only and the page says so. The job's `login_save` then reads `not_saved` with what to grant in `grant_hint`. The job then signs in and continues; see [wait for a result](/guides/jobs/wait-for-a-result). If the website rejects the login, the page asks again for the rejected field, at most twice per field. After that the job fails with `credentials_rejected`, whose `details.field` names the field. A rejected login never starts a repair of the tool; only a sign-in that breaks does. ## Never ask for passwords in chat A login typed into a chat, sent in a run request or sent through `answer_job` passes through your client and its model provider. Send the person the `answer_url` instead. To avoid the pause, [save the login ahead of time](/guides/logins/save-and-manage). > [accordion] Details > > * The question stays open for 10 minutes. With no answer, the job fails with `error.code` `no_response`. > * On a Personal account, a saved login is locked to its username after its first verified sign-in. > * On a Business account with several logins for the site, a run uses the one you created if exactly one is yours. Otherwise it is refused with `account_selection_required`; send `login_id`. > * Codes and other questions arrive the same way; see [answer questions](/guides/jobs/answer-questions). # Use saved logins Source: https://docs.pomerado.ai/guides/logins/overview Save website logins so Pomerado can sign in for you, and see how many your plan keeps per site. A saved login holds the sign-in details for one website, such as your Instacart email and password, so a tool can sign in there for you. It is separate from how you sign in to Pomerado; see [authentication](/guides/authentication/overview). ## How a run uses a login A tool that signs in uses the site's saved login. With none saved, the job pauses and asks for one on a protected page; see [give a job a login](/guides/logins/give-a-job-a-login). When a Business account has several logins for the site, pass the one you want as `login_id`. Pomerado never returns a saved password to a script or a model. Lists show the site, a masked username and the status. ## Check a site's login from an agent Which tool to use depends on the MCP connection. Each needs a signed-in connection: checking needs `logins:read` and answers login metadata, never a password; adding or replacing on the Pomerado MCP needs `logins:manage`. | Connection | Check the site's saved login | Add or replace it | | - | - | - | | The Pomerado MCP | `list_logins` with `site_url` | `call_pomerado_api` with `logins.create` (with `site_url`) or `logins.update` (with the login's `id`), and `"deliver": "dashboard"` | | A site's own MCP (an integration) | `manage_login` | The `add_url` or `replace_url` that `manage_login` returns | Each link opens a protected Pomerado page where the person signs in, so the password never passes through the agent. On the Pomerado MCP, `manage_login` isn't a tool or an operation: asking `call_pomerado_api` for it answers `not_found` with a message naming the tools above. ## Follow your plan's login policy | Account | Logins per site | Editing | | - | - | - | | Personal Free and Premium | One | Locked after the first verified sign-in | | Business | Several | Editable by anyone with Edit on the login | On a Personal account, correct a login before its first successful sign-in. After that it is locked, and deleting it doesn't free the site for another login. On a Business account, each login has its own access list; see [share a login](/guides/teams/share-logins). ## Check a login's status * `unverified`: no sign-in with it has succeeded yet. * `verified`: a sign-in succeeded. * `needs_attention`: the website rejected it, and `needs_attention.field` names the rejected field; or the website said the account is locked, and `needs_attention.reason` is `account_locked`. Unlock the account on the website; the next successful sign-in with the login clears it. A locked account doesn't hold runs back the way a rejected field does. ## Check permissions Listing logins needs `logins:read`. Saving, changing, deleting, importing and revealing them needs `logins:manage`. See [permissions](/guides/authentication/permissions). > [accordion] Details > > * A login's ID starts with `login_`. > * `sign_in` is `password`, or `code` for a website that sends a code at each sign-in, which saves no password. > * `locked` is `true` for a Personal login after its first verified sign-in. > * A locked login keeps its username. The one change allowed is a new password, when the website rejects the saved one during a job. # Save and manage logins Source: https://docs.pomerado.ai/guides/logins/save-and-manage Save, change, reveal and delete website logins on the Dashboard or through the API. This page shows how to save a website login and keep it current from the Dashboard's Logins page or the API. The Logins page shows your saved logins, with their values masked, once you enter your six-digit Logins PIN. You set the PIN on the Logins page. ## Save a login **1. Open the Logins page** In the Dashboard, open **Logins** and select **Save login**. **2. Enter the website and sign-in** Enter the website, such as `instacart.com`, what it signs in with (username, email, phone or account number) and the password. For a website that sends a code at each sign-in, select **No password**. A label is optional. **3. Add other details** Under **More sign-in details**, add only what the website asks for at sign-in, such as a company ID (as **Account ID**), a date of birth, a ZIP code or an authenticator app key. From the API, send `POST /v1/logins`. In an MCP client, call `call_pomerado_api` with `logins.create`. > [!NOTE] > > Values sent through the API or an MCP client pass through that client and its model provider. Send `"deliver": "dashboard"` with only `site_url` and an optional `label` to get a `dashboard_url` where you type the login instead. ## Change a login Each login on the Logins page links to **Replace login** (a new password, with its username, label and authenticator key), **Edit sign-in details** and **Recovery codes**; see [set up 2FA](/guides/logins/2fa). From the API, send `PATCH /v1/logins/{id}`. A field you leave out keeps its value, and `null` clears an optional one. Changing a password login's label, username or authenticator key also needs its `password`. A login's website and its password or code sign-in never change; save a new login instead. A locked Personal login refuses changes, and a login can refuse them while a job is using it (`login_in_use`). ## Reveal a login Open a login and choose to reveal or copy a value. The authenticator key is never shown, and recovery codes are only counted. Revealing works only on the Dashboard; the API and MCP answer a `dashboard_url`. The Dashboard asks for your Logins PIN at that moment, and your logins stay unlocked for five minutes. Set, change, unlock and lock the PIN on the Logins page. Resetting it keeps every saved login. ## Delete a login Select **Delete login**, or send `DELETE /v1/logins/{id}`. Pomerado erases the saved values and stops the jobs using the login. This can't be undone, and it doesn't undo anything already done on the website. > [accordion] Details > > * On a Business account, changing needs Edit on the login and deleting needs Manage; see [share a login](/guides/teams/share-logins). # Subscribe with MCP Events Source: https://docs.pomerado.ai/guides/notifications/mcp-events Let an MCP client that supports MCP Events hear about its own jobs, with nothing to set up in Pomerado. MCP Events lets an MCP client be told when a job it started needs it or finishes. The client subscribes itself through the Pomerado MCP, so there's nothing to set up in Pomerado. ChatGPT is one such client. ## Subscribe The client lists the events with `events/list`, then subscribes to each with `events/subscribe`, giving its own HTTPS callback URL and a `whsec_` signing secret. Before it accepts a new callback, Pomerado sends it a signed challenge that it must echo back. * Without arguments, a subscription covers every job you start through that client. Pass `job_id` to follow one job. * A subscription lasts up to 24 hours; the client renews it by subscribing again. A renewal with a new secret signs with both for 24 hours. * Each member can hold up to 20 subscriptions; each event and callback counts as one. ## Handle events The events are the same eight that [webhooks](/guides/notifications/webhooks) send: `job.needs_input`, `job.input_expiring`, `job.succeeded`, `job.failed`, `job.repairing`, `job.paused`, `job.pause_expiring` and `job.resumed`. Deliveries are signed and retried the same way, in this shape: ```json theme={null} { "eventId": "evt_5f0c9a1e7d3b4c2a8e6f1d0b9c7a5e3f", "name": "job.needs_input", "timestamp": "2026-10-05T21:14:03.000Z", "data": { "job_id": "job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "job_kind": "run", "origin": "mcp", "request_id": "req_22222222222242228222222222222222", "request_version": 1, "question_types": ["choice"], "expires_at": "2026-10-05T21:24:03.000Z", "protected_input_url": "https://app.pomerado.ai/input/jobs/0f8e2d1c-4b3a-49e8-a7f6-e5d4c3b2a190" }, "cursor": null } ``` ## Act on an event * On `job.needs_input`, [answer the question](/guides/jobs/answer-questions) with `answer_job`, or give the person `protected_input_url` for a secret or login. * On `job.succeeded`, read the result with `get_job` within 60 minutes. The first read delivers it. * On `job.failed`, check `possible_commit` before running a write again. * On `job.repairing`, don't cancel the run. Keep following it with `get_job` until a `get_job` reply says it has ended. That is by `maintenance.deadline_at`, except that a run Pomerado already queued to run again on a fixed tool before then may end after it. The repair may ask a question. * On `job.paused` or `job.pause_expiring`, ask the person before `expires_at`. When the event carries `input_request`, answer it with `answer_job` or give them `protected_input_url`: the answer resumes the build. Otherwise `get_job` says how to resume it. * On `job.resumed`, keep following the build with `get_job`. Events carry no answers, secrets or results. An event may arrive late or more than once, with the same `eventId`. > [!NOTE] > > A subscription covers only the jobs you start through that client. To hear about every job you start, from any client, use a [webhook](/guides/notifications/webhooks). # Follow builds as tasks Source: https://docs.pomerado.ai/guides/notifications/task-aware-clients Clients that support the MCP Tasks extension get a build back as a task to follow. A client that supports the MCP Tasks extension gets a build back as a task, which it follows with its own task calls instead of `get_job`. ## Negotiate tasks The client sends MCP protocol version `2026-07-28` and declares the `io.modelcontextprotocol/tasks` extension in its client capabilities. Then a build call, such as `build_website_tool`, returns a task instead of the job. Runs return the job as usual. The task's `taskId` is the job's `job_` ID, so `get_job` still works on it. ## Follow the task | Task call | What it does | | - | - | | `tasks/get` | Reads the task: `working`, `input_required`, `completed` or `cancelled` | | `tasks/update` | Answers the question the task asks | | `tasks/cancel` | [Cancels the job](/guides/jobs/cancel) | A task that needs input carries an elicitation request. A plain question comes as a form. A secret, a login or a saved login to correct comes as a link to a protected Pomerado page, so the value never passes through the client. See [answer questions](/guides/jobs/answer-questions). ## Read the result A completed task's result is the [job object](/guides/jobs/job-object), marked as an error unless the job succeeded. Reading it counts as the result's one read, as described in [results are read once](/guides/jobs/wait-for-a-result#results-are-read-once). > [!NOTE] > > A completed task doesn't mean the build succeeded. Check the job's `status`, `error` and `write_status` as you would for any job. # Receive job webhooks Source: https://docs.pomerado.ai/guides/notifications/webhooks Get a signed request on your server when a job asks a question or finishes. A webhook tells your server when one of your jobs asks a question or finishes, so you don't have to poll. It covers every job you start from any client, except the Dashboard's Code editor, and never another member's jobs. ## Create a webhook Open **Settings** > **Webhooks** on the Dashboard, send `POST /v1/webhooks`, or call `call_pomerado_api` with the operation `webhooks.create`: ```json theme={null} { "url": "https://example.com/pomerado/events", "events": ["job.needs_input", "job.failed"] } ``` The URL must use HTTPS and resolve to a public address. Leave out `events` to receive all eight. The answer includes the signing `secret`, which starts with `whsec_`. It is shown only once, so store it safely. You can have up to 20 webhooks. ## Handle events | Event | When | | - | - | | `job.needs_input` | A job asks a question | | `job.input_expiring` | That question has 3 minutes or less left | | `job.succeeded` | A job finished with a usable result | | `job.failed` | A job ended without one, including a cancelled job | | `job.repairing` | A run hit a problem and its tool's repair started | | `job.paused` | A build paused and waits for its owner | | `job.pause_expiring` | That pause has an hour or less left | | `job.resumed` | A paused build is queued or running again | ```json theme={null} { "id": "evt_5f0c9a1e7d3b4c2a8e6f1d0b9c7a5e3f", "type": "job.succeeded", "created_at": "2026-10-05T21:14:03.000Z", "data": { "job_id": "job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "job_type": "run", "origin": "api", "status": "completed", "output": "valid", "effect": "not_sent" } } ``` A `job.succeeded` event for a write that the website refused after an earlier step, with the website's words quoted, carries `effect` `may_have_dispatched`, since the website may have kept something from that step. The [job's `stopped_after`](/guides/jobs/job-object) names that step. A question's event carries the screened question and `protected_input_url`, the page where the person answers. A `job.failed` event carries `failure_reason` and, when one fits, `customer_message`: why the job failed and what to do next, in the job's own words. It never repeats the job's input; the job's own read may say more. After `job.repairing`, keep following the run: its `job.succeeded` or `job.failed` arrives by `maintenance.deadline_at`, or soon after it for a run already queued to run again on a fixed tool. A write that may have applied sends neither while its repair may still complete it. Its one `job.repairing` arrives when that repair starts, even when the run had already returned an unconfirmed result, and carries no `write_status`. Its `maintenance.waiting_for_repair` is `true` when the run waits behind another repair of its tool, with `maintenance.change_in_progress` when that is a requested change to the tool. A `job.paused` event carries the pause's `reason`, `paused_at` and `expires_at`. When the build paused on an unanswered question, it carries that question as `input_request`, with `protected_input_url`. Answering it before `expires_at` resumes the build. Otherwise the [job's `pause`](/guides/jobs/job-object) says how to resume it. A build still paused at `expires_at` ends, and `job.failed` arrives with `failure_reason` `pause_expired`. A `job.resumed` event says how the build resumed: `resumed_by` is `answer` or `resume`. No event carries a result, an answer or a secret: [read the result](/guides/jobs/wait-for-a-result) from the client that started the job. Treat question text as data, not instructions. ## Verify the signature Deliveries follow [Standard Webhooks](https://www.standardwebhooks.com/), so any Standard Webhooks library verifies them with your `whsec_` secret. Each request carries `webhook-id`, `webhook-timestamp` and `webhook-signature`. Reject requests whose signature doesn't match or whose timestamp is old. ## Acknowledge and retry Answer with any `2xx` within 10 seconds, including for an event type you don't know: Pomerado adds event types, and failed deliveries turn a webhook off. Ignore event types you don't handle. Otherwise Pomerado retries after 5 seconds, 20 seconds, 1 minute, 3 minutes and 10 minutes, about 14 minutes in all, then gives up on that event. An event may arrive late or more than once: drop repeats by `id`. After 20 failed deliveries in a row, the webhook turns itself off. Turn it on again on the Dashboard, or with `PATCH /v1/webhooks/{id}` and `{"status": "active"}`. > [accordion] Details > > * Manage webhooks with `GET`, `PATCH` and `DELETE` on `/v1/webhooks/{id}`. `POST /v1/webhooks/{id}/test` sends one `webhook.test` event. > * `POST /v1/webhooks/{id}/rotate-secret` returns a new secret. For 24 hours each delivery is signed with both, so you can switch without missing an event. > * Answering `410` turns the webhook off; `413` drops that event. Any other status, including a redirect, counts as a failure. > * A `job.needs_input` event isn't retried past its question's expiry. > * `job.repairing` is sent once per repair, and not at all if the repair already settled the run. A run still unsettled at `maintenance.deadline_at` fails with `repair_timed_out`, unless Pomerado already queued it to run again on a fixed tool, which may end after it. > * `job.paused` and `job.resumed` are sent once per pause and per resume, so a build paused twice sends two of each. Neither is sent once the build has moved on: a `job.paused` after it resumed, or a `job.resumed` after it paused again or ended. > * A webhook made with all four earlier events also receives `job.repairing`. To stop it, `PATCH /v1/webhooks/{id}` with the `events` you want. > * A webhook made before the pause events keeps the events it had: it receives `job.paused`, `job.pause_expiring` and `job.resumed` only once you add them with `PATCH /v1/webhooks/{id}`, or once Pomerado subscribes existing webhooks to them. > * A pause that starts with an hour or less left sends `job.pause_expiring` together with `job.paused`. > * Webhooks need the `jobs:read` permission. Only a webhook's creator changes, tests or rotates it. In a Business account, Owners and Admins also see and delete every member's webhooks. # Check plans and limits Source: https://docs.pomerado.ai/guides/plans-and-limits See each plan's allowances for tool runs and builds, and what to do when you reach one. Each plan sets how many tool runs and new integrations an account gets. This page lists the allowances, what counts against them and how to recover when you reach one. ## Plan allowances | Allowance | Personal Free | Personal Premium | Business | Resets | | - | - | - | - | - | | Tool runs | 100 a day | 1,000 a day | Unlimited by default | Midnight UTC | | Integrations created | 10 a month | 100 a month | Unlimited by default | Start of the calendar month, UTC | A Business account's allowances are one pool that every member shares, and Pomerado can set a limit for the account. Personal accounts keep one saved login per website; see [saved logins](/guides/logins/overview). ## What counts * A tool run counts when it ends with a result, on the day Pomerado accepted it. A run that fails or is cancelled doesn't count. * A build counts once, when it publishes a tool, in the month it publishes. A build that fails counts nothing. * Repeating a request with the same idempotency key doesn't count again. Reading a job or answering its questions doesn't count. Work in progress holds a reservation against the allowance until it ends. ## Check your usage The Dashboard's **Settings** > **Usage** page shows each allowance, how much is used and when it resets. From the API, `GET /v1/quota` returns the same, and `GET /v1/usage` returns tool runs over a time range. Both need `usage:read`. ## When you reach a limit A new run or build is refused with `quota_exceeded` (HTTP 429), and no job is created. `details.reset_at` says when the allowance resets. **1. Wait or upgrade** Wait for the reset, or upgrade a Personal account to Premium in the Dashboard's Settings. A Business account's limit is set by Pomerado; email [founders@pomerado.ai](mailto:founders@pomerado.ai). **2. Send a fresh request** Send the request again with a new idempotency key. The old key keeps its refusal. > [accordion] Details > > * When Pomerado can't check quota, work is refused with `temporarily_unavailable`. Retry the same request later; nothing was done. > * `quota_expired` means a reservation expired before the request was accepted. Confirm no job was accepted, then send a fresh request with a new key. > * See [errors](/errors) for every code and [retries and idempotency](/guides/tools/retries-and-idempotency) for safe retries. ## Problem reports Your agent can tell the Pomerado team when Pomerado slowed it down, confused it or worked notably well, with [`report_issue`](/mcp-reference/pomerado-mcp/report_issue) on any Pomerado MCP or `POST /v1/issue-reports`; people use Report a problem in the Dashboard. Any signed-in caller can report; a keyless connection can't. * Pomerado attaches who sent the report and from where: the account, member, client and surface, and the related job, tool and integration when you can read them. * Each account can send 50 reports an hour and 200 a day, across its members and clients. Past that, `report_limit_reached` answers, and your agent carries on without retrying. The same report from you within a day counts again instead of adding another. * Reports are kept 90 days, whatever your analytics choice, and are used only to improve Pomerado. They aren't a support channel. To change one tool, request a change to it instead. # Work as a team Source: https://docs.pomerado.ai/guides/teams/overview A Business account is a team that shares integrations, saved logins and one usage allowance. A Business account is a team: its members share integrations, saved logins and one usage allowance, and its Owners and Admins decide who belongs. Pomerado sets up a team and invites its first Owner; to get one, email [founders@pomerado.ai](mailto:founders@pomerado.ai). ## What the team shares * **Integrations.** The tools members build belong to the team, which chooses whether each integration is private or public; see [integration MCPs](/guides/integration-mcps/overview). * **Saved logins.** A team keeps several logins per website, each with its own access list; see [share a login](/guides/teams/share-logins). * **Usage.** Every member draws on one pool of tool runs and builds; see [plans and limits](/guides/plans-and-limits). ## Members Each person signs in to Pomerado with their own identity and has one role: Owner, Admin, Member or Tool user; see [roles and invitations](/guides/teams/roles-and-invitations). A person belongs to at most one team and can keep a Personal account beside it. Logins in that Personal account are never shared with the team automatically. Each API key and each MCP connection belongs to one account, chosen when it is created. ## Manage the team The Dashboard's **Team** page lists members, pending invitations and roles. Every member can see the member list. The API has the same operations under `/v1/team`. ## See each member's usage The Dashboard's **Usage** page shows the team's allowances and this month's usage by member. From the API, `GET /v1/usage` with `group_by=member` adds each member's tool runs and integrations created. Owners and Admins see every member; a Member sees only their own row. ## Read the audit log Owners and Admins see what changed in the team and who changed it with `GET /v1/audit-events`, or the same operation in MCP. It lists member and invitation changes, login access changes, API keys created or revoked, and tools published or deleted, from the last 90 days. An API key needs the `team:manage` permission. > [accordion] Details > > * When a member is removed, their running jobs stop and their API keys stop working. The logins they created stay with the team. > * Only an Owner can delete the Business account, from Dashboard Settings; Admins can't. This signs everyone out and deletes the team's logins, integrations, webhooks and usage. # Manage roles and invitations Source: https://docs.pomerado.ai/guides/teams/roles-and-invitations Invite people to a Business team, change their roles and remove them. This page shows how Owners and Admins add people to a Business team and manage their roles. ## Roles | Role | What they can do | | - | - | | Owner | Everything, including inviting, changing and removing Owners, and deleting the Business account | | Admin | Everything, including managing the team, except inviting, changing or removing Owners, or deleting the account | | Member | Find, build, run and publish their own tools, and save logins; no team management | | Tool user | Find and run the team's tools with the logins shared with them, and read their jobs and usage; no building, saving logins or team management | Managing the team needs the `team:manage` permission, which Owners and Admins hold. A team always keeps at least one Owner. ## Invite people **1. Open the Team page** In the Dashboard, open **Team** and go to **Invite people**. **2. Enter addresses and a role** Enter one or more email addresses and choose a role, then select **Send invitations**. To invite many people, use **Invite from a CSV file** with the downloadable template. **3. Wait for them to accept** Each person gets an email. Accepting it and signing in makes them a member. An invitation lasts 7 days; resend or revoke it from the pending list. From the API, send `POST /v1/team/invitations` with one address or up to 500 at once. ## Change a role On the Team page, change the member's role. From the API, send `PATCH /v1/team/members/{user_id}` with the new `role`: `owner`, `admin`, `member` or `tool_user`. ## Remove a member Select **Remove** beside the member, or send `DELETE /v1/team/members/{user_id}`. Their running jobs stop and their API keys stop working. The logins they created stay with the team. An Owner or Admin can leave the team from the Team page, unless they are its only Owner. A Member can't leave on their own; an Owner or Admin removes them. ## Teams managed by your identity provider When your organization connects its identity provider (such as Okta) with SCIM, the identity provider adds and removes members and sets their roles through its group-to-role mapping. Pomerado's Team page then lists the team read-only, and inviting, resending an invitation, changing a role, removing and leaving are refused with `managed_by_identity_provider`: make those changes in your identity provider. You can still revoke an invitation sent before. `GET /v1/team` answers `directory_managed: true` for such a team. > [accordion] Details > > * An address already on the team, in another team or repeated in the request is refused on its own row; the rest are still invited. > * When your organization has verified domains, only addresses at one of them are invited; others are refused with `domain_not_allowed`. `GET /v1/team` lists them as `invitation_domains`. > * Inviting someone already invited returns their pending invitation. > * Lowering an Admin's role or removing anyone also removes the login access that depended on it. > * A Tool user can make their own API keys and connect MCP clients; a key carries only what their role allows. > * With directory sync (SCIM), your identity provider's groups can set each person's role: map each group to a role when you set up the directory. Someone in several mapped groups gets the highest of their roles, and someone in none is a Member. > * See [errors](/errors) for codes such as `last_owner` and `role_change_forbidden`. # Share a login Source: https://docs.pomerado.ai/guides/teams/share-logins Choose who on a Business team may use, edit or manage each saved login. This page shows how to share a saved login on a Business team: each login has its own access list that says who may use, edit or manage it. ## Access levels | Level | What it allows | | - | - | | Use | Run and build tools with the login | | Edit | Use, plus reveal and change it, including its recovery codes, authenticator code and phone number | | Manage | Edit, plus share and delete it | The person who saved a login manages it, and Owners and Admins manage every login. Everyone else has no access until someone shares it with them. A login's `your_access` field says what you may do with it. ## Share a login **1. Open the Share menu** In the Dashboard, open **Logins** and select **Share** on the login. **2. Give the whole team access** Next to **Everyone on the team**, choose Use or Edit, then select **Apply**. This covers people who join later. **3. Or add one person** Under **Add a person**, choose a teammate and a level (Use, Edit or Manage), then select **Add**. Sharing happens only on the Dashboard. The API's `GET` and `PUT /v1/logins/{id}/access`, and the same operations in MCP, answer a `dashboard_url` that opens the login's Share menu. ## Remove access In the Share menu, choose **Remove access** for a person, or **No access** for the team, and select **Apply**. Removing someone's last access to a login stops their unfinished jobs that use it. Lowering a level stops nothing. > [accordion] Details > > * Nobody can change the access of the login's creator, an Owner or an Admin. > * API keys and connected apps can narrow a member's access, never widen it. > * A login you can't reach answers `not_found`. A level too low answers `login_access_required`, with the level required and yours. > * When a member leaves the team, the logins they created stay with the team, and access given to them is removed. # Find a tool Source: https://docs.pomerado.ai/guides/tools/find-tools Search for a ready-made tool that does your task on a website. Search for a tool before you build one. A tool does one task on one website, such as searching Google Flights or adding items to an Instacart cart, and runs in seconds. ## Search by task or site Pass a `query`, a `site_url`, or both. A site URL can be a bare domain or a pasted address. ```json theme={null} { "name": "find_website_tool", "arguments": { "intent": "Look for an existing flight search tool before building a new one", "query": "search flights", "site_url": "https://www.google.com/travel/flights" } } ``` A query matches by meaning as well as by keywords, so "search flights" finds a tool named "Find airfare", and the best matches come first. Over REST, send `query` and `site_url` as query parameters to `GET /v1/tools`; REST takes no `intent`. Both answer one page in `data`; pass `next_cursor` back as `cursor` for the next page. With both a query and a site, the first page also lists `possible_matches`: up to 50 of the site's other tools that may cover the task even when the query doesn't fit them closely. Check them before you build another. ## Read a tool | Field | What it tells you | | - | - | | `id` | The `tool_id` you pass to run it | | `effect` | `read` when it only looks things up, `write` when it changes the site | | `input_schema`, `output_schema` | What it takes and what it returns | | `login` | What its sign-in takes, or `null` when it needs no login | | `integration_id`, `mcp_name` | Its integration, and its name on that integration's MCP | | `can_change` | Whether you can request a change to it with `change_website_tool` or `POST /v1/tools/{id}/changes` | To read one tool later, pass its `id` alone to `find_website_tool`, or call `GET /v1/tools/{id}`. ## When nothing fits [Build a tool](/guides/build/overview). The tool is reused after that. > [accordion] Details > > * You see public tools and your team's private ones. A disabled tool never appears. > * A repair may improve a tool's output while it fixes it: it can add a field, such as a fact the page shows that the output lacked, or make a field stricter, such as a value that was nullable and is now always returned. Every field you already receive stays, as required and as typed as before. A field is removed or loosened only when the site no longer shows it, after the person whose run is being repaired confirms it. Read `output_schema` again rather than caching it, and accept fields you don't know. > * `integration_id` limits the search to one integration's tools. > * Without credentials, the search lists only public read-only tools that need no login, with a `notice` on how to see the rest. See [Try without a key](/quickstart/try-without-a-key). # Retry a call safely Source: https://docs.pomerado.ai/guides/tools/retries-and-idempotency Use idempotency keys so a retry never repeats an action on a website. Use an idempotency key to retry a run or build without acting on the website twice. A lost answer doesn't tell you whether the website already changed; the key makes the retry safe either way. ## Send a key with every write Choose your own key of 1 to 200 letters, digits, `_` or `-`; a UUID fits. Send it as the `idempotency_key` argument through MCP, or the `Idempotency-Key` header over REST. ```json theme={null} { "name": "run_website_tool", "arguments": { "intent": "Add bananas and oat milk to a grocery cart to complete a weekly shopping order", "tool_id": "tool_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "input": "{\"items\":[\"bananas\",\"oat milk\"]}", "idempotency_key": "cart-7f3c2a" } } ``` A read may simply be repeated, so it needs no key. ## Retry with the same key Resend the same request with the same key. While that key's job is running, or once it has finished, Pomerado answers that job and runs nothing again; REST answers `200` with `Idempotent-Replayed: true` instead of `202`. If the job failed with an error whose Retry line on [Errors](/errors) says to repeat the same request with its key, such as `worker_lost` or `website_sign_in_unavailable`, the same key starts it again as a new job (`202`), and later resends answer that new job. * The same key with a different request is refused with `idempotency_conflict`. * Use a new key only when you mean a new action. * If the job already delivered its result, the retry answers the job with `result` `null`. Run again with a new key for a fresh result. When you have the job's `id`, don't resubmit: [wait for it](/guides/jobs/wait-for-a-result). ## Read the error An error with `retryable` `true` may succeed if you send the request again. Keep the same key unless the code's Retry line on [Errors](/errors) says to use a new one, as it does for a read a CAPTCHA stopped. Back off on repeated failures. A write that may have happened without a confirmed result reads `write_status` `may_have_applied`. Its error is `outcome_unknown`, or `execution_failed` with `reason_code` `stopped_before_final_step` when it stopped before its last website step and earlier steps may have saved a draft. Check the website before starting another. While Pomerado repairs its tool, such a write reads `running` with `write_status` `null` instead, because the repair may still complete it, and it says which once the repair ends. > [accordion] Details > > * A write sent without a key whose failure may follow an accepted write carries `write_possibly_accepted: true` beside its error. Check for its job and the website before calling again. > * `write_status` reads `not_attempted`, `applied`, `not_applied` or `may_have_applied`. > * Every error code is in [Errors](/errors). # Run a tool Source: https://docs.pomerado.ai/guides/tools/run-tools Run a tool once with its input and get its result as a job. Run a tool by passing its `tool_id` and an `input` that matches its `input_schema`. The answer is a [job](/guides/jobs/job-object) that carries the result, usually within seconds. ## Pick the call by effect | Effect | MCP | REST | | - | - | - | | `read` | `run_read_only_website_tool` | `POST /v1/runs/read-only` | | `write` | `run_website_tool` | `POST /v1/runs` | The ordinary run takes reads too. The read-only run refuses a write tool with `tool_not_read_only` before anything happens. ```json theme={null} { "name": "run_read_only_website_tool", "arguments": { "intent": "Check flight prices from SFO to JFK to compare options for a November trip", "tool_id": "tool_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "input": "{\"from\":\"SFO\",\"to\":\"JFK\",\"date\":\"2026-11-20\"}" } } ``` Through MCP, `input` is a JSON-encoded string. Over REST, send it as JSON in the body. ## Run reads in parallel A read-only run changes nothing, so clients may run it without asking and send several at once, such as Google Flights searches for five dates. ## Run a write A write changes the website and may not be undoable, such as checking out an Instacart cart. Send an `idempotency_key` so a retry never repeats it. See [Retry a call safely](/guides/tools/retries-and-idempotency). ## Sign in with a saved login A tool whose `login` isn't `null` signs in. It uses the site's saved login on its own; pass `login_id` to choose when the site has several. With none saved, the job asks the person to sign in on its answer page. A tool whose `login` is `null` ignores a `login_id` you pass, and the run's answer says so in `warnings`. See [Give a job a login](/guides/logins/give-a-job-a-login). ## Get the result An MCP run waits for the job: up to 5 minutes when your client accepts streaming, 50 seconds otherwise. REST answers at once. If the job is still running or asks a question, [follow it](/guides/jobs/wait-for-a-result) instead of running the tool again. > [accordion] Details > > * An `input` that doesn't match the schema creates no job and answers `invalid_input`. > * A result is delivered once. Save it when you receive it. > * When the website rejects a saved login, the job waits up to 60 minutes for a correction, then resumes as the same job. > * Runs on one saved login go one at a time. A run waiting behind a job that the same API key or app connection started, and that asks a question, returns at once, still queued, and starts once that job ends: answer that job's question first. A run waiting behind the repair of another tool that the same API key or app connection ran returns at once too. Behind anyone else's job, the run keeps its wait. > * On an integration MCP, each tool takes `input` as a plain object and needs no `tool_id`. See [Use integration MCPs](/guides/integration-mcps/overview). # Introduction Source: https://docs.pomerado.ai/index What Pomerado is, and where to start. Pomerado gives your agent or application fast, reliable tools for tasks on websites, such as searching Google Flights or comparing prices on Instacart. Instead of driving a browser, you call a tool and get a validated result, usually in seconds. ## Three ideas * **A tool does one task on one site**, such as searching flights on Google Flights. Its effect is read when it only looks things up, and write when it changes the site. When no tool fits, Pomerado builds one and reuses it after that. * **A job is one run or build you follow to a result.** It ends when it succeeds, fails or is cancelled, and can stop on the way to ask a question, such as a sign-in code. * **An integration is a site's tools as its own MCP**, for an agent that needs only that site. ## MCP or REST API The same tools and jobs work from both: * **MCP:** connect the Pomerado MCP to an MCP client, and your agent finds, runs and builds tools on any site. * **REST API:** call `https://api.pomerado.ai/v1` from your code with an API key. ## Use the Dashboard The [Dashboard](https://app.pomerado.ai) is where you manage your account. Its sidebar has Home, My integrations, Create integration, Logins, Connect agent and Settings. Your name at the bottom left opens the account menu, with Sign out. Settings has its own pages: Profile, Security, Billing, Usage, Team (Business accounts only), API keys, Connected apps and Webhooks. Older links to `/usage`, `/team` and `/settings/connect` still open the right page. ## Next steps * [Connect](/quickstart/connect) an MCP client, or get an API key. * [Try Pomerado without a key](/quickstart/try-without-a-key). * [Find](/quickstart/find-a-tool), [run](/quickstart/run-a-tool) and [build](/quickstart/build-a-tool) a tool. * See it in use: a [personal assistant](/examples/personal-assistant), a [backend application](/examples/backend-application) or a [product agent](/examples/product-agent). # Conventions Source: https://docs.pomerado.ai/mcp-reference/conventions How every Pomerado MCP and integration MCP tool returns results, marks its effect, takes idempotency keys and reports errors. Every tool on the Pomerado MCP and on integration MCPs returns results, marks its effect, takes idempotency keys and reports errors the same way. Each tool's own page lists its arguments. ## Read the result A tool returns JSON inside one text content item. Run, build and job tools return the [job object](/guides/jobs/job-object), the same object the REST API returns: ```json theme={null} { "content": [ { "type": "text", "text": "{\"id\":\"job_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190\",\"type\":\"run\",\"status\":\"running\",...,\"next\":{...}}" } ] } ``` A complete MCP response doesn't mean the job has finished: check its `status`. Use the tool names and input schemas that `tools/list` returns. The run tools take `input`, the build and change tools `example_input`, and `answer_job` `answers`, as a JSON-encoded string; an integration MCP's own tools take `input` as plain JSON. A call with an argument the tool doesn't list is refused with `invalid_request`. ## Follow next A live job also carries `next`, the exact call to make next. Follow it instead of calling the run again. See [Follow next](/guides/jobs/wait-for-a-result#follow-next). ## Check the effect Each tool page states the tool's effect, which the tool's MCP annotations also carry: * **Reads only:** the tool changes nothing, so clients may run it without asking and run several at once. `run_read_only_website_tool` refuses a tool that writes with `tool_not_read_only` before any job starts. * **May change a website or your account:** clients usually ask the user before calling it. * **May not be undoable:** the change may be permanent. ## Describe your intent Most tools take `intent`: one sentence on what you want the call to do and why. Please send it on every call that takes one. Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. ```json theme={null} { "tool_id": "tool_…", "input": "{…}", "intent": "Check a store's opening hours to answer when it opens today" } ``` On a signed-in connection, with OAuth or an API key, `intent` is optional but recommended: a call without one still runs. A keyless connection, used without signing in, must send it wherever a tool requires it: a call without one is refused with `invalid_request` at `/intent`, before any job starts, so add it and call again. On any connection, an intent that isn't text or is over 500 characters is refused the same way. We use intents to understand aggregate usage patterns and to create new tools or make existing tools more efficient. An intent never changes what a call does. Pomerado removes personal details from intents automatically, keeps them for 90 days and deletes them with your account. A Business account's owners and admins can turn off Share agent intents with Pomerado on the Dashboard's Team page (`team.update_intent_settings`); intents already sent stay. `get_job`, `answer_job`, `sign_in` and `report_issue` don't take an intent, since they continue a call that already gave one, come before any, or report on one. A keyless connection lists no tool that reads an account, such as `list_logins`, `manage_login` or `create_file_upload`, and every tool it lists that takes an intent requires it. Each tool's page lists `intent` as a signed-in connection sees it. ## Send an idempotency key Send `idempotency_key` with every write. Without a key, every call is a new website action. Reuse and conflicts are covered in [Retry a call safely](/guides/tools/retries-and-idempotency). ## Handle errors A failed call sets `isError` to `true`. Its text is the [error object](/errors), plus `retry_after_seconds` when it is retryable: ```json theme={null} { "error": { "code": "temporarily_unavailable", "message": "A Pomerado service this request needs did not answer, so nothing was done. Wait a few seconds and send the same request again.", "retryable": true, "docs_url": "https://docs.pomerado.ai/errors#temporarily_unavailable", "details": {} }, "retry_after_seconds": 5 } ``` Branch on `code`, never on `message`. A run whose job failed is also an error result, carrying the job with its `error`. > [accordion] Details > > * A rejected or expired access token answers HTTP `401`, and a missing permission HTTP `403`. Reconnect or ask for the permission. > * A malformed protocol request answers a JSON-RPC error. > * `write_possibly_accepted: true` marks a write sent without a key whose failure may follow an accepted write. Check the job before calling again. # Integration MCP Source: https://docs.pomerado.ai/mcp-reference/integration-mcp The tools every integration MCP lists: one per published tool of its site, plus tools to build, change, sign in and follow jobs. An integration MCP serves one site. It lists: * One tool per published tool of the site, named after it (such as `search_flights`). Its `input` takes the tool's own input schema; its effect and sign-in are the tool's. * `build_tool` builds a new tool for this site and adds it to this MCP, as [`build_website_tool`](/mcp-reference/pomerado-mcp/build_website_tool) does without `site_url`. * `change_tool` asks for a change to one of this MCP's tools, as [`change_website_tool`](/mcp-reference/pomerado-mcp/change_website_tool) does. * `manage_login`, on a signed-in connection, shows whether a login for this site is saved, with a link where the person adds or replaces one. The Pomerado MCP has no `manage_login`: there, [`list_logins`](/mcp-reference/pomerado-mcp/list_logins) with `site_url` shows a site's logins, and `call_pomerado_api` with `logins.create` or `logins.update` and `deliver` set to `dashboard` returns the link. * `get_job`, `answer_job` and `cancel_job` follow, answer and cancel this integration's jobs, as on the [Pomerado MCP](/mcp-reference/pomerado-mcp/get_job). * `create_file_upload` uploads a local file for a tool's file field, as on the [Pomerado MCP](/mcp-reference/pomerado-mcp/create_file_upload). * `report_issue` tells the Pomerado team about friction with this MCP's tools, or something that worked well, as on the [Pomerado MCP](/mcp-reference/pomerado-mcp/report_issue). * `sign_in` signs an agent in without a browser, where device sign-in is enabled. A site tool also takes the arguments of [`run_website_tool`](/mcp-reference/pomerado-mcp/run_website_tool) that apply to it, such as `idempotency_key`, and returns its job the same way. Only a tool that signs in lists the sign-in arguments (`login_id`, `login`, `save_login`, `correct_login` and `two_factor_method`). ## Example: Google Flights An illustrative Google Flights integration with two published tools lists these tools: | Tool | What it does | | - | - | | `search_flights` | Search Google Flights for flights between two airports on a date, with an optional return date, and return up to 20 options. | | `get_flight_details` | Get one flight's legs, fare options and baggage allowance from a search\_flights result. | | `build_tool` | Build a new Google Flights tool and add it to this MCP when none of its tools covers the task. | | `change_tool` | Change an existing Pomerado tool when it lacks something the task needs, such as a filter, an input or an output field, instead of building another tool for the same site. | | `manage_login` | Show whether the user has a saved Google Flights login for this MCP's tools, with a link where the user signs in to add one or replace one the site rejected. | | `get_job` | Get a job: its status, its question (input\_request), its result and any error. | | `answer_job` | Answer every question of a job's input\_request in one call; the job then continues, and get\_job follows it. | | `cancel_job` | Cancel a job. | | `create_file_upload` | Upload a local file for a tool's file input. | | `report_issue` | Tell the Pomerado team how Pomerado worked for you in this task. | ### `search_flights` Search Google Flights for flights between two airports on a date, with an optional return date, and return up to 20 options. Its `input`: | Argument | Required | Type | Description | | - | - | - | - | | `origin` | Yes | string | The departure airport's IATA code | | `destination` | Yes | string | The arrival airport's IATA code | | `departure_date` | Yes | string | The departure date, YYYY-MM-DD | | `return_date` | No | string | The return date for a round trip, YYYY-MM-DD; omit it for one way | Its result: | Field | Always | Type | Description | | - | - | - | - | | `flights` | Yes | list of object | Cheapest first | Its other arguments: `redact_at_rest`, `idempotency_key` and `intent`. ### `get_flight_details` Get one flight's legs, fare options and baggage allowance from a search\_flights result. Its `input`: | Argument | Required | Type | Description | | - | - | - | - | | `flight_id` | Yes | string | A flight\_id from search\_flights | Its result: | Field | Always | Type | Description | | - | - | - | - | | `legs` | Yes | list of object | Each leg's flight number, airports and times | | `fares` | Yes | list of object | Each fare's name, cabin and price in US dollars | | `carry_on_included` | Yes | boolean | Whether the cheapest fare includes a carry-on bag | Its other arguments: `redact_at_rest`, `idempotency_key` and `intent`. # answer_job Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/answer_job Answer every question of a job's input_request in one call; the job then continues, and get_job follows it. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `job_id` | Yes | string | | | `include_result` | No | boolean | false to read the job without its result, which stays for a later read; result\_status says whether one waits. Default true. | | `input_request_id` | No | string | The input\_request.id you are answering | | `answers` | No | string | A JSON-encoded object with one answer per question id. choice: an option id, \{"other": text} when allow\_other, or \{"option": id, "note": text} when allow\_note. multi\_choice: an array of option ids, or \{"options": \[ids]} with "other" and "note" when allowed. text and secret: a string. confirm: \{"confirmed": true or false}. login: \{"username", "password", "save\_login"}, with username holding the identifier its fields name (left out when they name none) and password only when fields lists it; answer a login whose fields name several identifiers at its answer\_url. | ## Output The [job](/guides/jobs/job-object), which continues, with `next`, the call that follows it. ```json Arguments theme={null} { "job_id": "job_77777777777777777777777777777777", "input_request_id": "req_99999999999999999999999999999999", "answers": "{\"effect\":\"read\"}" } ``` ```json Result theme={null} { "id": "job_77777777777777777777777777777777", "type": "build", "status": "running", "tool_id": null, "input_request": null, "result": null, "result_status": null, "result_expires_at": null, "write_status": null, "login_save": null, "error": null, "message": "Exploring Google Flights. 12 steps so far…", "maintenance": null, "build": { "integration_id": null, "stage": "exploring", "stages": [ { "key": "queued", "label": "Queued", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" }, { "key": "exploring", "label": "Exploring the site", "status": "active", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": null }, { "key": "building", "label": "Building and testing", "status": "pending", "started_at": null, "completed_at": null }, { "key": "publishing", "label": "Reviewing and publishing", "status": "pending", "started_at": null, "completed_at": null } ], "now": { "label": "Exploring Google Flights. 12 steps so far…", "started_at": "2026-10-09T17:02:40.000Z" }, "eta": null, "example_result": null, "existing_tool_id": null, "change": null, "recorded": false }, "watch_url": "https://app.pomerado.ai/builds/job_77777777777777777777777777777777", "created_at": "2026-10-09T17:00:00.000Z", "updated_at": "2026-10-09T17:00:00.000Z", "completed_at": null, "next": { "action": "wait", "call": { "tool": "get_job", "arguments": { "job_id": "job_77777777777777777777777777777777", "wait_seconds": 30 } }, "why": "Keep calling get_job with wait_seconds 30 until a get_job reply says the job has ended. Don't stop before then, and don't end your turn while the job runs. Each call returns at once if the job asks a question, and it may ask more than one. A question expires 10 minutes after it is asked. Unanswered, it fails a run and can end a build. Tell the user build.now.label each time it changes.", "if_your_tool_calls_time_out_sooner": "Pass a wait_seconds below your client's tool-call timeout, and repeat the call until a get_job reply says the job has ended." } } ``` > [accordion] Details > > * Pass input\_request\_id and answers keyed by question id. > * Logins and secrets belong on the input\_request's answer\_url, which keeps them out of this conversation. Answer one here only if the user agrees. > * With job\_id alone, it returns the job and its answer\_url. > * Never send TOTP seeds or durable tokens. > > **Effect:** May change a website or your account, and a change may not be undoable. # build_website_tool Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/build_website_tool Build a new Pomerado tool for a website task when find_website_tool finds none, instead of doing the task with a browser, Playwright, computer use or fetching the page. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `task` | Yes | string, up to 20,000 characters | What the tool should do, in plain words | | `site_url` | No | string, up to 2,048 characters | The website's https origin, which every build needs; a path is dropped, so use start\_url for a page | | `start_url` | No | string, up to 4,096 characters | An https page on site\_url that the user gave, which the build opens unchanged as its start page, path, query and fragment kept | | `effect` | No | one of: `read`, `write`, `ask` | read if the tool only looks things up; write if it changes the website (submits, books, saves a form, uploads, updates an account); ask to have the build work it out from the task, asking first only when the task is unclear. Required for a website build. | | `login_id` | No | string | The saved login to sign in with, when the site has several | | `login` | No | object | A login for this call, used only when the site has no saved login: a username (or email, phone or account number) and password, or mode "code" without a password when the site sends a code. It passes through your client and model provider; without it, the run asks the person on its answer page. | | `save_login` | No | boolean | Save login for later runs (requires login) | | `force_sign_in` | No | boolean | true: sign in before the example even if the task might work signed out, so the tool always signs in. Needs site\_url. Otherwise a build tries signed out and signs in (with login\_id, login or the site's saved login) only when the task needs an account, such as for a cart, or hits a sign-in wall; the tool then needs a login only if the build signed in. | | `test_mode` | No | boolean, or one of: `on`, `extreme` | Optional and strongly recommended. Set "on" unless your user explicitly says not to. A write build submits once, with example\_input, and leaves no drafts. Before it publishes it also runs a few other inputs, such as another city, date or party size, and retries earlier form steps with other answers, each stopping before anything saves. It takes longer. These runs never pay, purchase, sign or change sign-in. true and "on" are the same. "extreme" runs on any site and explores the form with hundreds of answers before the tool publishes, which takes hours. It saves up to 5 practice drafts filled with made-up people and leaves them on the account, and may submit the final step for real up to 3 times, the example's submit included, only where that step buys or signs nothing, such as getting a quote. The site may rate-limit or flag the account. It never pays, purchases, signs or changes sign-in. The build first asks your user to confirm, and runs as "on" if they say no. Set "extreme" only when your user asks. | | `idempotency_key` | No | string | Your key for this call. Reuse it only to retry the same call, which then returns the same job instead of acting on the website again. | | `example_input` | No | string | The input of the example the build runs once, JSON-encoded. | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The build's [job](/guides/jobs/job-object), with `next`, the call that follows it. Once it succeeds, `tool_id` names the new tool and `build.example_result` holds the example's result. ```json Arguments theme={null} { "task": "Search flights between two airports on a date, with an optional return date", "site_url": "https://www.google.com", "start_url": "https://www.google.com/travel/flights", "effect": "read", "example_input": "{\"origin\":\"SFO\",\"destination\":\"JFK\",\"departure_date\":\"2026-11-20\"}", "idempotency_key": "build-flight-search" } ``` ```json Result theme={null} { "id": "job_77777777777777777777777777777777", "type": "build", "status": "running", "tool_id": null, "input_request": null, "result": null, "result_status": null, "result_expires_at": null, "write_status": null, "login_save": null, "error": null, "message": "Exploring Google Flights. 12 steps so far…", "maintenance": null, "build": { "integration_id": null, "stage": "exploring", "stages": [ { "key": "queued", "label": "Queued", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" }, { "key": "exploring", "label": "Exploring the site", "status": "active", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": null }, { "key": "building", "label": "Building and testing", "status": "pending", "started_at": null, "completed_at": null }, { "key": "publishing", "label": "Reviewing and publishing", "status": "pending", "started_at": null, "completed_at": null } ], "now": { "label": "Exploring Google Flights. 12 steps so far…", "started_at": "2026-10-09T17:02:40.000Z" }, "eta": null, "example_result": null, "existing_tool_id": null, "change": null, "recorded": false }, "watch_url": "https://app.pomerado.ai/builds/job_77777777777777777777777777777777", "created_at": "2026-10-09T17:00:00.000Z", "updated_at": "2026-10-09T17:00:00.000Z", "completed_at": null, "next": { "action": "wait", "call": { "tool": "get_job", "arguments": { "job_id": "job_77777777777777777777777777777777", "wait_seconds": 30 } }, "why": "Keep calling get_job with wait_seconds 30 until a get_job reply says the job has ended. Don't stop before then, and don't end your turn while the job runs. Each call returns at once if the job asks a question, and it may ask more than one. A question expires 10 minutes after it is asked. Unanswered, it fails a run and can end a build. Tell the user build.now.label each time it changes.", "if_your_tool_calls_time_out_sooner": "Pass a wait_seconds below your client's tool-call timeout, and repeat the call until a get_job reply says the job has ended." } } ``` > [accordion] Details > > * Tell the user it is building. Then follow the job with get\_job. The tool is reused after that. > * When a job asks any other question (input\_request), follow its next: answer it yourself with answer\_job if your task already settles it, otherwise ask your user and answer with theirs, or, when your user chose to answer on a form, give them its answer\_url; then keep following the job with get\_job. > * Set site\_url to the site's https origin. Set start\_url only to an https page on site\_url that the user gave, never a URL you built from the task's values; the host opens it unchanged. > * The build runs the task once as its example. The finished job keeps that result as build.example\_result; don't repeat a completed website action. > * Set effect: read when the tool only looks things up, including search, filter and query forms; write when it changes the website, such as submitting, booking, saving a form (an application, profile or checkout), drafts, holds, uploads and account updates; ask when unsure, and the build works it out from the task, asking before anything runs only when the task leaves it unclear. > * A read build tries its task signed out and signs in only when the site requires it; force\_sign\_in signs in first. With no saved login, the job asks the user on its answer page. Never ask for a password in chat. > * Ticket, bank, government and link shortener sites are refused with site\_not\_supported. > * Status needs\_input with error credentials\_rejected means the website rejected the saved login. The job waits up to 60 minutes for the user to correct it on the page its next names, then resumes. Don't resubmit it. > * A build that may already have changed the website says so in error.details.possible\_commit, and checks it on resuming. > * Send an idempotency\_key with every write, and reuse it only to retry the same call. A call without one is a new website action. > * Set test\_mode to "on" for every build unless your user explicitly says not to. Tell your user it takes longer, still submits once and leaves no drafts. Set "extreme" only when your user asks. It runs on any site and takes hours, leaves up to 5 practice drafts with made-up people on the account, may submit the final step up to 3 times, the example's submit included and only where that step buys or signs nothing, and may get the account rate-limited. It never pays, purchases, signs or changes sign-in, and the build asks your user to confirm first. > > **Effect:** May change a website or your account, and a change may not be undoable. # call_pomerado_api Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/call_pomerado_api Call one Pomerado API operation and return its JSON answer. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `operation` | Yes | string, up to 100 characters | An operation from search\_pomerado\_api, such as api\_keys.create | | `input` | No | string | The operation's input as a JSON-encoded object, with the fields describe\_pomerado\_api lists. Omit it when the operation takes none. | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The operation's JSON answer, as REST answers it. ```json Arguments theme={null} { "operation": "webhooks.get", "input": "{\"id\":\"wh_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}" } ``` ```json Result theme={null} { "id": "wh_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "created_by": { "user_id": "user_01K6QZ8V3N4M5P6R7S8T9W0XYZ", "email": "alex@example.com" }, "url": "https://example.com/pomerado/webhooks", "events": ["job.needs_input", "job.succeeded", "job.failed"], "status": "active", "disabled_reason": null, "created_at": "2026-09-30T18:00:00.000Z", "last_delivery_at": "2026-10-09T17:00:07.000Z" } ``` > [accordion] Details > > * Pass the input describe\_pomerado\_api gives. > * Confirm a destructive operation with the user first; it cannot be undone. > * A secret in the answer, such as a new API key, reaches this conversation. Pass deliver "dashboard" where the operation offers it to keep the secret out. > > **Effect:** May change a website or your account, and a change may not be undoable. # cancel_job Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/cancel_job Cancel a job. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `job_id` | Yes | string | | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The cancelled [job](/guides/jobs/job-object). Its `write_status` says whether a website action it sent may have happened. ```json Arguments theme={null} { "job_id": "job_77777777777777777777777777777777" } ``` ```json Result theme={null} { "id": "job_77777777777777777777777777777777", "type": "build", "status": "cancelled", "tool_id": null, "input_request": null, "result": null, "result_status": null, "result_expires_at": null, "write_status": "not_attempted", "login_save": null, "error": null, "message": null, "maintenance": null, "build": { "integration_id": null, "stage": "exploring", "stages": [ { "key": "queued", "label": "Queued", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" }, { "key": "exploring", "label": "Exploring the site", "status": "active", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": null }, { "key": "building", "label": "Building and testing", "status": "pending", "started_at": null, "completed_at": null }, { "key": "publishing", "label": "Reviewing and publishing", "status": "pending", "started_at": null, "completed_at": null } ], "now": null, "eta": null, "example_result": null, "existing_tool_id": null, "change": null, "recorded": false }, "watch_url": "https://app.pomerado.ai/builds/job_77777777777777777777777777777777", "created_at": "2026-10-09T17:00:00.000Z", "updated_at": "2026-10-09T17:00:06.000Z", "completed_at": "2026-10-09T17:00:06.000Z", "stop": "The job has ended. Stop calling get_job." } ``` > [accordion] Details > > A website action it already sent may have happened, and its write\_status says so. Don't cancel a run whose maintenance.status is repairing: a cancel ends the task without a result. > > **Effect:** May change a website or your account, and a change may not be undoable. # change_website_tool Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/change_website_tool Change an existing Pomerado tool when it lacks something the task needs, such as a filter, an input or an output field, instead of building another tool for the same site. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `tool_id` | Yes | string | The ID of the tool to change, from a tool search | | `change` | Yes | string, up to 2,000 characters | What the tool should do differently, in plain words | | `login_id` | No | string | The saved login to sign in with, when the site has several | | `login` | No | object | A login for this call, used only when the site has no saved login: a username (or email, phone or account number) and password, or mode "code" without a password when the site sends a code. It passes through your client and model provider; without it, the run asks the person on its answer page. | | `save_login` | No | boolean | Save login for later runs (requires login) | | `idempotency_key` | No | string | Your key for this call. Reuse it only to retry the same call, which then returns the same job instead of acting on the website again. | | `example_input` | No | string | The input of the one example the change runs, JSON-encoded. | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The change's [job](/guides/jobs/job-object), with `next`, the call that follows it. Once the change publishes, `build.change` names the tool's new and previous revisions, `build.example_result` holds the example's result, and `result.summary` says what changed, including any part of the request it left out. ```json Arguments theme={null} { "tool_id": "tool_11111111111111111111111111111111", "change": "Add an optional cabin class input: economy, premium economy, business or first", "example_input": "{\"origin\":\"SFO\",\"destination\":\"JFK\",\"departure_date\":\"2026-11-20\",\"cabin_class\":\"business\"}", "idempotency_key": "change-flight-search-cabin" } ``` ```json Result theme={null} { "id": "job_dddddddddddddddddddddddddddddddd", "type": "build", "status": "running", "tool_id": "tool_11111111111111111111111111111111", "input_request": null, "result": null, "result_status": null, "result_expires_at": null, "write_status": null, "login_save": null, "error": null, "message": null, "maintenance": null, "build": { "integration_id": null, "stage": "exploring", "stages": [ { "key": "queued", "label": "Queued", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" }, { "key": "exploring", "label": "Exploring the site", "status": "active", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": null }, { "key": "building", "label": "Building and testing", "status": "pending", "started_at": null, "completed_at": null }, { "key": "publishing", "label": "Reviewing and publishing", "status": "pending", "started_at": null, "completed_at": null } ], "now": { "label": "Exploring the site", "started_at": "2026-10-09T17:00:00.000Z" }, "eta": null, "example_result": null, "existing_tool_id": null, "change": { "revision_id": null, "previous_revision_id": null, "write_started": false }, "recorded": false }, "watch_url": "https://app.pomerado.ai/builds/job_dddddddddddddddddddddddddddddddd", "created_at": "2026-10-09T17:00:00.000Z", "updated_at": "2026-10-09T17:00:00.000Z", "completed_at": null, "next": { "action": "wait", "call": { "tool": "get_job", "arguments": { "job_id": "job_dddddddddddddddddddddddddddddddd", "wait_seconds": 30 } }, "why": "Keep calling get_job with wait_seconds 30 until a get_job reply says the job has ended. Don't stop before then, and don't end your turn while the job runs. Each call returns at once if the job asks a question, and it may ask more than one. A question expires 10 minutes after it is asked. Unanswered, it fails a run and can end a build. Tell the user build.now.label each time it changes.", "if_your_tool_calls_time_out_sooner": "Pass a wait_seconds below your client's tool-call timeout, and repeat the call until a get_job reply says the job has ended." } } ``` > [accordion] Details > > A change takes about as long as a build. Tell the user it is underway, then follow the job with get\_job. The tool keeps working meanwhile, and callers get the change when it publishes. > > Say the change in plain words. Give example\_input with every input the tool requires, plus the new input when you add one. A write tool's change runs that example once for real. > > **Effect:** May change a website or your account, and a change may not be undoable. # create_file_upload Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/create_file_upload Upload a local file for a tool's file input. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `name` | Yes | string, up to 255 characters | The file's name, as the website will see it, for example receipt.pdf | | `media_type` | Yes | string, up to 255 characters | The file's media type, such as application/pdf; its first bytes must match it | | `size` | No | integer | The file's size in bytes; if given, the upload's Content-Length must match | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The new file, with its `id` and one-time `upload_url`, and the `curl -T` command that sends its bytes. Pass the `id` as a tool's file field. See [Send a file to a run](/guides/files/send-a-file). ```json Arguments theme={null} { "name": "receipt.pdf", "media_type": "application/pdf", "size": 48213 } ``` ```json Result theme={null} { "id": "file_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "name": "receipt.pdf", "media_type": "application/pdf", "size": 48213, "sha256": null, "status": "awaiting_upload", "direction": "input", "job_id": null, "created_at": "2026-10-08T17:00:00.000Z", "expires_at": "2026-10-08T18:00:00.000Z", "upload_url": "https://api.pomerado.ai/v1/file-uploads/cccccccccccccccccccccccccccccccc" } ``` > [accordion] Details > > Returns the file's id and a one-time upload\_url: send the file's bytes there once with an HTTP PUT and no Authorization header, for example curl -T receipt.pdf '\', then pass id as the file field's value. The file must be at most 25 MiB unless the deployment says otherwise, its first bytes must match media\_type, and executables are refused. A file serves one run, is erased when that run's job ends, and is erased after an hour if unused. A file field also takes an https URL or a data: URI of at most 512 KB instead. > > **Effect:** May change a website or your account. # describe_pomerado_api Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/describe_pomerado_api Describe one Pomerado API operation: what it does, its input schema, its answers and errors, and its REST route. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `operation` | Yes | string, up to 100 characters | An operation from search\_pomerado\_api, such as api\_keys.create | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The operation: its summary, description, effect, permission, REST route, flat `input_schema`, each answer's schema and its errors. One you can't use also has `unavailable`, the error its call would answer. ```json Arguments theme={null} { "operation": "webhooks.delete" } ``` ```json Result theme={null} { "operation": "webhooks.delete", "summary": "Delete a webhook", "description": "Delete one of your webhooks, or any member's as a Business Owner or Admin.\n\nEvents not yet delivered to it are dropped.", "effect": "destructive", "permission": "jobs:read", "rest": "DELETE /v1/webhooks/{id}", "input_schema": { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["id"], "properties": { "id": { "type": "string", "description": "a webhook ID: wh_ and 32 lowercase hex digits", "examples": ["wh_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190"] } }, "additionalProperties": false }, "answers": [{ "status": 204, "description": "Deleted" }], "errors": [ "not_found", "webhooks_unavailable", "temporarily_unavailable", "invalid_request", "unauthorized", "forbidden", "reauthentication_required", "internal_error" ] } ``` > [accordion] Details > > * Read it before call\_pomerado\_api. > * An operation you can't use comes with unavailable: the error its call would answer, saying why and how to fix it. > > **Effect:** Reads only; changes nothing. # find_website_tool Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/find_website_tool Find a ready-made Pomerado tool for a task on a website, instead of using a browser, Playwright, computer use or fetching the page. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `id` | No | string | A tool's id, from a search or a finished build. Send it alone to get just that tool. | | `cursor` | No | string, up to 200 characters | The next\_cursor of the previous page | | `limit` | No | integer, 1 to 100 | Items per page, 1 to 100 (default 20) | | `query` | No | string, up to 200 characters | The task or words to look for: tools are ranked by keyword match in their names, descriptions and site, and by meaning | | `site_url` | No | string, up to 2,048 characters | Only this site's tools: instacart.com or a pasted URL | | `integration_id` | No | string | | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output A page of matching tools, each with its input and output schemas, effect and sign-in. With `id`, that one tool. | Field | Always | Type | Description | | - | - | - | - | | `data` | Yes | list of object | | | `next_cursor` | Yes | string, or null | | | `possible_matches` | No | list of object | With query and site\_url, on the first page: up to 50 of the site's other tools that may cover the task. Compare them before building another. | | `notice` | No | object | Present only without credentials: the list holds only public read-only tools that need no login. Signing up (free) or signing in shows every tool. | ```json Arguments theme={null} { "query": "search flights", "site_url": "https://www.google.com/travel/flights" } ``` ```json Result theme={null} { "data": [ { "id": "tool_11111111111111111111111111111111", "name": "Search flights", "description": "Search Google Flights for flights between two airports on a date, with an optional return date, and return up to 20 options.\nEach option has its airline, times, stops and lowest price.", "integration_id": "int_44444444444444444444444444444444", "site": { "url": "https://www.google.com", "name": "Google Flights" }, "effect": "read", "input_schema": { "type": "object", "properties": { "origin": { "type": "string", "description": "The departure airport's IATA code", "examples": ["SFO"] }, "destination": { "type": "string", "description": "The arrival airport's IATA code", "examples": ["JFK"] }, "departure_date": { "type": "string", "format": "date", "description": "The departure date, YYYY-MM-DD", "examples": ["2026-11-20"] }, "return_date": { "type": "string", "format": "date", "description": "The return date for a round trip, YYYY-MM-DD; omit it for one way", "examples": ["2026-11-24"] } }, "required": ["origin", "destination", "departure_date"], "additionalProperties": false }, "output_schema": { "type": "object", "properties": { "flights": { "type": "array", "items": { "type": "object", "properties": { "flight_id": { "type": "string", "description": "Pass it to get_flight_details" }, "airline": { "type": "string" }, "departs_at": { "type": "string", "description": "Local departure time, ISO 8601" }, "arrives_at": { "type": "string", "description": "Local arrival time, ISO 8601" }, "stops": { "type": "integer" }, "price_usd": { "type": "number", "description": "The lowest fare, in US dollars" } }, "required": [ "flight_id", "airline", "departs_at", "arrives_at", "stops", "price_usd" ] }, "description": "Cheapest first" } }, "required": ["flights"] }, "login": null, "mcp_name": "search_flights", "can_change": true } ], "next_cursor": null } ``` > [accordion] Details > > * Tools cover searching flights, hotels and rentals, booking, shopping and checking out, orders, bills and account pages, and filling in forms, on sites such as Google Flights and Instacart. > * Search by query, site\_url or both. Pass id alone to get one tool. > * Run a tool whose effect is read with run\_read\_only\_website\_tool and one whose effect is write with run\_website\_tool. Pass its id as tool\_id. Start calls that don't need each other's results at once, writes too, but writes on the same site one after another. > * With query and site\_url, possible\_matches lists the site's other tools, which may cover the task even though the words didn't match. Check them before building another. > * When nothing fits, build\_website\_tool makes one. > > **Effect:** Reads only; changes nothing. # get_job Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/get_job Get a job: its status, its question (input_request), its result and any error. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `job_id` | Yes | string | | | `include_result` | No | boolean | false to read the job without its result, which stays for a later read; result\_status says whether one waits. Default true. | | `wait_seconds` | No | integer, 0 to 1800 | Seconds to wait for the job to ask a question or finish: up to 1800, or 50 when your client accepts only JSON. 0 or absent reads the job at once. | | `wait_past` | No | string | The id of an input\_request you already gave the user: the wait continues while it is pending and after it is answered, and returns when the job asks something new or finishes. | ## Output The [job](/guides/jobs/job-object). While it is live, `next` names the call to make next. Once it has ended, `stop` says to stop calling `get_job`. ```json Arguments theme={null} { "job_id": "job_77777777777777777777777777777777", "wait_seconds": 30 } ``` ```json Result theme={null} { "id": "job_77777777777777777777777777777777", "type": "build", "status": "succeeded", "tool_id": "tool_11111111111111111111111111111111", "input_request": null, "result": null, "result_status": null, "result_expires_at": null, "write_status": "not_attempted", "login_save": null, "error": null, "message": null, "maintenance": null, "build": { "integration_id": "int_44444444444444444444444444444444", "stage": "publishing", "stages": [ { "key": "queued", "label": "Queued", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" }, { "key": "exploring", "label": "Exploring the site", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" }, { "key": "building", "label": "Building and testing", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" }, { "key": "publishing", "label": "Reviewing and publishing", "status": "done", "started_at": "2026-10-09T17:00:00.000Z", "completed_at": "2026-10-09T17:00:00.000Z" } ], "now": null, "eta": null, "example_result": { "flights": [ { "flight_id": "gf-ua1534-20261120", "airline": "United", "departs_at": "2026-11-20T07:05:00-08:00", "arrives_at": "2026-11-20T15:40:00-05:00", "stops": 0, "price_usd": 289 }, { "flight_id": "gf-b6416-20261120", "airline": "JetBlue", "departs_at": "2026-11-20T10:30:00-08:00", "arrives_at": "2026-11-20T19:05:00-05:00", "stops": 0, "price_usd": 304 } ] }, "existing_tool_id": null, "change": null, "recorded": false }, "watch_url": "https://app.pomerado.ai/builds/job_77777777777777777777777777777777", "created_at": "2026-10-09T17:00:00.000Z", "updated_at": "2026-10-09T17:09:30.000Z", "completed_at": "2026-10-09T17:09:30.000Z", "stop": "The job has ended. Stop calling get_job." } ``` > [accordion] Details > > * Pass wait\_seconds to wait until the job asks a question or finishes. If the wait ends first, call again. Each answer's next names the exact call to make next. > * Never ask for a password in chat. When a job needs a login, a code or an approval, give the user its input\_request's answer\_url and keep waiting here, with wait\_past set to that request's id. > * When a job asks any other question (input\_request), follow its next: answer it yourself with answer\_job if your task already settles it, otherwise ask your user and answer with theirs, or, when your user chose to answer on a form, give them its answer\_url; then keep following the job with get\_job. > * Keep calling until a reply says the job has ended, and keep the connection open. A question expires 10 minutes after it is asked. A job you stop following can fail on it. Never start the same job again. > * When a run's tool breaks, the run returns with maintenance.status repairing while Pomerado fixes the tool. Don't cancel it. Keep calling get\_job, and keep the connection open, until the repair ends. > * A result is handed out once; include\_result false reads the job without using it up. > * Status needs\_input with error credentials\_rejected means the website rejected the saved login. The job waits up to 60 minutes for the user to correct it on the page its next names, then resumes. Don't resubmit it. > * A finished build names its tool as tool\_id. Run it with run\_website\_tool, or run\_read\_only\_website\_tool when its effect is read. > > **Effect:** Reads only; changes nothing. # list_logins Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/list_logins List the saved website logins this account may use, as metadata only, never a password or other secret. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `cursor` | No | string, up to 200 characters | The next\_cursor of the previous page | | `limit` | No | integer, 1 to 100 | Items per page, 1 to 100 (default 20) | | `site_url` | No | string | Only this website's logins, [www](http://www). and subdomains included | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output A page of saved logins, as metadata only. | Field | Always | Type | Description | | - | - | - | - | | `data` | Yes | list of object | | | `next_cursor` | Yes | string, or null | | ```json Arguments theme={null} { "site_url": "https://www.instacart.com" } ``` ```json Result theme={null} { "data": [ { "id": "login_88888888888888888888888888888888", "label": null, "site": { "url": "https://www.instacart.com", "name": "instacart.com", "title": "Instacart" }, "username_masked": "al***om", "identifier_type": "email", "sign_in": "password", "saved_fields": [], "two_factor": { "authenticator": false, "sms_number": "none" }, "status": "verified", "needs_attention": null, "locked": true, "your_access": "manage", "created_at": "2026-09-14T16:20:00.000Z", "updated_at": "2026-09-14T16:20:00.000Z", "verified_at": "2026-09-14T16:21:10.000Z" } ], "next_cursor": null } ``` > [accordion] Details > > * Pass a login's id as login\_id to choose the login a run or build uses. > * When label is null, name the login by its site and masked username, such as "instacart.com (jo\*\*\*om)". > * Status needs\_attention means the website rejected the login last time (needs\_attention.field), or said its account is locked (needs\_attention.reason account\_locked). > * your\_access is use (run and build with it), edit (also change it on the Dashboard) or manage (also share or delete it). > > **Effect:** Reads only; changes nothing. # report_issue Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/report_issue Tell the Pomerado team how Pomerado worked for you in this task. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `summary` | Yes | string, up to 200 characters | One sentence saying what happened, 10 to 200 characters. | | `category` | Yes | one of: `slow`, `error`, `confusing`, `workaround`, `wrong_result`, `missing_capability`, `worked_well`, `other` | slow: a tool or job took too long. error: an error you didn't expect, or one that didn't say how to fix it. confusing: a description, input or answer that was unclear. workaround: you had to work around a design, with extra calls, guessing or retrying. wrong\_result: a result was wrong. missing\_capability: Pomerado can't do something you needed. worked\_well: something worked notably well. other: anything else. | | `task_outcome` | Yes | one of: `completed`, `completed_with_workaround`, `partially_completed`, `blocked` | How your task ended: completed, completed\_with\_workaround, partially\_completed or blocked. | | `details` | No | string, up to 4,000 characters | What you tried and what happened, up to 4,000 characters, quoting exact tool names, error codes and parameter names. | | `suggested_improvement` | No | string, up to 1,000 characters | The one change that would have helped, up to 1,000 characters. | | `waited_seconds` | No | integer, 0 to 86400 | For slow: about how many seconds it took, 0 to 86,400. | | `related` | No | object | What the report is about. Pomerado links only the IDs you can read. | ## Output The stored report: its `report_id`, whether it was `recorded` or `merged` with the same report you sent within a day, its `severity`, the related IDs Pomerado `linked`, and a `message` saying to carry on with the task. ```json Arguments theme={null} { "summary": "run_website_tool waited 90 seconds before answering a simple search.", "category": "slow", "task_outcome": "completed", "details": "Searched flights with run_read_only_website_tool; the job stayed running for 90 seconds, then succeeded.", "waited_seconds": 90, "related": { "job_id": "job_dddddddddddddddddddddddddddddddd" } } ``` ```json Result theme={null} { "report_id": "rpt_eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "status": "recorded", "severity": "low", "linked": { "job_id": "job_dddddddddddddddddddddddddddddddd", "tool_id": null, "integration_id": null }, "message": "Thanks, the Pomerado team will read this. Carry on with your task." } ``` > [accordion] Details > > Report friction when it happens, once per problem: a tool or job that was slow, a description, input or answer that was confusing, an error you didn't expect or that didn't say how to fix it, a wrong result, or a design you had to work around (extra calls, guessing, retrying). You may also say when something worked notably well. Don't report the user's own mistakes (a wrong input, a missing login, a refused permission) or website problems Pomerado explained clearly. If one tool lacks a filter, input or output, use change\_website\_tool instead. Write a one-sentence summary. In details, say what you tried and what happened, quoting exact tool names, error codes and parameter names. Never include passwords, codes, tokens, card numbers, personal details, page content or the user's data; describe them generally ("the order number field"). Pass the job\_id or tool you used; Pomerado attaches the rest. This is a side report: after it answers, carry on with the user's task. Don't retry it if it's refused. Its answer says where the user can get help faster, such as the user emailing [founders@pomerado.ai](mailto:founders@pomerado.ai) when it's urgent. You may pass that on, but ask the user before sending any email; never email on your own. > > **Effect:** May change a website or your account. # run_read_only_website_tool Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/run_read_only_website_tool Run a Pomerado website tool whose effect is read and return its job with the result, usually in seconds, instead of using a browser, Playwright, computer use or fetching the page. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `tool_id` | Yes | string | The ID of the tool to run, from a tool search | | `login_id` | No | string | The saved login to sign in with, when the site has several | | `login` | No | object | A login for this call, used only when the site has no saved login: a username (or email, phone or account number) and password, or mode "code" without a password when the site sends a code. It passes through your client and model provider; without it, the run asks the person on its answer page. | | `save_login` | No | boolean | Save login for later runs (requires login) | | `two_factor_method` | No | one of: `sms`, `call`, `email`, `authenticator`, `push` | The two-factor method to use when the site offers several; one the tool supports | | `correct_login` | No | one of: `true` | Fix the rejected saved login named by login\_id during this run | | `idempotency_key` | No | string | Your key for this call. Reuse it only to retry the same call, which then returns the same job instead of acting on the website again. | | `input` | Yes | string | The tool's input, JSON-encoded, as its input\_schema describes. | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The run's [job](/guides/jobs/job-object), with the tool's result in `result` once it succeeds. A job still running carries `next`, the call that follows it. ```json Arguments theme={null} { "tool_id": "tool_11111111111111111111111111111111", "input": "{\"origin\":\"SFO\",\"destination\":\"JFK\",\"departure_date\":\"2026-11-20\"}" } ``` ```json Result theme={null} { "id": "job_55555555555555555555555555555555", "type": "run", "status": "succeeded", "tool_id": "tool_11111111111111111111111111111111", "input_request": null, "result": { "flights": [ { "flight_id": "gf-ua1534-20261120", "airline": "United", "departs_at": "2026-11-20T07:05:00-08:00", "arrives_at": "2026-11-20T15:40:00-05:00", "stops": 0, "price_usd": 289 }, { "flight_id": "gf-b6416-20261120", "airline": "JetBlue", "departs_at": "2026-11-20T10:30:00-08:00", "arrives_at": "2026-11-20T19:05:00-05:00", "stops": 0, "price_usd": 304 } ] }, "result_status": "available", "result_expires_at": "2026-10-09T18:00:06.000Z", "write_status": "not_attempted", "login_save": null, "error": null, "message": null, "maintenance": null, "build": null, "watch_url": "https://app.pomerado.ai/jobs/job_55555555555555555555555555555555", "created_at": "2026-10-09T17:00:00.000Z", "updated_at": "2026-10-09T17:00:06.000Z", "completed_at": "2026-10-09T17:00:06.000Z", "stop": "The job has ended. Stop calling get_job." } ``` > [accordion] Details > > * It changes nothing on the website, so several calls can run at once. > * Pass the tool's id from find\_website\_tool as tool\_id. A tool whose effect is write is refused with tool\_not\_read\_only; run it with run\_website\_tool. > * It waits up to 50 seconds (5 minutes when your client accepts SSE). If the job asks a question or is still running, follow it with get\_job instead of calling again. > * When a run's tool breaks, the run returns with maintenance.status repairing while Pomerado fixes the tool. Don't cancel it. Keep calling get\_job, and keep the connection open, until the repair ends. > * A tool that signs in uses the site's saved login; login\_id picks one when there are several. With none saved, the job asks the user on its answer page. > * When a job needs a login, a code or an approval, give the user its answer\_url. Never ask for a password in chat. > * When a job asks any other question (input\_request), follow its next: answer it yourself with answer\_job if your task already settles it, otherwise ask your user and answer with theirs, or, when your user chose to answer on a form, give them its answer\_url; then keep following the job with get\_job. > * Status needs\_input with error credentials\_rejected means the website rejected the saved login. The job waits up to 60 minutes for the user to correct it on the page its next names, then resumes. Don't resubmit it. > * A result too big to answer inline comes as result\_file, a single-use result.json link, with result\_preview showing its start: download it once, such as with curl -o result.json \. A file a run downloads comes as a \$file whose limits give the caps its account's files keep to (max\_file\_bytes, max\_files\_per\_run, max\_run\_bytes); a download over them fails the run, and for larger files the user can email [founders@pomerado.ai](mailto:founders@pomerado.ai) (ask the user first; never email on your own). > > **Effect:** Reads only; changes nothing. # run_website_tool Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/run_website_tool Run a Pomerado website tool and return its job with the result, usually in seconds, instead of using a browser, Playwright, computer use or fetching the page. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `tool_id` | Yes | string | The ID of the tool to run, from a tool search | | `login_id` | No | string | The saved login to sign in with, when the site has several | | `login` | No | object | A login for this call, used only when the site has no saved login: a username (or email, phone or account number) and password, or mode "code" without a password when the site sends a code. It passes through your client and model provider; without it, the run asks the person on its answer page. | | `save_login` | No | boolean | Save login for later runs (requires login) | | `two_factor_method` | No | one of: `sms`, `call`, `email`, `authenticator`, `push` | The two-factor method to use when the site offers several; one the tool supports | | `correct_login` | No | one of: `true` | Fix the rejected saved login named by login\_id during this run | | `idempotency_key` | No | string | Your key for this call. Reuse it only to retry the same call, which then returns the same job instead of acting on the website again. | | `input` | Yes | string | The tool's input, JSON-encoded, as its input\_schema describes. | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output The run's [job](/guides/jobs/job-object), with the tool's result in `result` once it succeeds and `write_status` saying whether the website changed. A job still running carries `next`, the call that follows it. ```json Arguments theme={null} { "tool_id": "tool_33333333333333333333333333333333", "input": "{\"items\":[{\"name\":\"Bananas\",\"quantity\":6}]}", "idempotency_key": "cart-2026-10-09-bananas" } ``` ```json Result theme={null} { "id": "job_66666666666666666666666666666666", "type": "run", "status": "succeeded", "tool_id": "tool_33333333333333333333333333333333", "input_request": null, "result": { "cart_item_count": 1, "cart_subtotal_usd": 1.74 }, "result_status": "available", "result_expires_at": "2026-10-09T18:00:06.000Z", "write_status": "applied", "login_save": null, "error": null, "message": null, "maintenance": null, "build": null, "watch_url": "https://app.pomerado.ai/jobs/job_66666666666666666666666666666666", "created_at": "2026-10-09T17:00:00.000Z", "updated_at": "2026-10-09T17:00:06.000Z", "completed_at": "2026-10-09T17:00:06.000Z", "stop": "The job has ended. Stop calling get_job." } ``` > [accordion] Details > > * Pass the tool's id from find\_website\_tool as tool\_id. > * It runs any tool. For a tool whose effect is read, use run\_read\_only\_website\_tool instead. Run writes on different sites at once, and writes on the same site one after another. > * Send an idempotency\_key with every write, and reuse it only to retry the same call. A call without one is a new website action. > * It waits up to 50 seconds (5 minutes when your client accepts SSE). If the job asks a question or is still running, follow it with get\_job instead of calling again. > * When a run's tool breaks, the run returns with maintenance.status repairing while Pomerado fixes the tool. Don't cancel it. Keep calling get\_job, and keep the connection open, until the repair ends. > * A tool that signs in uses the site's saved login; login\_id picks one when there are several. With none saved, the job asks the user on its answer page. > * When a job needs a login, a code or an approval, give the user its answer\_url. Never ask for a password in chat. > * When a job asks any other question (input\_request), follow its next: answer it yourself with answer\_job if your task already settles it, otherwise ask your user and answer with theirs, or, when your user chose to answer on a form, give them its answer\_url; then keep following the job with get\_job. > * Status needs\_input with error credentials\_rejected means the website rejected the saved login. The job waits up to 60 minutes for the user to correct it on the page its next names, then resumes. Don't resubmit it. > * A result too big to answer inline comes as result\_file, a single-use result.json link, with result\_preview showing its start: download it once, such as with curl -o result.json \. A file a run downloads comes as a \$file whose limits give the caps its account's files keep to (max\_file\_bytes, max\_files\_per\_run, max\_run\_bytes); a download over them fails the run, and for larger files the user can email [founders@pomerado.ai](mailto:founders@pomerado.ai) (ask the user first; never email on your own). > > **Effect:** May change a website or your account, and a change may not be undoable. # search_pomerado_api Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/search_pomerado_api Search the Pomerado API for everything beyond running and building website tools: integrations and their MCP setup, API keys, saved logins, connected apps, usage, webhooks and more. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `query` | No | string, up to 200 characters | Words to look for in operation names and descriptions; omit to list all | | `group` | No | string, up to 60 characters | Only this group, such as api\_keys | | `intent` | No | string, up to 500 characters | Describe the intended use case in one sentence: what this call should do and why, such as "Check a store's opening hours to answer when it opens today". Describe the task, not the person: leave out names, contact details, account numbers and anything used to sign in. Pomerado uses it to improve its tools. Optional, but recommended: please include it on every call. | ## Output `operations`: each matching operation, with its group, summary and effect, those you can use first. One your plan, team role or API key doesn't allow follows them with `unavailable`: the error its call would answer, whose `message` says why and how to fix it and whose `details` name the permission (and, for a key, `keys_url`). Operations only the Dashboard calls are never listed. ```json Arguments theme={null} { "group": "team" } ``` ```json Result theme={null} { "operations": [ { "operation": "team.get", "group": "team", "summary": "Get team settings", "effect": "read" }, { "operation": "team.list_members", "group": "team", "summary": "List team members", "effect": "read" }, { "operation": "team.update_member", "group": "team", "summary": "Change a member's role", "effect": "write", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } }, { "operation": "team.remove_member", "group": "team", "summary": "Remove a team member", "effect": "destructive", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } }, { "operation": "team.list_invitations", "group": "team", "summary": "List invitations", "effect": "read", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } }, { "operation": "team.invite", "group": "team", "summary": "Invite team members", "effect": "write", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } }, { "operation": "team.resend_invitation", "group": "team", "summary": "Resend an invitation", "effect": "write", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } }, { "operation": "team.revoke_invitation", "group": "team", "summary": "Revoke an invitation", "effect": "write", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } }, { "operation": "team.get_intent_settings", "group": "team", "summary": "Get the team's intent setting", "effect": "read", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } }, { "operation": "team.update_intent_settings", "group": "team", "summary": "Change the team's intent setting", "effect": "write", "unavailable": { "code": "role_permission_missing", "message": "Your role in this team doesn't include the \"Manage the team\" permission (team:manage), which this request needs. Ask a team Owner or Admin to do it, or to change your role on the Team page in the Pomerado Dashboard.", "retryable": false, "docs_url": "https://docs.pomerado.ai/errors#role_permission_missing", "details": { "permission": "team:manage" } } } ] } ``` > [accordion] Details > > * Lists the operations you can use first, each with its effect: read, write or destructive. > * Operations your plan, team role or API key doesn't allow follow, each with unavailable: the error its call would answer, saying why and how to fix it, such as creating a key that includes the permission. > > **Effect:** Reads only; changes nothing. # sign_in Source: https://docs.pomerado.ai/mcp-reference/pomerado-mcp/sign_in Sign this agent in to Pomerado without a browser on this machine. ## Arguments | Argument | Required | Type | Description | | - | - | - | - | | `expires_in_days` | No | number | When starting: how many days the API key lasts, 1 to 90 (default 30). | | `device_code` | No | string | The device\_code from the call that started the sign-in. Omit it to start one. | ## Output Started, the code the person enters and where: `user_code`, `verification_url`, `interval`, `expires_at` and `device_code`. Polled with `device_code`, its `status`, and once approved the new API key with its `secret`, shown once. ```json Arguments theme={null} {} ``` ```json Result theme={null} { "user_code": "BDFG-HJKL", "verification_url": "https://app.pomerado.ai/auth/device", "interval": 5, "expires_at": "2026-10-09T17:10:00.000Z", "device_code": "ZXhhbXBsZS1kZXZpY2UtY29kZS1mb3ItdGhlLWRvY3M" } ``` > [accordion] Details > > * Call it without device\_code to get verification\_url and user\_code. Give both to the person, who opens the link on any device, signs in and approves. > * Then call it again with device\_code every interval seconds. It returns status pending until they approve, then the API key, once. > * Send the key as the Authorization: Bearer header of this MCP's signed-in URL (for a keyless connection, its URL without /keyless). It works on every Pomerado MCP and the REST API. > * Never ask the person for a password. > > **Effect:** May change a website or your account. # Build a tool Source: https://docs.pomerado.ai/quickstart/build-a-tool Build a new tool when no existing tool covers your task. Describe a task and a site, and Pomerado builds a tool for it on the real site, then reuses it. Building needs you to be signed in. ## From an MCP client Ask your agent, for example: "Build a tool that searches Instacart for a product near a ZIP code." It calls `build_website_tool`: ```json theme={null} { "intent": "Build a tool to search Instacart for a product near a ZIP code", "task": "Search Instacart for a product and return the matches at each store with name, size and price", "site_url": "https://www.instacart.com", "effect": "read", "example_input": "{\"query\": \"oat milk\", \"zip\": \"94103\"}" } ``` The agent tells you it's building, then follows the job with `get_job`. ## From the REST API ```bash theme={null} curl https://api.pomerado.ai/v1/builds \ -H "Authorization: Bearer $POMERADO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: build-instacart-search-1" \ -d '{"task": "Search Instacart for a product and return the matches at each store with name, size and price", "site_url": "https://www.instacart.com", "effect": "read", "example_input": {"query": "oat milk", "zip": "94103"}}' ``` The answer is the build's job. Read it with `GET /v1/jobs/{id}`; `build.stage` shows progress. ## Use the new tool When the job succeeds, its `tool_id` names the new tool, and `build.example_result` holds the result of the example it ran. [Run it](/quickstart/run-a-tool) like any other tool. > [accordion] Details > > * Set `effect` to `read` when the tool only looks things up, `write` when it changes the site, or `ask` to have the build work it out from the task, asking you first only when the task leaves it unclear. > * A write build makes the change once, with your example input. Don't repeat it. > * If the build needs your website login, the job asks for it on a protected page. Never paste a password into chat. > * If a tool already covers the task, the job names it as `tool_id` instead. > * Ticket, bank, government and link shortener sites are refused with `site_not_supported`. > * More in the [build overview](/guides/build/overview). ## Next See it in use: a [personal assistant](/examples/personal-assistant), a [backend application](/examples/backend-application) or a [product agent](/examples/product-agent). # Connect to Pomerado Source: https://docs.pomerado.ai/quickstart/connect Connect an MCP client to the Pomerado MCP, or create an API key for the REST API. Connect an agent through MCP, or create an API key to call the REST API from your code. ## Connect an MCP client **1. Open your client's setup** On the Dashboard, open [Connect agent](https://app.pomerado.ai/connect) in the sidebar and choose your client. **2. Add the server** Run the command or paste the config it shows. In Claude Code, for example: ```bash theme={null} claude mcp add --transport http pomerado 'https://mcp.pomerado.ai/mcp' ``` **3. Sign in** Your client opens Pomerado's sign-in in your browser. In Claude Code, run `/mcp`, choose pomerado and sign in. Your agent now lists the Pomerado tools, such as `find_website_tool` and `run_website_tool`. > [accordion] Details > > * The page connects with an API key by default: it creates a key when it opens and puts it in the setup you copy. To sign in with your Pomerado account instead, choose Your Pomerado account. Claude Desktop signs in with your account only. > * **Let your agent do it**, the default, gives a setup prompt to paste into your agent. It carries the key and tells the agent how to add the MCP with it. > * A chat client that supports custom MCP connectors takes the same URL. See [client setup](/guides/connect/client-setup). > * An agent without a browser can [sign in with a code](/guides/authentication/sign-in-with-a-code). ## Get an API key Create a key on the Dashboard under Settings, then [API keys](https://app.pomerado.ai/settings/api-keys), or copy the key the Connect agent page made for you. The secret is shown once, so store it in a secret manager. Send it as a bearer token: ```bash theme={null} curl https://api.pomerado.ai/v1/account \ -H "Authorization: Bearer $POMERADO_API_KEY" ``` A new key lasts 90 days and carries your permissions, except managing API keys, billing and the team. See [API keys](/guides/authentication/api-keys). ## Next [Try Pomerado without a key](/quickstart/try-without-a-key), or [find a tool](/quickstart/find-a-tool). # Find a tool Source: https://docs.pomerado.ai/quickstart/find-a-tool Search Pomerado's tools by task, by site or both. Search Pomerado's tools by words, by site or both before you run or build one. ## From an MCP client Ask your agent, for example: "Find a Google Flights tool that searches flights." It calls `find_website_tool`: ```json theme={null} { "intent": "Look for an existing tool that searches flights before running or building one", "query": "search flights", "site_url": "google.com" } ``` ## From the REST API ```bash theme={null} curl "https://api.pomerado.ai/v1/tools?query=search+flights&site_url=google.com" \ -H "Authorization: Bearer $POMERADO_API_KEY" ``` ## Read the result Both match your query by meaning as well as keywords and answer a page of matching tools, best match first (abridged here): ```json theme={null} { "data": [ { "id": "tool_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "name": "search_flights", "description": "Search Google Flights for flights between two airports on a date.", "site": { "url": "https://www.google.com", "name": "Google Flights" }, "effect": "read", "input_schema": { "type": "object", "required": ["origin", "destination", "departure_date"], "properties": { "origin": { "type": "string" }, "destination": { "type": "string" }, "departure_date": { "type": "string" } } }, "login": null } ], "next_cursor": null } ``` The fields you need next: * `id`: pass it as `tool_id` to run the tool. * `effect`: `read` when the tool only looks things up, `write` when it changes the site. * `input_schema`: the input the tool takes. * `login`: the login fields and two-factor methods the tool's sign-in uses, or `null` when it needs no website login. > [accordion] Details > > * With both a query and a site, the first page also has `possible_matches`: other tools on that site that may cover the task even though the words didn't match. > * Pass `next_cursor` back as `cursor` for the next page. > * No tool fits? [Build one](/quickstart/build-a-tool). > * More in [find tools](/guides/tools/find-tools). ## Next [Run the tool](/quickstart/run-a-tool). # Run a tool Source: https://docs.pomerado.ai/quickstart/run-a-tool Run a tool once and get its result from the job it starts. Run a tool with its ID and an input that matches its input schema. Each run starts a job, which carries the result when it succeeds. ## From an MCP client For a read tool, your agent calls `run_read_only_website_tool`, with the input as a JSON-encoded string: ```json theme={null} { "intent": "Check flight prices from SFO to JFK to find options for a November trip", "tool_id": "tool_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "input": "{\"origin\": \"SFO\", \"destination\": \"JFK\", \"departure_date\": \"2026-11-20\"}" } ``` The call usually returns the finished job in seconds. If the job is still running, follow its `next` (`get_job` with `wait_seconds`) instead of running the tool again. For a write tool, use `run_website_tool` and send an `idempotency_key`. ## From the REST API ```bash theme={null} curl https://api.pomerado.ai/v1/runs/read-only \ -H "Authorization: Bearer $POMERADO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"tool_id": "tool_0f8e2d1c4b3a49e8a7f6e5d4c3b2a190", "input": {"origin": "SFO", "destination": "JFK", "departure_date": "2026-11-20"}}' ``` REST answers at once with `202` and the job. Read the job after the `Retry-After` seconds, until its status is `succeeded`, `failed` or `cancelled`: ```bash theme={null} curl https://api.pomerado.ai/v1/jobs/job_6f1d0c2b3a4e45f8a9b7c6d5e4f3a2b1 \ -H "Authorization: Bearer $POMERADO_API_KEY" ``` ```json theme={null} { "id": "job_6f1d0c2b3a4e45f8a9b7c6d5e4f3a2b1", "type": "run", "status": "succeeded", "result": { "flights": [ { "airline": "Example Air", "departs_at": "08:05", "price": 289 } ] } } ``` Save the result when you read it: it is kept until its first read, and if never read, until `result_expires_at`: 60 minutes after the job ends, or at least 60 minutes after its repair ends while a repair is confirming it. > [accordion] Details > > * Run a write tool with `POST /v1/runs` and an `Idempotency-Key` header, so a retry returns the same job instead of acting on the site twice. See [retries and idempotency](/guides/tools/retries-and-idempotency). > * A job can stop with status `needs_input` to ask a question, such as a sign-in code. See [answer questions](/guides/jobs/answer-questions). > * Instead of polling, get a [webhook](/guides/notifications/webhooks) when a job asks a question or finishes. > * A tool that signs in uses your saved login for the site. See [logins](/guides/logins/overview). ## Next [Build a tool](/quickstart/build-a-tool) when none fits your task. # Try without a key Source: https://docs.pomerado.ai/quickstart/try-without-a-key Try Pomerado for free, with no account, sign-in or API key. Try Pomerado for free, with no account, sign-in or API key, by connecting your agent to the keyless URL: ```bash theme={null} claude mcp add --transport http pomerado 'https://mcp.pomerado.ai/mcp/keyless' ``` Then ask for something a public tool can look up, such as "Find nonstop flights from SFO to JFK next Friday on Google Flights." ## What you can do * Find tools. The list shows only the tools you can run without signing in. * Run public read-only tools that need no website login, such as a flight search, within a daily limit. * Follow those runs with `get_job`. ## What needs an account Building a tool, running a tool that changes a site or needs a website login, and answering or cancelling a job all need an account, as does the rest of the API. Each answers `sign_in_required`. Signing up is free. When the day's runs are used up, a run answers `try_limit_reached` until the next day. An account gives you the free plan's limits. ## Sign up or sign in Signing up and signing in are the same step. Ask your agent to sign in. Its `sign_in` tool gives you a link and a short code: open the link, sign in to Pomerado, or sign up if you have no account, and approve. The agent gets an API key and reconnects at `https://mcp.pomerado.ai/mcp`, without `/keyless`, with the key as its bearer token. See [sign in with a code](/guides/authentication/sign-in-with-a-code). > [accordion] Details > > * The REST API works without a key too: `GET /v1/tools`, `GET /v1/tools/{id}`, `POST /v1/runs`, `POST /v1/runs/read-only`, `POST /v1/integrations/{id}/tools/{tool_id}/runs` and `GET /v1/jobs/{id}` with no Authorization header. > * Without a key, the tool list carries a `notice` saying what an account adds and how to sign up or sign in. > * On MCP, `try_limit_reached` is a tool error with `retry_after_seconds`, so the connection stays usable. On REST it's a `429` with `Retry-After`. > * Each public integration's MCP has a keyless URL as well: its URL with `/keyless` added. > * Keep a job's ID private: without a key, anyone who has it can read that run. > * The keyless URL takes no key. Send one there and it answers `keyless_url_credentials` with the URL to use instead.