Request Tracker 6.0.3 REST API 实用指南

REST 2.0 与 REST 1.0 全端点实测(RT 6.0.3):Auth Token 认证、Apache 反代丢 Authorization 头与 REST2 建单 body 格式两大生产坑排障,附 rt CLI 与 rt-crontool 定时批处理实测。

Request TrackerREST APICLI命令工单系统2026/10/24

Request Tracker 6.0.3 REST API 实用指南

RT 提供三代程序化接口:REST 2.0(JSON,官方主推)、REST 1.0(邮件格式,广泛存量)、以及封装 REST 1.0 的命令行工具 rt。本文在《Request Tracker 6.0.3 安装指南》部署的实例上全量实测:认证方式(token / session cookie)、10+ 个端点的请求格式、以及两个非常有价值的生产坑(Apache 反代丢 Authorization 头、REST2 建单 body 格式)。实测日期:2026-10-24。

一、API 全景与选择建议

接口路径数据格式认证状态
REST 2.0/REST/2.0/...JSONAuth Token(Authorization: token)或会话 Cookie主推(RT 4.4+)
REST 1.0/REST/1.0/...类邮件头文本会话 Cookie(表单登录)或 Basic(视配置)存量兼容
rt CLI/opt/rt6/bin/rt命令行~/.rtrc(内部走 REST 1.0)与系统同生命周期
rt-crontool/opt/rt6/sbin/rt-crontool命令行本地执行定时批处理

选择建议:新集成一律 REST 2.0(JSON、超媒体 _url 自描述、语义化 HTTP 状态码);维护老脚本用 REST 1.0;运维批量操作直接 rt CLI + rt-crontool 组合。

二、REST 2.0 实测

2.1 认证:Auth Token(实测推荐)

REST 2.0 的官方推荐认证是用户级 Auth Token(不是密码),生成入口:Web 登录 → 右上角 Logged in as root → Settings → About me(或 Preferences)→ Auth Tokens → New Token,填描述并输入自己的登录密码确认,token 只显示一次,格式 1-14-xxxx(用户 ID 开头)。

调用时附头(格式官方文档明确要求 Authorization: token <token>,中间的 token 一词不能省):

curl -H 'Authorization: token 1-14-85667bbb02f060bfb16da2d41221d05b' \
     'http://127.0.0.1/REST/2.0/ticket/1'

实测返回(JSON,节选):

{
   "Queue" : { "type" : "queue", "id" : "1",
               "_url" : "http://rt-server.example.com/REST/2.0/queue/1" },
   "Subject" : "CLI test ticket",
   "Status" : "open",
   "Requestors" : [ { "_url" : ".../REST/2.0/user/alice@example.com", ... } ],
   "Created" : "2026-10-24T05:59:54Z"
}

带认证失败时返回 {"message":"Unauthorized"}(HTTP 401)——实测 token 错误或(见 2.2)头没到后端都是这个表现。

2.2 生产坑 #1:Apache 反代下 Authorization 头丢失(实测排障)

  • 现象:直连后端一切正常,经 Apache mod_proxy_fcgi 反代后 REST2 全部 401 {"message":"Unauthorized"};同一个 token、同一个头格式。
  • 诊断过程:在本机另起一个独立 HTTP 实例(/opt/rt6/sbin/rt-server --port 8898)绕过 Apache,同样的 curl 命令立即返回 200 JSON——证明 token 有效,是 Apache 层把 Authorization 头吞了(Apache 处理到 mod_proxy_fcgi 时默认不把 Authorization 透传给 FastCGI 后端环境变量)。
  • 解法(Apache 2.4.52 实测可用):
    RewriteEngine On
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    
    实测加上后 REST2 token 认证全通。
  • 为什么不用现成指令:新版本 Apache 提供 CGIPassAuth On / ProxyFCGISetIf "true" HTTP_AUTHORIZATION "%{Authorization}s",但Ubuntu 22.04 的 Apache 2.4.52 尚无这两个指令(实测 configtest 直接报 Invalid command,apache 起不来),所以用 mod_rewrite 环境变量方案最稳。FCGI 协议会把 HTTP_AUTHORIZATION 环境变量作为 Authorization 头交给后端。
  • 提醒:若你的 Apache ≥ 2.4.62,优先用 ProxyFCGISetIf;≥ 2.4.62 也可用 CGIPassAuth On。

2.3 端点实测清单(全部实测通过)

以下实例均实测通过(T="Authorization: token <token>"):

① 查看单工单:

curl -s -H "$T" http://127.0.0.1/REST/2.0/ticket/1
# 实测:HTTP 200,返回完整 JSON(含 _url 超媒体链接)

② 建单(官方格式:字段顶层平铺):

curl -s -H "$T" -X POST http://127.0.0.1/REST/2.0/ticket \
  -H 'Content-Type: application/json' \
  --data '{"Queue":"General","Subject":"created via REST2","Requestor":["bob@example.com"]}'
# 实测输出:
# {"id":"3","_url":"http://rt-server.example.com/REST/2.0/ticket/3","type":"ticket"}
# HTTP 201 Created(响应头含 Location 指向新工单)

⚠ 生产坑 #2:建单 body 不能包 {"Fields":{...}}。实测把字段包进 Fields 会稳定返回 {"message":"Could not create ticket. Queue not set"}(HTTP 400)——REST2 的 validate_input 在反序列化后的顶层找 Queue,包了 Fields 顶层就只有 Fields 一个键。源码依据:lib/RT/REST2/Resource/Ticket.pm 的 validate_input 与 lib/RT/REST2/Resource/Record/Writable.pm 的 from_json(直接 decode_json($request->content) 取顶层)。PUT 更新同理用顶层平铺。

③ 更新工单:

curl -s -H "$T" -X PUT http://127.0.0.1/REST/2.0/ticket/1 \
  -H 'Content-Type: application/json' \
  --data '{"Status":"open"}'
# 实测输出:[] (空 JSON 数组 = 更新成功且无警告;失败时返回错误对象)

④ 评论(comment,内部备注):

curl -s -H "$T" -X POST http://127.0.0.1/REST/2.0/ticket/1/comment \
  -H 'Content-Type: application/json' \
  --data '{"Content":"comment via REST2","ContentType":"text/plain"}'
# 实测输出:[ "Comments added" ]

⑤ 回复(correspond,发请求人):

curl -s -H "$T" -X POST http://127.0.0.1/REST/2.0/ticket/1/correspond \
  -H 'Content-Type: application/json' \
  --data '{"Content":"reply via REST2","ContentType":"text/plain"}'
# 实测输出:[ "Correspondence added" ]

⑥ 历史事务(分页):

curl -s -H "$T" http://127.0.0.1/REST/2.0/ticket/1/history
# 实测输出(节选):
# { "per_page" : 20, "count" : 20, "pages" : 2,
#   "next_page" : "http://127.0.0.1/REST/2.0/ticket/1/history?page=2",
#   "items" : [ { "id" : "42", "type" : "transaction", "_url" : ".../transaction/42" }, ... ] }
# 取单条事务:GET /REST/2.0/transaction/42

⑦ 搜索工单:

curl -s -H "$T" "http://127.0.0.1/REST/2.0/tickets?query=Queue%3D%27General%27"
# 实测输出:{ "count" : n, "items" : [ { "id" : "1", "type" : "ticket", "_url" : ".../ticket/1" }, ... ] }
# 可加参数:orderby=Created&fields=Subject,Status (fields 控制返回字段)

⑧ 队列列表:

curl -s -H "$T" http://127.0.0.1/REST/2.0/queues/all
# 实测输出:{ "total" : 1, "items" : [ { "id" : "1", "type" : "queue",
#   "_url" : ".../REST/2.0/queue/1" } ] }
# 单队列详情:GET /REST/2.0/queue/1

⑨ 用户对象:

curl -s -H "$T" http://127.0.0.1/REST/2.0/user/root
# 实测输出:{ "_hyperlinks" : [ { "type" : "user", "ref" : "self", "id" : "14", ... } ], ... }

⑩ 认领/取回:POST /REST/2.0/ticket/1/take(另有 untake/steal,dispatch 规则同源码 82 行)。

REST2 响应的特征(实测总结):

  • 所有对象间引用都是 _url 超媒体链接(JSON HyperSchema 风格),顺着 _url 就能走完对象图。
  • 集合端点统一 { count, per_page, pages, items } 信封;/tickets 与 /queues/all 等"/all"变体区别在于是否强制包含禁用对象。
  • 写操作成功返回 [] 或操作结果数组;失败返回 {"message": "..."} + 4xx。
  • 注意区分单复数端点:/ticket/1(单条,可写)与 /tickets(搜索集合,POST 只作 GET 用——源码 RT::REST2::Resource::Tickets 带 ProcessPOSTasGET 角色)。想建单必须 POST 单数 /ticket。
  • RT 管理员可在配置中关停 REST2:Set($EnableREST2, undef);(默认开启)。

token 之外,REST2 也接受浏览器会话(先表单登录拿 Cookie):

JAR=/tmp/rt_cookies.txt
NEXT=$(curl -s -c $JAR http://127.0.0.1/ | grep -oE 'name="next" value="[^"]*"' | sed 's/.*value="//;s/"$//')
curl -s -b $JAR -c $JAR http://127.0.0.1/NoAuth/Login.html \
  --data-urlencode user=root --data-urlencode pass=你的密码 --data-urlencode next=$NEXT
curl -s -b $JAR http://127.0.0.1/REST/2.0/ticket/1      # 实测:200 JSON

实测要点:RT 6 登录表单带 next nonce 字段;登录后 CSRFToken 校验只拦 HTML 表单 POST,不影响 REST GET。适合人机混合脚本,服务端集成仍推荐 token。

三、REST 1.0 实测

REST 1.0 无 token 机制,认证即"登录一个会话":

JAR=/tmp/r1.txt
NEXT=$(curl -s -c $JAR http://127.0.0.1/ | grep -oE 'name="next" value="[^"]*"' | sed 's/.*value="//;s/"$//')
curl -s -b $JAR -c $JAR http://127.0.0.1/NoAuth/Login.html \
  --data-urlencode user=root --data-urlencode pass=你的密码 --data-urlencode next=$NEXT

实测通过的端点(响应第一行固定 RT/6.0.3 200 Ok,正文为邮件头风格):

查工单:

curl -s -b $JAR http://127.0.0.1/REST/1.0/ticket/1/show
# 实测输出(节选):
# RT/6.0.3 200 Ok
#
# id: ticket/1
# Queue: General
# Owner: Nobody
# Subject: CLI test ticket
# Status: open
# Requestors: alice@example.com

搜索(参数名是 query,不是 qid——实测用错参数会报 no value sent for required parameter 'query'):

curl -s -b $JAR "http://127.0.0.1/REST/1.0/search/ticket?query=Queue%3D%27General%27"
# 实测输出:
# RT/6.0.3 200 Ok
#
# 1: CLI test ticket
# 3: created via REST2

建单(content 表单字段承载邮件风格指令):

curl -s -b $JAR http://127.0.0.1/REST/1.0/ticket/new \
  --data-urlencode $'content=Queue: General\nSubject: created via REST1\nRequestor: carol@example.com\nText: body via REST1'
# 实测输出:
# RT/6.0.3 200 Ok
#
# # Ticket 4 created.

评论与改单(content 首行必须是 Action: comment / 直接字段):

curl -s -b $JAR http://127.0.0.1/REST/1.0/ticket/1/comment \
  --data-urlencode $'content=Action: comment\nText: REST1 comment via curl'
# 实测输出:RT/6.0.3 200 Ok / # Comments added
# 注意:不带 Action 头时实测返回 400 Bad Request "# Invalid action: `'."

curl -s -b $JAR http://127.0.0.1/REST/1.0/ticket/1/edit \
  --data-urlencode $'content=Status: resolved'
# 实测输出:RT/6.0.3 200 Ok / # Ticket 1 updated.

REST 1.0 的完整动词表(官方 rt.html 文档):ticket/new、ticket/<id>/show|edit|comment|correspond|history|links|merge、search/ticket、user/<id>、queue/<id>、attachment/<id> 等十余个。响应 RT/3.x.y <状态码> <消息> 开头是程序化解析的锚点。

四、rt CLI:REST 的命令行封装

/opt/rt6/bin/rt 内部走 REST 1.0,配置文件 ~/.rtrc(权限 600):

server http://127.0.0.1/
user root
passwd q1w2e3r4

实测命令集(每条均为实跑输出):

# 查看对象
rt show user/root          # 实测输出 Name: root / EmailAddress: root@localhost / Privileged: 1 ...
rt show ticket/1           # 邮件头风格全字段

# 搜索(TicketSQL)
rt ls "Queue = 'General'"                          # 实测:1: CLI test ticket / 3: created via REST2
rt ls -f Subject,Status "Queue = 'General' AND Status = 'resolved'"   # -f 控制输出列

# 建单
rt create -t ticket set "Subject=CLI test ticket" "Queue=General" \
    "Requestor=alice@example.com" "Text=First ticket created via rt CLI"
# 实测输出:# Ticket 1 created.

# 改单
rt edit ticket/1 set Status=resolved       # 实测输出:# Ticket 1 updated.
echo "Password: q1w2e3r4" | rt edit user/root set Password=q1w2e3r4
# 实测输出:# User root updated.

# 评论与回复
rt comment -m "internal note via CLI" ticket/1     # 实测:# Comments added
rt correspond -m "reply to requestor via CLI" ticket/1   # 实测:# Correspondence added

# 合并(注意:只接受数字 id,写 ticket/2 会报 Unrecognised argument,实测)
rt merge 2 1                               # 实测输出:# Merge completed.

# 帮助
rt help          # 总览;rt help list / show / create / edit / merge 分章

两个实测坑:

  1. rt merge 的参数是 rt merge <from-id> <to-id>,只收数字;ticket/2 形式会报 Unrecognised argument 'ticket/2'。
  2. 已 resolved 的工单再收到 correspond 会被重新打开(RT 默认生命周期行为)——本实验实测:resolve 后执行 rt correspond,工单状态自动回到 open。做自动化测试时别把这个当 bug。

五、rt-crontool:定时批处理

/opt/rt6/sbin/rt-crontool 是给 crontab 用的批处理引擎:搜索一批工单并对每张执行一个 Action。实测:

/opt/rt6/sbin/rt-crontool --search RT::Search::ActiveTickets \
    --set RT::Action::RecordComment \
    --template 'blank' \
    --argument 'daily scan via crontool'
# 实测:无输出且工单历史出现 "daily scan via crontool" 评论(rt CLI show 可见)

三段式结构:--search(选工单,如 RT::Search::ActiveTickets / RT::Search::FromSQL 配 --search-arg "Status='open'")、--set(动作,如 RecordComment / Notify)、--template(通知模板)。典型 crontab:

# 每日 8 点给逾期未回工单加提醒评论
0 8 * * * /opt/rt6/sbin/rt-crontool --search RT::Search::FromSQL --search-arg "Status='open' AND LastUpdated < '2 days ago'" --set RT::Action::RecordComment --template 'blank' --argument 'Auto reminder: no update for 2 days'
# 会话清理(官方建议每周)
30 0 * * 0 /opt/rt6/sbin/rt-clean-sessions --older 8D
# 邮件摘要
0 9 * * * /opt/rt6/sbin/rt-email-digest -m daily

(rt-clean-sessions / rt-email-digest 为安装自带工具,官方推荐周期运行。)

六、排障速查表

现象根因解法(本篇实测章节)
REST2 全部 401 {"message":"Unauthorized"},Web 界面正常Apache 反代丢 Authorization 头§2.2 mod_rewrite 环境变量转发(Apache 2.4.52 实测)
apachectl configtest 报 Invalid command 'CGIPassAuth' / 'ProxyFCGISetIf'Apache 2.4.52 无这两个指令同上用 mod_rewrite(§2.2)
REST2 建单 400 Could not create ticket. Queue not setbody 包了 {"Fields":{...}}字段顶层平铺(§2.3 ②)
REST2 更新成功但返回 [] 看着像失败[] 就是成功语义(无警告)判定逻辑按 HTTP 状态码 + 数组(§2.3 ③)
REST1 search 报 no value sent for required parameter 'query'参数名写成了 qid参数名 query(§三)
REST1 comment 400 Invalid actioncontent 缺 Action: comment 首行补 Action 头(§三)
rt merge ticket/2 ticket/1 报 Unrecognised argumentmerge 只收数字 idrt merge 2 1(§四)
rt CLI 报 Incorrect username or password~/.rtrc 里是旧密码,或 root 密码未初始化为约定值更新 .rtrc;改密 rt edit user/root set Password=...(§四)

七、参考链接