Mathesar 0.12.0 API 使用详解

Mathesar 0.12.0 API 实战手册:/api/rpc/v0/ 端点会话认证、107 个 JSON-RPC 方法全景与 7 类错误码实测、databases/schemas/tables/records/explorations/roles/users 各域请求-响应实测回显、分页排序约定与 records.search 行为坑位、遗留 REST 数据文件与导出端点。

MathesarMathesar2026/10/30

Mathesar 0.12.0 API 使用详解

目标:把 Mathesar 0.12.0 的 API 面讲全讲透——JSON-RPC 2.0 端点的认证方式、107 个方法的完整清单、各数据域的请求/响应实测回显、错误码语义与通用分页/排序约定。本文所有请求与响应均为真实环境逐条执行所得(Ubuntu 22.04 宿主 + Mathesar 0.12.0 + PostgreSQL 17.6/16.15),未登录对照、错误参数对照一并实测。

一、环境信息

  • 操作系统:Ubuntu 22.04(x86_64)
  • Mathesar:0.12.0(mathesar/mathesar:0.12.0),Web/API 入口 http://localhost:8128(Caddy 反代)
  • API 实现栈:Django 4.2.29 + modernrpc(JSON-RPC 2.0),运行于 Gunicorn(WEB_CONCURRENCY=3)
  • 测试数据:元数据库 mathesar_django;业务库 warehouse(内置 PG 17.6,含 employees 表 oid 17574、departments 表 oid 17594);外部库 business(PG 16.15)
  • 演示账号:admin / q1w2e3r4(超级管理员)
  • 客户端工具:curl + 会话 Cookie 文件(cj.txt)

二、前置条件(端点/认证/客户端工具)

端点:0.12.0 的 RPC 端点是 POST /api/rpc/v0/(Django 路由源文件实测:path('api/rpc/v0/', views.MathesarRPCEntryPoint.as_view()))。注意不是 /rpc/——实测直接 POST /rpc/ 会落到前端 SPA 兜底页返回 404 HTML。

认证方式:Django 会话 Cookie。先用 /auth/login/ 完成登录拿到 sessionid,之后所有 RPC 请求带 Cookie 头即可;无需额外 CSRF 头(实测带 Cookie 的 POST 不带 X-CSRFToken 也正常执行)。每库还需配置「协作者」绑定角色(见 5.8 域),admin 超级管理员默认全通。

客户端工具:curl 即可(本文全程使用);任何 JSON-RPC 2.0 客户端(如支持 batch 的 HTTP 工具)同样适用。

方法自描述工具:服务端内置 system.listMethods(列全部方法)、system.methodHelp(单方法帮助)、system.methodSignature(签名)——三者实测匿名可调,是探索 API 的第一入口。

三、API 总览

3.1 协议与报文约定

  • 协议:JSON-RPC 2.0。请求体是数组(支持批量),每个元素含 id、jsonrpc: "2.0"、method、params;响应按 id 一一对应。
  • HTTP 状态码恒为 200:业务错误也返回 200,靠响应体里的 error 对象区分(实测未认证调用、参数错误均如此),客户端必须解析 JSON 而非看状态码。
  • 批量:一次 POST 可混排多个方法,服务端按序执行(本文多处示例即真实批量请求)。
  • 参数命名:params 为对象,键名与方法定义的 keyword 参数严格一致(方法名点号后的分段如 databases.configured.list 对应模块化注册)。

3.2 方法全景(107 个,实测枚举)

以 system.listMethods 枚举的 107 个方法,按域归类统计如下(完整原始清单可用 4.2 的命令复现):

域方法数代表方法
system4listMethods、methodHelp、methodSignature、multicall
databases9setup.create_new、setup.connect_existing、configured.list/patch/disconnect、upgrade_sql、privileges.*
servers2configured.list、configured.patch
schemas6add/delete/get/list/patch、privileges.*
tables15add/delete/get/list/list_joinable、import/get_import_preview、metadata.*、privileges.*
columns9add/delete/patch/list/list_with_metadata、add_primary_key_column、metadata.*、reset_mash
constraints3add/delete/list
records7add/delete/get/list/list_summaries/patch/search
data_modeling5add_foreign_key_column、add_mapping_table、split_table、move_columns、suggest_types
explorations6add/delete/get/list/replace/run
forms11add/delete/get/list/patch/submit、set_publish_public、regenerate_token 等
roles8add/delete/list/get_current_role、configured.*(add/delete/list/set_password)、set_members
collaborators4add/delete/list/set_role
users7add/delete/get/list、patch_self/patch_other、password.*
analytics5get_state/initialize/disable/upload_feedback/view_report

3.3 错误码语义(全部实测命中)

code含义实测触发方式
-32603内部错误(含认证失败)未带 Cookie 调 databases.configured.list:"Authentication failed when calling \"databases.configured.list\""
-32602参数无效system.methodHelp 用 method 传参(应为 method_name):"Invalid parameters: __system_method_help() missing 1 required positional argument: 'method_name'"
-31049服务端 TypeError(缺必填关键字参数)roles.configured.list 只传 database_id:"TypeError: list_() missing 1 required keyword-only argument: 'server_id'"
-31025KeyError(参数结构缺键)explorations.run 的 exploration_def 缺 database_id 键:"KeyError: 'database_id'"
-28009对象不存在对未配置库调 schemas.list:"DoesNotExist: Database matching query does not exist."
-30259SQL UndefinedTable列操作指向已不存在的关系:"UndefinedTable: Relation with OID 17575 does not exist \nSQLSTATE: 42P01"
-30188空值约束"NullValueNotAllowed: null values cannot be formatted as an SQL identifier \nSQLSTATE: 22004"

⚠️ 重要实测结论:「多传了拼错的键」与「缺必填键」表现不同——缺必填键抛 -31049/-31025;而拼错的额外键会被静默忽略(如 records.search 误传 other_filters 仍正常返回全表),排障时优先核对参数拼写而非猜测静默行为。

四、认证与首次调用(curl 实测)

4.1 登录拿会话

# 第一步:取 CSRF 令牌并保存 Cookie
csrf=$(curl -s -c cj.txt http://localhost:8128/auth/login/ \
  | grep -oP 'name="csrfmiddlewaretoken" value="\K[^"]+' | head -1)
# 第二步:提交登录(表单字段 username/password)
curl -s -b cj.txt -c cj.txt -o /dev/null -w 'login: %{http_code}\n' \
  -X POST http://localhost:8128/auth/login/ \
  --data-urlencode "csrfmiddlewaretoken=$csrf" \
  --data-urlencode "username=admin" \
  --data-urlencode "password=q1w2e3r4" \
  -H "Referer: http://localhost:8128/auth/login/"

实测回显:login: 302(302 跳转到 / 即登录成功,cj.txt 已含 sessionid)。

4.2 首个调用:枚举方法清单

# 匿名即可调用(system.* 无需登录,实测验证)
curl -s -X POST http://localhost:8128/api/rpc/v0/ \
  -H 'Content-Type: application/json' \
  -d '[{"id":1,"jsonrpc":"2.0","method":"system.listMethods","params":{}}]' \
  | python3 -m json.tool | head -8

实测回显(节选):

[
    {
        "id": 1,
        "jsonrpc": "2.0",
        "result": [
            "analytics.disable",
            "analytics.get_state",
            "analytics.initialize",
            "analytics.upload_feedback",
            "analytics.view_report",
            "collaborators.add",
            ...

4.3 认证边界实测

未登录调用业务方法(对照实验):

curl -s -X POST http://localhost:8128/api/rpc/v0/ \
  -H 'Content-Type: application/json' \
  -d '[{"id":1,"jsonrpc":"2.0","method":"databases.configured.list","params":{}}]'

实测回显(HTTP 200,body 为错误对象):

[{"id": 1, "jsonrpc": "2.0", "error": {"code": -32603, "message": "Authentication failed when calling \"databases.configured.list\""}}]

换成 system.listMethods 则正常返回方法清单——system 域匿名开放,业务域全部需要会话。

4.4 方法帮助与签名

curl -s -b cj.txt -X POST http://localhost:8128/api/rpc/v0/ \
  -H 'Content-Type: application/json' \
  -d '[{"id":11,"jsonrpc":"2.0","method":"system.methodHelp","params":{"method_name":"records.list"}}]'

实测回显(HTML 片段即帮助正文,节选):

[{"id": 11, "jsonrpc": "2.0", "result": "<p>List records from a table, and its row count. Exposed as `list`.</p><p>Args:     table_oid: Identity of the table in the user's database.     database_id: The Django id of the database containing the table.     limit: The maximum number of rows we'll return.     offset: The number of rows to skip before returning records from following rows.     order: An array of ordering definition objects.     filter: An array of filter definition objects.     grouping: An array of group definition objects. ...</p>"}]

⚠️ 注意参数名是 method_name 而非 method——传错实测报 -32602(见 3.3 错误码表)。

五、各 API 域详解

以下每域给出「核心方法 → 请求 → 实测响应」。示例中的 oid/id 均来自本文实测环境,读者环境需替换为自己的值(用 *.list 方法先查出来)。

5.1 databases / servers 域

databases.setup.create_new(在指定 PG 服务器上新建库并纳入管理)与 databases.setup.connect_existing(接管已有库)是接入数据面的起点。实测 connect_existing 的响应结构:

{"server": {"id": 2, "host": "mathesar-0.12.0-extpg", "port": 5432, "sslmode": "prefer"},
 "database": {"id": 2, "name": "business", "server_id": 2, "last_confirmed_sql_version": "0.12.0", "needs_upgrade_attention": false, "nickname": "外部业务库"},
 "configured_role": {"id": 2, "name": "pgadmin", "server_id": 2}}

databases.configured.list(列已配置库,最常用的起点方法):

curl -s -b cj.txt -X POST http://localhost:8128/api/rpc/v0/ \
  -H 'Content-Type: application/json' \
  -d '[{"id":101,"jsonrpc":"2.0","method":"databases.configured.list","params":{}}]'

实测回显:

[{"id": 101, "jsonrpc": "2.0", "result": [{"id": 1, "name": "warehouse", "server_id": 1, "last_confirmed_sql_version": "0.12.0", "needs_upgrade_attention": false, "nickname": "仓储示例库"}, {"id": 2, "name": "business", "server_id": 2, "last_confirmed_sql_version": "0.12.0", "needs_upgrade_attention": false, "nickname": "外部业务库"}]}]

servers.configured.list:

[{"id": 1, "host": "mathesar_db", "port": 5432, "sslmode": "prefer"}, {"id": 2, "host": "mathesar-0.12.0-extpg", "port": 5432, "sslmode": "prefer"}]

相关方法:databases.configured.patch(改昵称等元数据)、databases.configured.disconnect(断开管理,不删库)、databases.upgrade_sql(库内 msar SQL 层升级,响应头看 last_confirmed_sql_version 与 needs_upgrade_attention 追踪)。

5.2 schemas / tables 域

schemas.list(参数 database_id):

[{"oid": 17489, "name": "Library Management", "owner_oid": 10, "description": null, "table_count": 7, "current_role_owns": true, "current_role_priv": ["USAGE", "CREATE"]},
 {"oid": 2200, "name": "public", "owner_oid": 6171, "description": "standard public schema", "table_count": 2, "current_role_owns": true, "current_role_priv": ["USAGE", "CREATE"]}]

tables.list(参数 database_id + schema_oid):

[{"oid": 17574, "name": "employees", "type": "table", "schema": 2200, "owner_oid": 10, "description": null, "current_role_owns": true, "current_role_priv": ["SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER"]},
 {"oid": 17594, "name": "departments", "type": "table", "schema": 2200, "owner_oid": 10, "description": null, "current_role_owns": true, "current_role_priv": ["SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER"]}]

tables.add(建表,参数含 database_id、schema_oid、表名)实测返回 {"oid": 17574, "name": "employees", "renamed_columns": {}}——oid 是后续一切表级操作的标识。tables.list_joinable 返回外键可 JOIN 路径(实测片段):

{"joinable_tables": [{"base": 17574, "target": 17594, "join_path": [[[17574, 5], [17594, 1]]], "fkey_path": [[17602, false]], "depth": 1, "multiple_results": false}], "target_table_info": {"17594": {"name": "departments", "columns": {"1": {"name": "id", "type": "integer", "primary_key": true}, "2": {"name": "dname", "type": "character varying", "primary_key": false}}}}}

导入类方法 tables.get_import_preview / tables.import 配合 REST 面的 data_files 上传使用(见 5.9)。

5.3 columns 域

columns.list(参数 database_id、table_oid)实测回显(节选):

[{"id": 1, "name": "id", "type": "integer", "type_options": null, "nullable": false, "primary_key": true, "default": {"value": "identity", "is_dynamic": true}, "has_dependents": false, "description": null, "current_role_priv": ["SELECT", "INSERT", "UPDATE", "REFERENCES"]},
 {"id": 2, "name": "name", "type": "character varying", ...},
 {"id": 3, "name": "salary", "type": "numeric", ...},
 {"id": 4, "name": "hired_on", "type": "date", ...}]

要点:id 即 attnum,records 域的读写都用它做列标识;default.is_dynamic: true 表示 identity/序列类动态默认值。columns.add 实测批量建列返回新建列 id 数组([2, 3, 4]);外键列不在 columns 域,走 data_modeling.add_foreign_key_column(注意实测它要求 referrer_table_oid 关键字参数,漏传报 -31049)。

5.4 records 域(核心数据面)

records.list(读表,参数 database_id、table_oid、limit、offset、order、filter、grouping、return_record_summaries):

curl -s -b cj.txt -X POST http://localhost:8128/api/rpc/v0/ \
  -H 'Content-Type: application/json' \
  -d '[{"id":6,"jsonrpc":"2.0","method":"records.list","params":{"database_id":1,"table_oid":17574,"limit":2}}]'

实测回显(外键列 5 的关联摘要一并提供):

{"count": 2, "results": [{"1": 1, "2": "关羽", "3": 9500.75, "4": "2023-04-01 AD", "5": 1}, {"1": 2, "2": "张飞", "3": 8100, "4": "2023-07-15 AD", "5": null}], "grouping": null, "linked_record_summaries": {"5": {"1": "骑兵部"}}, "record_summaries": null, "joined_record_summaries": null, "download_links": null}

💡 技术拓展:响应体里行数据用「attnum 字符串 → 值」的字典而非列名对象("2": "关羽"),这是 Mathesar RPC 的独有约定;想要列名形式的结果,用 explorations.run(其输出为 output_columns + 别名键字典,见 5.5),两套读法在报表场景各有所长。

records.add(写入,键为 attnum)实测回显(插入张三丰一行):

{"results": [{"1": 4, "2": "张三丰", "3": 8800, "4": "2024-01-08 AD"}], "linked_record_summaries": null, "record_summaries": null}

records.patch(更新单行,record_id 定位)实测回显:

{"results": [{"1": 1, "2": "关羽", "3": 9500.75, "4": "2023-04-01 AD"}]}

records.delete(⚠️ 参数名是复数 record_ids 数组)实测回显 [3](删除的 id 列表);漏传实测报 -31049:"TypeError: delete() missing 1 required keyword-only argument: 'record_ids'"。

records.search(按 search_params 检索打分排序)——实测行为要点:{"value":"关"}(不带 attnum)返回全表 2 行;{"attnum":2,"value":"关羽"} 返回 0 行。方法帮助只说明「字符串列做基本匹配、非字符串列需精确匹配」,未定义对象必填键,错误结构不报错、静默返回空表或全表,生产使用前需先用自己表结构试出正确对象结构(或改用 records.list 的 filter 参数)。

5.5 explorations 域(自定义查询)

explorations.add(保存查询定义)实测请求与响应:

// 请求 params(节选)
{"database_id": 1, "name": "薪资一览", "base_table_oid": 17574, "schema_oid": 2200,
 "initial_columns": [{"attnum": 1}, {"attnum": 2}, {"attnum": 3}, {"attnum": 5}],
 "display_names": {"5": "部门"}, "description": "员工与部门关联查询"}
// 响应 result
{"id": 1, "database_id": 1, "name": "薪资一览", "base_table_oid": 17574, "schema_oid": 2200, "initial_columns": [{"attnum": 1}, {"attnum": 2}, {"attnum": 3}, {"attnum": 5}], "transformations": null, "display_options": null, "display_names": {"5": "部门"}, "description": "员工与部门关联查询"}

explorations.run(免保存直接跑;⚠️ exploration_def 对象内必须自带 database_id 键,缺失实测报 -31025):

curl -s -b cj.txt -X POST http://localhost:8128/api/rpc/v0/ \
  -H 'Content-Type: application/json' -d '[
{"id":17,"jsonrpc":"2.0","method":"explorations.run","params":{"database_id":1,"exploration_def":{"database_id":1,"name":"临时运行","base_table_oid":17574,"schema_oid":2200,"initial_columns":[{"attnum":1,"alias":"id"},{"attnum":2,"alias":"name"},{"attnum":3,"alias":"salary"},{"attnum":5,"alias":"dept_id"}],"display_names":{"dept_id":"部门"},"limit":10,"offset":0}}}]'

实测回显(节选——注意输出已是列别名字典 + 列元数据):

{"query": {"database_id": 1, "name": "临时运行", "base_table_oid": 17574, "schema_oid": 2200, "initial_columns": [{"attnum": 1, "alias": "id"}, {"attnum": 2, "alias": "name"}, {"attnum": 3, "alias": "salary"}, {"attnum": 5, "alias": "dept_id"}], "display_names": {"dept_id": "部门"}, "limit": 10, "offset": 0},
 "records": {"count": 2, "results": [{"id": 1, "name": "关羽", "salary": "9500.75", "dept_id": 1}, {"id": 2, "name": "张飞", "salary": "8100", "dept_id": null}]},
 "output_columns": ["id", "name", "salary", "dept_id"],
 "column_metadata": {"dept_id": {"alias": "dept_id", "display_name": "部门", "type": "integer", ...}}}

5.6 roles 域

roles.configured.list(⚠️ 必须同时传 database_id + server_id,实测漏传报 -31049):

[{"id": 1, "name": "mathesar", "server_id": 1}]

roles.list(列出库内 PG 角色)、roles.get_current_role(当前会话角色)、roles.configured.add/set_password/delete 管理连接凭据角色;roles.set_members 管理角色继承。

5.7 collaborators 与 users 域

users.add 实测回显:

{"id": 2, "username": "zhangsan", "is_superuser": false, "email": "zhangsan@example.com", "full_name": "", "display_language": "en"}

collaborators.add(把用户绑到库的 configured_role 上)实测回显:

{"id": 2, "user_id": 2, "database_id": 1, "configured_role_id": 1, "user_info": {"id": 2, "username": "zhangsan", "is_superuser": false, "email": "zhangsan@example.com", "full_name": "", "display_language": "en"}}

collaborators.list 实测可见 admin 自身的绑定:[{"id": 1, "user_id": 1, "database_id": 1, "configured_role_id": 1, "user_info": {"id": 1, "username": "admin", "is_superuser": true, ...}}]。users.list 实测回显:

[{"id": 2, "username": "zhangsan", "is_superuser": false, "email": "zhangsan@example.com", "full_name": "", "display_language": "en"}, {"id": 1, "username": "admin", "is_superuser": true, "email": "admin@example.com", "full_name": null, "display_language": "en"}]

调用顺序依赖:collaborators.add 前必须先 users.add(直接对不存在用户实测报 -28009 DoesNotExist: User matching query does not exist.)。

5.8 analytics 域

analytics.get_state 查询匿名统计开关状态,analytics.disable 关闭上报,analytics.initialize 同意并上报初始化报告。上报地址由配置参数 MATHESAR_ANALYTICS_URL 等决定(生产实测指向 analytics.mathesar.dev,内网部署可在环境变量覆盖,见《Mathesar 0.12.0 配置参数详解》4.7)。

5.9 遗留 REST 面(非 JSON-RPC)

除 RPC 外,0.12.0 保留少量 REST 端点(Django 路由实测),会话认证方式相同:

端点方法实测行为
/api/db/v0/data_files/GET返回已上传导入文件列表,实测 {"count": 0, "results": []};POST 用于上传 CSV/TSV
/api/db/v0/data_files/<id>/GET/PATCH单个数据文件详情/更新
/api/export/v0/explorations/GET导出查询结果;缺参实测 400:{"errors": {"database_id": ["This field is required."], "exploration_id": ["This field is required."]}}
/api/export/v0/tables/GET导出表数据(参数结构同上风格)

六、分页/过滤/排序通用约定

  • 分页:列表类方法统一 limit + offset,响应带 count 总数(实测 records.list limit:2 返回 count: 2、结果数组 2 行)。
  • 排序:order 为对象数组,元素 {"attnum": <列id>, "direction": "asc"|"desc"}。实测对 salary 列(attnum 3)asc 后张飞(8100)排前、关羽(9500.75)排后,排序生效:
{"count": 2, "results": [{"1": 2, "2": "张飞", "3": 8100, "4": "2023-07-15 AD", "5": null}, {"1": 1, "2": "关羽", "3": 9500.75, "4": "2023-04-01 AD", "5": 1}]}
  • 过滤:records.list 的 filter 为过滤定义对象数组(结构见 system.methodHelp 的 records.list 帮助原文);records.search 的 search_params 行为坑位见 5.4 实测说明。
  • 分组:grouping 数组定义分组列,响应 grouping 字段回显分组状态(实测未分组时为 null)。
  • 批量调用:一次 POST 混排任意多方法,id 对应取回(本文 4.2/5.1 示例即真实批量)。

七、常见问题(FAQ)

Q1:POST /rpc/ 返回 404 HTML? 端点路径错了。0.12.0 的 RPC 端点是 /api/rpc/v0/(/rpc/ 会落进前端 SPA 兜底路由返回 404 页面,实测确认)。

Q2:HTTP 200 却拿不到数据? 看响应体里的 error 对象——Mathesar 所有业务错误(含未认证 -32603)都走 200 + JSON-RPC error,客户端切勿以状态码判断成败。

Q3:明明登录了还报 Authentication failed? 确认请求带了 Cookie 头(curl 用 -b cj.txt);会话过期后重新登录即可。另外 REQUIRE_SSO_LOGIN=true 的实例本地口令登录被禁用,需改走 SSO 流程。

Q4:报 TypeError missing keyword-only argument 怎么排查? 这是方法定义的必填关键字参数没传全(实测案例如 records.delete 缺 record_ids、roles.configured.list 缺 server_id、data_modeling.add_foreign_key_column 缺 referrer_table_oid)。用 system.methodHelp 查该方法 Args 清单逐个补齐。

Q5:参数多传了一个拼错的键为什么没报错? 实测多余键被静默忽略(如 records.search 误传 other_filters 照常返回)。这会掩盖拼写错误,建议先 system.methodHelp 核对参数名再上生产。

Q6:oid 与 id 怎么区分? schema_oid/table_oid 是 PostgreSQL 侧的对象 OID(重建表会变);database_id/exploration_id/user_id 是 Mathesar 元数据库的 Django 主键。records/columns 域混用两者:database_id(Mathesar id)+ table_oid(PG oid)是固定搭配,实测方法签名均如此。

八、参考链接