Troubleshooting
MarkdownWhen a request can’t complete, the API returns an HTTP status and a short JSON message:
{ "error": "extension offline" }Common responses
Section titled “Common responses”| Response | Next step |
|---|---|
503 extension offline |
Open the connected browser. Check its status before trying again. |
503 extension outdated |
Apigami in that browser must be updated before it can connect again. Chrome installs the update on its own; if it hasn’t reconnected within a few minutes, ask the user to restart Chrome. |
403 not paired |
This user isn’t connected to this app. Send them through the connect page. |
403 account disabled |
Apigami disabled this account. Skip this person: your other users still answer, and this one may come back. |
403 app is private |
This person left your private app’s team. They pass again if they come back. |
404 unknown user_ref |
The reference isn’t one of your team’s. Check you saved the user_ref from the code exchange and use a key of the same team. |
403 consent requested |
The approval window is waiting for an answer. Let the user respond, then try again. |
403 no grant |
The user declined or removed access. They can change this from Apigami’s menu in their browser. |
401 bad client credentials |
Check the API key. It may be missing, incorrect or revoked. |
An unanswered approval also includes "consentPending": true. Don’t open a new connection
flow for this: the browser is connected and waiting for the user’s decision.
Handle an error in your code
Section titled “Handle an error in your code”const data = await response.json()
if (!response.ok) { if (data.consentPending) { console.log('Please answer the approval window in your browser.') } else { console.error(data.error) }} else { console.log(data.result)}Other responses
Section titled “Other responses”Account access and verification
| Response | Next step |
|---|---|
403 client disabled |
No member of the app’s team has an active account. Contact us if you think this is a mistake. |
403 client not verified |
This app has reached ten connected users. Contact us for verification. |
| Email not confirmed | Creating an app and using the MCP server require a confirmed address. The dashboard and the MCP server send the confirmation email; open its link, then try again. |
400 clientId does not match the token |
Remove appId or clientId from the body. The API key already identifies your app. |
Connecting a user
| Response | Next step |
|---|---|
400 invalid_grant from POST /connect/token |
The code is wrong, expired, already used or issued to another app, or code_verifier or redirect_uri doesn’t match. Start a new connection. |
400 redirect_uri is not registered for this app |
Register the URL in the dashboard, path included. Only the query and the fragment can vary. |
400 code_challenge (S256) is required |
Send a base64url SHA-256 challenge and code_challenge_method: "S256". |
error=access_denied on your redirect URL |
The user cancelled. Offer the connect button again. |
error=expired on your redirect URL |
The user took more than 30 minutes. Start a new connection. |
Choosing the user's browser
| Response | Next step |
|---|---|
403 install_ref is not a browser of this user |
Use an install_ref listed by GET /v1/users/{user_ref}/installs, or leave it out. |
409 no browser is attached to this user |
Ask the user to open your connect link in the Chrome they use and attach it. |
409 several browsers of this user are online… |
The site was never asked about and more than one browser could ask. Send install_ref. |
409 no browser of this user holds the permission for … |
None of the user’s browsers can reach that site. Ask the user to open that browser, or send install_ref. |
Invalid request parameters
| Message | Next step |
|---|---|
user_ref is required |
Send the user’s user_ref in the body. It replaces extensionId: get one by connecting the user. |
user_ref must be a string or install_ref must be a string |
Send the references as strings, exactly as Apigami returned them. |
op is required |
Include the name of the operation. |
params.domain is required |
Include a hostname such as example.com in params.domain. |
targetDomain is required |
Include the hostname in the approval request. |
These responses have HTTP status 400.
403 <domain> is Apigami's own domain answers a call or an approval request on apigami.com,
apigami.tech or a host under them, such as your application’s address, {slug}.apigami.tech: no
operation may run there and no approval window opens for it.
Emails and AI
emails.send and ai.generate are answered by Apigami, never a browser, and refuse with their own
messages, each with what to do: Emails to your users and
AI in your application.
Event and list query parameters
| Message | Next step |
|---|---|
since must be an ISO timestamp |
Send an ISO timestamp such as 2026-09-10T00:00:00Z. |
bad cursor |
Pass back the previous response’s next value without changing it. |
limit must be a number |
Send an integer. The default is 100 for GET /events and 500 for GET /v1/users and GET /v1/installs; the maximum is 1000. |
These responses have HTTP status 400. See Reading events and
Everyone connected to your app.
An operation failed or timed out
| Response | Next step |
|---|---|
502 extension disconnected |
The browser disconnected during the call. Check whether the action completed before retrying. |
502 operation has timed out |
The browser did not answer within 35 seconds, or stopped answering for 20 seconds during the call. Reduce the work and check for a result before retrying. |
502 operation failed or another browser message |
Check the URL, tab, selector and approved domain for the operation. |
tab <id> was closed or tab <id> is not on the granted domain <domain> |
Pass the tabId tabs.open returned, or one from tabs.list. A tab id belongs to one browser until it restarts. |
the tab opened on <domain> is still loading |
The tab has not reached the site yet. Try again in a moment. |
request.fetch answers status: 403 with x-datadome, cf-mitigated: challenge, or a body naming captcha-delivery or challenge-platform |
Anti-bot protection, not a sign-in problem. Open a tab of the site with tabs.open, then retry. Don’t read or copy the anti-bot cookie. |
403 <host> serves another application's pages, on its author's own domain… |
Another application, of your group or not, serves its pages at that name, its author’s own address, and they hold their token there: no operation of yours runs on that name or under it, and no approval window opens for it. Its parent domain and the names beside it stay reachable. If the name is yours, contact us. |
500 internal error |
An Apigami error occurred. Contact us if it persists. |
Your app’s address, https://{slug}.apigami.tech, answers 404 No application answers at this address. |
No version answers at that host: check the slug, deploy a version with app_deploy and publish it with app_publish. {slug}--staging serves the version your latest deployment put there, and answers this when there is none. A slug you renamed away from answers this too. |
A page of your app’s address answers 503 This page took too long or This page was stopped |
That request used more than its 300 seconds of computing time, or the 128 MB of memory its instance shares with the requests it serves at once: look for a loop that does not end, or move the work to an action. app_logs says which (Limits). |
A page of your app’s address answers 502 This page failed |
Your code threw while answering it: app_logs has what it printed. |
An action or GET /v1/me answers 401 bad run token to a page’s token |
The token expired or was renewed, or the request comes from another origin than its page’s. Use the latest token; apigami.js keeps it for you. Full page at your app’s address, the person’s session renews the token in silence until 30 days go by without a visit; on your own site, apigami.js renews it for twelve hours after the person went through your connect link. A 401 there means the session or those twelve hours ended, or the renewal was refused because the account, its connection or its browser no longer passes, or the person signed out of Apigami’s dashboard. apigami.js then sends the person to be connected again at the next action that needs one, and they come back to the page. |
An action’s answer has no X-Apigami-* header your code set |
Apigami removes them all: they are its own. X-Apigami-Settings is read before, to ask the person to set up or reconnect. |
A 502 whose body carries "outcome": "unknown" means the browser received the call and its answer
was lost: the request may have reached the site. Before repeating a write, such as sending a message,
read the site to see whether the first one went through.
Messages such as invalid url, no open tab for example.com or no element matches explain
what the browser couldn’t do. The operation guide lists the expected parameters.
API calls aren’t queued or retried automatically. Before retrying an action that changes something on a site, check whether it already completed. This avoids sending a message or submitting a form twice.