skill 面向公开分发,不应包含部署机内网地址。SKILL.md / cli-reference / rest-api / instance-access 共 10 处替换;rest-api 双源表与 ssh 隧道 兜底整段删除(前端 rewrite 已让域名全功能可用)。
11 KiB
REST API Reference (direct HTTP)
How to call the inkreach-teable backend API directly with plain HTTP (curl / node:http / axios / any language) — no CLI involved. The CLI wraps exactly these routes, so everything it does is reproducible raw.
Use direct API when: integrating external scripts/services, the CLI lacks an endpoint (auth signup, share views…), writing automation script actions that call back into the instance, or working from a language/runtime without the CLI. For one-off table/automation operations, prefer the CLI.
1. Base URL & Authentication
BASE=https://table.inkreach.cc
TOKEN=teable_xxx_yyy # PAT; web UI Settings → Personal Access Tokens
curl -s "$BASE/api/auth/user" -H "Authorization: Bearer $TOKEN" # verify → 200 + user JSON
- Every authenticated call:
Authorization: Bearer <PAT>(+Content-Type: application/jsonon bodies). PAT scopes ∩ base role = effective permission. - Discover the configured base/token the CLI already uses:
inkreach-teable auth status, or read~/.inkreach-teable/config.json. - ⚠️ Node v24 global
fetch()(undici) can silently drop the Authorization header — usecurl,node:http, oraxiosinstead (details in development.md).
2. Endpoint Map
Verified route-for-route against src/lib/client.ts (the CLI's HTTP layer).
Records
| Method & route | Purpose |
|---|---|
GET /api/table/{tid}/record |
List/query records (params below) |
POST /api/table/{tid}/record |
Create one ({fields}) or many ({records:[{fields}]}) |
PATCH /api/table/{tid}/record |
Batch update — {records:[{id, fields}]} |
GET /api/record/{rid} |
Fetch one record |
PATCH /api/record/{rid} |
Update one — {fields} |
DELETE /api/table/{tid}/record?recordIds=recA&recordIds=recB |
Delete by ids |
List-query params (objects are passed JSON-stringified; repeated keys use
key[]): filter (JSON), projection (JSON array), search[] (keyword —
MUST be the search[] tuple form, plain search= is ignored), skip / take
(pagination; no orderBy on this fork — sort via view or sql). Response:
{ records: [{id, fields}], total? }.
Fields
| Method & route | Purpose |
|---|---|
GET/POST /api/table/{tid}/field |
List / create |
GET/PATCH/DELETE /api/table/{tid}/field/{fid} |
Read / update / delete |
PUT /api/table/{tid}/field/{fid}/convert |
Change type or options (PATCH strips options!) |
GET /api/table/{tid}/select-visibility/{rid}/{fid} |
Choices visible to a given row (visibilityFilter) |
Tables / Views / Presets
| Method & route | Purpose |
|---|---|
GET/POST /api/base/{bid}/table |
List / create (body {name, fields:[…]}) |
GET/PUT /api/table/{tid}, DELETE /api/base/{bid}/table/{tid} |
Read-update / delete table |
GET/POST /api/table/{tid}/view, GET/PATCH/DELETE /api/view/{vid} |
View CRUD |
GET/POST /api/table/{tid}/view-preset, PUT/DELETE …/view-preset/{pid} |
Shared view presets |
Automation (self-built engine — bid = base id)
| Method & route | Purpose |
|---|---|
GET/POST /api/base/{bid}/workflow |
List / create ({name, description?}) |
GET /api/base/{bid}/workflow/{wid} or /api/base/any/workflow/{wid} |
Read one (any-variant works cross-base) |
PATCH/DELETE /api/base/{bid}/workflow/{wid} |
Update metadata / soft delete |
POST …/workflow/{wid}/activate | …/deactivate |
Activate (creates snapshot) / deactivate |
PUT …/workflow/{wid}/node |
Whole-graph node replacement ({nodes:[…]}) |
POST …/workflow/{wid}/test |
Dry-run → {nodeResults} |
GET/POST …/workflow/{wid}/secret, PUT/DELETE …/secret/{sid} |
Secrets (write-only values) |
GET …/workflow/{wid}/execution, GET …/execution/{eid}, POST …/execution/{eid}/rerun |
Run history / rerun |
POST /api/table/{tid}/record/{rid}/{fid}/button-click |
Fire a button field |
POST /api/webhook/{bid}/{wid} |
Fire a webhook (⚠️ real route — the UI shows /api/webhook/base/{bid}/workflow/{wid} which is WRONG; authorization is never validated) |
Misc
| Method & route | Purpose |
|---|---|
POST /api/base/{bid}/sql |
Read-only SQL ({sql}; JOIN/GROUP BY; no record ids) |
GET /api/table/{tid}/aggregation/row-count |
Total row count ({"rowCount":N} — verified) |
GET /api/table/{tid}/aggregation… |
Stats family (group-points, calendar-daily, search-count…) — see swagger for params |
GET/POST /api/access-token, DELETE /api/access-token/{id} |
PAT management |
POST /api/auth/signup |
Create account (open on this instance) |
3. Canonical Examples
BASE=https://table.inkreach.cc; AUTH="Authorization: Bearer $TOKEN"
# create records (single + bulk). typecast: accepts strings for non-text
# fields; fieldKeyType 'id' = keys are fld ids ('name' = field names)
curl -s -X POST "$BASE/api/table/tblXXX/record" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"fieldKeyType":"id","typecast":true,"records":[{"fields":{"fldTitle":"hello","fldStatus":"Todo"}}]}'
# batch update — each record needs its id
curl -s -X PATCH "$BASE/api/table/tblXXX/record" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"fieldKeyType":"id","typecast":true,"records":[{"id":"recXXX","fields":{"fldStatus":"Done"}}]}'
# query with filter (JSON must be url-encoded), pagination
FILTER='{"conjunction":"and","filterSet":[{"fieldId":"fldStatus","operator":"is","value":"Todo"}]}'
curl -s -G "$BASE/api/table/tblXXX/record" -H "$AUTH" \
--data-urlencode "filter=$FILTER" --data-urlencode "take=50" --data-urlencode "skip=0"
# change field options — MUST go through convert (PATCH strips options)
curl -s -X PUT "$BASE/api/table/tblXXX/field/fldXXX/convert" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"type":"singleSelect","options":{"choices":[{"name":"Todo"},{"name":"Done"}]}}'
# automation: create → wire nodes → activate. Node JSON uses the SELF-BUILT
# contract: trigger "conditions" (not filter), "fieldIds" (not watchFieldIds),
# category "recordCondition" (not recordMatchesConditions) — full shapes in
# trigger-types.md / action-types.md
curl -s -X POST "$BASE/api/base/bseXXX/workflow" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"name":"On Go click"}'
curl -s -X PUT "$BASE/api/base/bseXXX/workflow/wflXXX/node" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"nodes":[{"type":"trigger","data":{"triggerType":"buttonClick","tableId":"tblXXX","fieldIds":["fldXXX"]}},{"type":"action","data":{"actionType":"script","code":"return triggerInput.record.fields"}}]}'
curl -s -X POST "$BASE/api/base/bseXXX/workflow/wflXXX/activate" -H "$AUTH"
Inside an automation script action, don't hardcode the token — call back with the injected values:
const res = await fetch(triggerInput._apiBaseUrl + "/api/table/tblB/record", {
method: "POST",
headers: { Authorization: "Bearer " + triggerInput._accessToken, "Content-Type": "application/json" },
body: JSON.stringify({ records: [{ fields: { fldLog: "done" } }] }),
});
4. Gotchas (read before debugging a silent no-op)
typecast:truedoes NOT create select choices. An invalid singleSelect string resolves to null and the update becomes a silent no-op (mutationApplied=false): HTTP 200, response echoes the OLD value,__last_modified_timefrozen. Always write existing choice values — checkfield get/ select-visibility first.- 400 "does not meet the current row conditions" — value rejected by the
choice's
visibilityFilter. QueryGET /api/table/{tid}/select-visibility/{rid}/{fid}for the allowed set. - Field
optionsonly via the convert route — plainPATCH fieldstrips them. PUT …/nodeis whole-graph replacement — GET current nodes, modify, PUT all of them back. Node edits take effect only after re-activate(hasDraft: true= unactivated changes).- Response-error shape: non-2xx returns
{status, code, message};401= bad/expired token,403= scope or base-role too low. - To see the exact request the CLI sends for any operation, run it with
--debug(prints method/url/body) — the fastest way to learn a route.
5. Full Route Inventory (this guide ≠ the whole API)
The backend exposes ~362 paths; this guide documents the
agent-operational core (what the CLI wraps). The authoritative full spec is
the live OpenAPI JSON at $BASE/docs-json (browsable UI: $BASE/docs):
curl -s "$BASE/docs-json" | python3 -c "import json,sys; [print(m.upper(), p) for p,o in json.load(sys.stdin)['paths'].items() for m in o]"
⚠️ Swagger is a curated registry, NOT an auto-scan (audited 2026-09-23)
The doc is generated from an explicit registerRoutes() registry
(packages/openapi/src/generate.schema.ts), so any controller never
registered there is invisible in /docs. Cross-diffing every
@Controller route in the backend source against docs-json found gaps in
BOTH directions:
- Real routes missing from swagger (controller-verified, several
live-200): the whole authority-matrix family
(
/api/base/{bid}/authority-matrix*— the 4-layer permission system, seedocs/authority-matrix.mdin the app repo); select-visibility (GET /api/table/{tid}/select-visibility/{rid}/{fid}); plugin runtime (/api/plugin/{pid}/runtime*,upload-bundle,bridge,plugin-proxy/*,csp-origins); ShareDB internals (…/socket/doc-ids,…/socket/snapshot-bulk); oauth-server (/api/oauth/authorize,/decision,/access_token); import status; admin ops (performance-cache, heap-snapshot);/health+/health/memory(backend origin only — the domain 404s them). - Doc entries with no controller on this CE instance (~27:
/user-integrations*,/unsubscribe/*,/billing/*,/admin/enterprise-license/*…): registered by the shared openapi package but served by the enterprise backend — they 404 here.
So the completeness check is the controller grep, not docs-json.
Route groups NOT detailed in this guide (see docs-json for shapes):
/api/auth/* (session signin/signout, password/email flows, oauth),
/api/user/* (profile, lang, timezone, avatar), /api/space/* +
/api/base/{bid}/node/* (spaces & folder tree), /api/share/* +
/api/base/{bid}/share (public share views, 17 paths),
/api/attachments/* (upload/signature/notify), /api/comment/{tid},
/api/notifications/*, /api/mail-sender, /api/import/* +
/api/export/* + /api/base/import* (CSV/Excel/Airtable),
/api/table/{tid}/selection (paste/copy ops), /api/table/{tid}/undo-redo,
/api/trash/*, /api/template/*, /api/pin/*, /api/organization/*,
/api/oauth/*, /api/admin/*, /api/billing, /api/instance/usage,
/api/webhook/{bid} (official teable webhooks — distinct from the
automation webhook route), plugin UI endpoints (/api/plugin/* panels,
chart, proxy), /api/v2/integrity, /api/v1/ai-proxy.
When you use one of these, verify the exact param shape before calling —
from docs-json, or from the backend
source:
grep -rh "@Controller(\|@Get(\|@Post(\|@Patch(\|@Put(\|@Delete(" apps/nestjs-backend/src/features --include='*.controller.ts'
in the inkreach-teable app repo. Do not guess key names (the automation
contract lesson applies instance-wide).