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 数据文件与导出端点。
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 的命令复现):
| 域 | 方法数 | 代表方法 |
|---|---|---|
| system | 4 | listMethods、methodHelp、methodSignature、multicall |
| databases | 9 | setup.create_new、setup.connect_existing、configured.list/patch/disconnect、upgrade_sql、privileges.* |
| servers | 2 | configured.list、configured.patch |
| schemas | 6 | add/delete/get/list/patch、privileges.* |
| tables | 15 | add/delete/get/list/list_joinable、import/get_import_preview、metadata.*、privileges.* |
| columns | 9 | add/delete/patch/list/list_with_metadata、add_primary_key_column、metadata.*、reset_mash |
| constraints | 3 | add/delete/list |
| records | 7 | add/delete/get/list/list_summaries/patch/search |
| data_modeling | 5 | add_foreign_key_column、add_mapping_table、split_table、move_columns、suggest_types |
| explorations | 6 | add/delete/get/list/replace/run |
| forms | 11 | add/delete/get/list/patch/submit、set_publish_public、regenerate_token 等 |
| roles | 8 | add/delete/list/get_current_role、configured.*(add/delete/list/set_password)、set_members |
| collaborators | 4 | add/delete/list/set_role |
| users | 7 | add/delete/get/list、patch_self/patch_other、password.* |
| analytics | 5 | get_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'" |
| -31025 | KeyError(参数结构缺键) | explorations.run 的 exploration_def 缺 database_id 键:"KeyError: 'database_id'" |
| -28009 | 对象不存在 | 对未配置库调 schemas.list:"DoesNotExist: Database matching query does not exist." |
| -30259 | SQL 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.listlimit: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)是固定搭配,实测方法签名均如此。
八、参考链接
- RPC 端点源码(0.12.0 路由定义):https://github.com/mathesar-foundation/mathesar/blob/0.12.0/mathesar/urls.py
- RPC 方法模块目录:https://github.com/mathesar-foundation/mathesar/tree/0.12.0/mathesar/rpc
- modernrpc(JSON-RPC 实现):https://github.com/mbelge/modernrpc
- JSON-RPC 2.0 规范:https://www.jsonrpc.org/specification
- 本专栏相关:《Mathesar 0.12.0 介绍与选型指南》(架构与 107 方法由来)、《Mathesar 0.12.0 Docker Compose 部署指南》(端点入口部署)、《Mathesar 0.12.0 配置参数详解》(上报地址等配置项)