API
The base URL is https://app.adoor.halmoslabs.com. Send Authorization: Bearer <key> and a JSON body.
A key comes from one of three places:
- In ADoor, open Access and choose New connection. The key is shown once. This is the usual way.
- An app that other people connect signs them in with OAuth and receives a token for the access they approve.
- A job exchanges a workload token at
POST /oauth/token, described in Workload identity.
GET /api/operations lists the operations this key can use, with their input schemas. POST /api/operations/<name> runs one. The body is that operation's input.
The agent tools on HTTP and the CLI include data.query, data.execute, context.get, history.read, history.undo, files.upload, access.request, share.apply, organize.apply, billing.apply, account.apply, and data.export. MCP names the data tools query, execute, context, history, undo, import, request_access, and export. MCP does not publish share.apply, organize.apply, billing.apply, or account.apply. Each of those actions is its own MCP tool: invite_member, resend_invitation, revoke_invitation, update_member, remove_member, leave_workspace, create_connection, revoke_connection, update_connection, list_access_requests, resolve_access_request, rename_workspace, rename_space, move_table, add_reference, remove_reference, list_references, get_billing, get_usage, open_checkout, open_billing_portal, trim_history, get_account, rename_account, list_invitations, accept_invitation, and decline_invitation. Listing sessions, revoking a session, signing out everywhere, changing email, and deleting the account stay on the website and the CLI through account.apply. They are not MCP tools. data.execute runs SQL against this connection's own ADoor database, including DDL. It is not a shell. HTTP accepts SELECT in data.execute, paged at 1000 rows with truncated, nextCursor and rowCount. dryRun: true rolls INSERT, UPDATE, DELETE and SELECT back and commits nothing. A page without ORDER BY a unique key can repeat or skip rows when other writes commit. Pass workspace as an id or slug on those operations when this connection can open that workspace. Any other value is the same error as a missing workspace. MCP also lists spaces_create, spaces_archive, spaces_unarchive, and workspaces_create. The website still calls the other operations on this same endpoint. Federations and consent stay on the website. open_checkout and open_billing_portal return a link the user opens. Checkout records a claim, and either call may store a Stripe customer id. No card data is returned, and neither call changes the plan. trim_history drops undo images and can't be undone. data.export on MCP returns a download link.
These paths call the same operations:
GET /api/identityGET /api/tablesPOST /api/queryruns SQL the credential is allowed to run. A SELECT returns at most 1000 rows, withtruncated,nextCursorandrowCount. Passcursorto continue. A page withoutORDER BYa unique key can repeat or skip rows when other writes commit.GET /api/identityincludesexpiresAtandeffectiveExpiresAt.effectiveExpiresAtis the earliest of the connection clock, ancestor connection clocks, the access credential, and a browser sign-in when the call has one. A permission grant has no clock of its own. An OAuth access token lasts 1 hour and is refreshed.
SQL functions and operators
Caller SQL admits an allowlist. A function, operator, or statement shape that is not on it fails with UNSUPPORTED_SQL and names the function or operator.
Admitted operators are comparisons (=, <>, <, >, <=, >=), arithmetic (+, -, *, /, %, ^), boolean AND / OR / NOT, BETWEEN, IN, IS NULL, LIKE, ILIKE, SIMILAR TO, IS DISTINCT FROM, string ||, and JSON ->, ->>, #>, #>>, @>, <@, ?, ?|, ?&.
Admitted functions include:
- Aggregates that return one scalar:
count,sum,avg,min,max,bool_and,bool_or,stddev,variance, and the other standard one-value aggregates. string_agg,array_agg,json_agg, andjsonb_aggover a user table, including a query that also hasLIMIT. A free statement admits four of these growing aggregates, including an aggregateORDER BY. A paid statement admits eight. Any window form, includingOVER (), is rejected on both plans.- Text:
lower,upper,initcap,length,char_length,octet_length,substring,substr,left,right,trim,btrim,ltrim,rtrim,position,strpos,starts_with,ends_with,split_part,reverse. - Null handling:
coalesce,nullif. - Date and time:
now,current_date,current_timestamp,extract,date_part,date_trunc,age, andto_charwith a short literal format. - JSON field access and
jsonb_typeof,jsonb_array_length,jsonb_path_exists. generate_seriesandunnestas row sources when the following aggregate stays inside the produced-bytes budget.generate_seriesis limited to 1000000 rows. Literal numeric and timestamp bounds are counted, and integer arithmetic of literals is folded. A subquery, parameter, column, or other expression bound is rejected. A step of zero is rejected.SIMILAR TO, which PostgreSQL parses aspg_catalog.similar_to_escape.row_to_jsonof a user-table row.
WITH RECURSIVE is not available. ARRAY (SELECT ...), JSON_ARRAYAGG, and JSON_OBJECTAGG are not available. JSON_ARRAY(1, 2) and JSON_OBJECT('id': id) of ordinary values are available.
These calls produce a new value and are admitted only when that value, and every copy of it, stays within 1 MiB: repeat, lpad, rpad, format, replace, regexp_replace, translate, overlay, concat, ||, array constructors, array_to_string, json_build_array, jsonb_build_array, json_build_object, jsonb_build_object, and row_to_json of a subquery. A column of a CTE or subquery keeps the size of the expression that built it, so WITH a AS (SELECT repeat('x', 1000000) AS v) SELECT v || v FROM a is rejected. Either plan may include eight stored columns in one produced value. A free plan charges each at 1 MiB and a paid plan charges each at 8 MiB, so first || ' ' || last and jsonb_build_object('id', id, 'name', name) are admitted, and repeat of a stored column is admitted through 8. A ninth is rejected. A free plan limits each written value to 10000 leaf elements and 1 MiB. A paid plan limits each written value to 100000 leaf elements and 8 MiB. Nested arrays and jsonb count every leaf. The check runs before the row is stored, and a type change checks the column again at that plan's cap. A sort, DISTINCT, GROUP BY, or UNION is rejected when estimated width times estimated rows exceeds 64 MiB on a free plan or 256 MiB on a paid plan. Stored columns in that estimate use the plan write cap. An unknown user table contributes 16 rows on a free plan and 8 rows on a paid plan, so ORDER BY of name || '!' stays available. format accepts at most 32 conversions. replace and regexp_replace accept a short literal replacement and no backreference. A long replacement, a backreference, or a value that grows past 1 MiB is UNSUPPORTED_SQL.
A cross join, a comma join, or JOIN whose condition is not equality of two plain columns from different inputs is rejected when the product of the inputs' row caps is above 1000000. An unconstrained user table counts as 1000000 rows, so two user tables with no join condition are rejected, and so is a user table crossed with generate_series(1, 2). adoor_tables and adoor_columns are metadata views and count as one row in that product. <, <>, a * 0 = b * 0, and a filter on one side do not count as that equality. The same product includes every allowlisted set-returning function in a subquery, a CTE, a lateral join, a join input, or the select list. A call bounded by one stored row stays available, including unnest(tags), regexp_split_to_table(body, ','), unnest(ARRAY[1, 2]), and generate_series(1, 3) from a table. FROM docs d, unnest(d.tags) is admitted once when d.tags is a stored column of that plain table. A second such call, a cast, an unqualified name, or a CTE or subquery column counts in the row product. Equality does not bound a join when either side expands a stored column. USING and NATURAL stay available for two plain tables, and so does a.id = b.id when neither side is that expansion. ON true and ON a.id = b.id OR true do not. string_agg, array_agg, and json aggregates are rejected in any window. The database then explains the statement and rejects a plan that is too wide, too costly, or over 1000000 estimated rows. A free account shares a per-minute budget of statement time across its sessions.
GETandPOST /api/connectionsPOST /api/connections/<id>/revokeGETandPOST /api/sessions
curl https://app.adoor.halmoslabs.com/api/operations/spaces.list \
-H "Authorization: Bearer $ADOOR_KEY" \
-H 'Content-Type: application/json' -d '{}'
The same key is the bearer token for MCP. Connect has the steps.