@ 论坛/ sdk
输入的脚本客户端 论坛 REST API:一个组织的文献、论坛、博客和其他一切。 它没有运行时间依赖关系,运行在Node 18+, Bun, Deno 和浏览器中( global) fetch) (中文(简体) ).
安装
npm install @forumulate/sdk
认证
Mint 个人访问 账户 —— 托肯斯 (单位:千美元)论坛.com/账户/托肯) (中文(简体) ). 象征性需要 read 读取和 read + 键 write 改变一切. API 令牌和范围 解释两道门是象征性的.
import { ForumulateClient } from '@forumulate/sdk';
// Reads FORUMULATE_TOKEN and FORUMULATE_URL (default https://forumulate.com).
const forumulate = ForumulateClient.fromEnv();
// …or explicitly. The API base is the site plus /api.
const explicit = new ForumulateClient({
baseUrl: 'https://forumulate.com/api',
token: process.env.FORUMULATE_TOKEN,
});
| 变量 | 默认 | 含义 |
|---|---|---|
FORUMULATE_TOKEN | 无 | 个人访问 Token, 发送为 Authorization: Bearer。 。 。 . |
FORUMULATE_URL | https://forumulate.com | 遗址. API是它的 API是它的 API是它的 /api。一个已经结束于 /api 用于当前. |
FORUMULATE_API_URL | 无 | 准确的 API 基础,用于本地 API 没有 /api 前缀( e)http://localhost:4000) (中文(简体) ). |
快速启动
const { user } = await forumulate.auth.me();
const { orgs } = await forumulate.orgs.list();
const orgId = orgs[0]!.id; // ids and slugs are both accepted wherever an org id is
// Every list is an async iterator over every row, across every page.
for await (const space of forumulate.spaces.list({ orgId })) {
console.log(space.slug, space.type);
}
每个操作都是一种方法
每个API的~1800操作都是以其操作Id命名的方法:
ForumTopics_get 实值 forumulate.forum.topics.get, (中文(简体) ), Webhook_rotate 实值
forumulate.webhooks.rotate。路径、查询、正文和回复从
openAPI 光谱和命名空间由它产生,所以在
API 参考 在这里是一种方法.
// One argument: path params at the top level, `query` and `body` nested.
const { space } = await forumulate.spaces.get({ orgId, spaceId });
await forumulate.blog.create({ orgId, spaceId, body: { title: 'v2 is out', content } });
await forumulate.webhooks.update({ orgId, webhookId, body: { active: false } });
await forumulate.webhooks.delete({ orgId, webhookId }); // `delete` aliases the API's `remove`
await forumulate.tokens.rotate({ id: patId });
await forumulate.forum.taxonomy.listCategories({ orgId, spaceId }); // a sub-namespace per area of a namespace
| 区域 | 命名空间 |
|---|---|
| 组织 | orgs, (中文(简体) ), spaces, (中文(简体) ), sites, (中文(简体) ), members, (中文(简体) ), invites, (中文(简体) ), roles, (中文(简体) ), grants, (中文(简体) ), groups, (中文(简体) ), tokens, (中文(简体) ), users, (中文(简体) ), billing, (中文(简体) ), sso, (中文(简体) ), scim |
| 文档 | items, (中文(简体) ), pages, (中文(简体) ), collections, (中文(简体) ), shapes, (中文(简体) ), snapshots, (中文(简体) ), changeRequests, (中文(简体) ), docs, (中文(简体) ), openapi, (中文(简体) ), comments |
| 社区和出版 | blog, (中文(简体) ), forum, (中文(简体) ), community, (中文(简体) ), moderation, (中文(简体) ), chat, (中文(简体) ), messages, (中文(简体) ), changelog, (中文(简体) ), roadmap, (中文(简体) ), newsletter |
| 空间类型 | status, (中文(简体) ), events, (中文(简体) ), courses, (中文(简体) ), faq, (中文(简体) ), careers, (中文(简体) ), legal, (中文(简体) ), forms, (中文(简体) ), questions |
| 平台 | webhooks, (中文(简体) ), domains, (中文(简体) ), themes, (中文(简体) ), extensions, (中文(简体) ), integrations, (中文(简体) ), notifications, (中文(简体) ), mail, (中文(简体) ), assets, (中文(简体) ), embeds, (中文(简体) ), privacy |
| 搜索、人工智能和分析 | search, (中文(简体) ), ai, (中文(简体) ), analytics |
| 无头CMS | content (图书馆、条目、模型、地方、密钥、资产.), delivery |
| 公共阅读器( 无符号) | public,则 public… 每个区域的子名称空间 : forum.publicTopic, (中文(简体) ), status.public, (中文(简体) ), blog.public. |
method.operationId 告诉你是什么操作 一个方法呼叫。 以 id 表示,类型相同:
const { space } = await forumulate.request('Space_get', { orgId, spaceId });
for await (const hook of forumulate.paginate('Webhook_list', { orgId })) console.log(hook.url);
0.5位助手(spaces.list(orgId), (中文(简体) ), items.create(orgId, spaceId, input)...) 仍然工作着
不变,但 auth.register 财务报告和审定财务报表 auth.login,在 0.6 中被删除:他们的路线在
签入后转到了阿特拉斯系统,所以每个呼叫者都失败了. 以个人访问托肯认证 。 如果用生成的方法分享一个名称,它就是相同的操作,而成员
无论是呼叫形状.
倾斜
列表返回 a PagePromise数字 : await 当API回答第一页时, for await 每行都是这样.
const first = await forumulate.blog.list({ orgId, spaceId, query: { limit: 50 } });
first.page.nextCursor; // the envelope: page.{limit, hasMore, nextCursor, total}, rows under data
for await (const post of forumulate.blog.list({ orgId, spaceId })) {
/* every post */
}
for await (const page of forumulate.blog.list({ orgId, spaceId }).pages()) {
/* every page */
}
走着走着 page.nextCursor,以及 ?page= 对于没有光标的排序列表。 这个
生成的方法发送 Forumulate-Version (SDK的版本生成于, API_VERSION, (中文(简体) ),
除非你设定 apiVersion),因为只有一个版本的答案携带了信封.
共同任务
1. 写出并出版 " 降标 " 一页
items 是类型不可知的表面: 相同的呼叫会写出一个 docs 页面、博客文章或论坛主题 .
const { item } = await forumulate.items.create(orgId, spaceId, {
title: 'Install',
slug: 'install',
body: '# Install\n\nRun `npm i acme`.',
});
await forumulate.items.publish(orgId, spaceId, item.id);
// Later: replace the body and republish.
await forumulate.items.setContent(orgId, spaceId, item.id, { body: '# Install\n\n…' });
await forumulate.items.publish(orgId, spaceId, item.id);
2. 列出空间中的所有内容
const res = await forumulate.items.list(orgId, spaceId, { limit: 100, include: ['body'] });
for (const it of res.items) console.log(it.id, it.title, it.contentIn);
// res.page.nextCursor pages onward; res.sync.checkpoint drives an incremental sync.
问 body。 。 。 . sections 仅为写在形状上的棋子而填入,所以空格为自由形式的页面答案 include: ['sections'] 单用空物体.
3. 搜索
// Everything you can see in an org: docs, forum, blog.
const { hits } = await forumulate.search.unified(orgId, 'rate limits', { limit: 10 });
// A public docs space, no token needed.
const anon = new ForumulateClient({ baseUrl: 'https://forumulate.com/api' });
const { hits: pub } = await anon.public.search(orgId, spaceId, 'rate limits');
4. 启动论坛专题并作出答复
const doc = (text: string) => ({
type: 'doc' as const,
content: [{ type: 'paragraph', content: [{ type: 'text', text }] }],
});
const { topic } = await forumulate.forum.createTopic(orgId, forumSpaceId, {
title: 'v2 is out',
content: doc('Here is what changed.'),
});
const topicId = (topic as { id: string }).id;
await forumulate.forum.reply(orgId, forumSpaceId, topicId, doc('Upgrade notes are in the docs.'));
5. 校验网签
import { constructWebhookEvent } from '@forumulate/sdk';
// Verifies the signature and its timestamp, THEN parses; throws if either fails.
const event = await constructWebhookEvent({
secret: process.env.FORUMULATE_WEBHOOK_SECRET!,
body: rawBody, // the exact bytes received, before JSON.parse
signature: req.headers['x-forumulate-signature'] as string,
timestamp: req.headers['x-forumulate-timestamp'] as string, // deliveries older than 5 min fail
});
verifyWebhookSignature (另见: forumulate.webhooks.verifySignature)是同一支票返回 a
布尔。 以 forumulate.webhooks.create({ orgId, body: { url, events } })· ;
webhooks.rotate, (中文(简体) ), webhooks.ping, (中文(简体) ), webhooks.deliveries 财务报告和审定财务报表 webhooks.replay 之后管理它.
6. 翻译,任何作品
client.translations 从空格类型中选择正确的路径:一个docs页面的语言双,一个帖子,主题或发行的翻译,或者一个事件. 你只给原作和语言取名. 机器翻译使用你的组织自己的DeepL密钥或AI提供者. 见 各地翻译。 。 。 .
const cov = await client.translations.status(orgId, spaceId); // piece × language → missing/draft/published/outdated
await client.translations.locales.set(orgId, { spaceId }, [{ code: 'es', label: 'Español' }]);
await client.translations.auto(orgId, spaceId, pageId, 'es');
const es = await client.translations.get(orgId, spaceId, pageId, 'es'); // { title, markdown, state }
await client.translations.put(orgId, spaceId, pageId, 'es', { markdown: '…', published: true });
7. 您公布的政策的大门注册和注册
client.policies 和三个独立的函数是服务器的一半
政策接受门: 您的合法空间
发布术语,隐私政策等,被用户在任何应用程序中接受,无论在任何标识中.
import { ForumulateClient, signPolicyTicket, verifyAcceptanceToken, policyGatePageUrl } from '@forumulate/sdk';
const client = ForumulateClient.fromEnv();
const space = { orgId: process.env.FORUMULATE_ORG!, spaceId: process.env.FORUMULATE_LEGAL_SPACE! };
// What does this user still have to accept before signing in?
const status = await client.policies.status({ ...space, subject: user.id, event: 'sign_in' });
if (!status.satisfied) {
// Send them to the hosted acceptance page with a ticket you sign yourself (no API call).
const ticket = await signPolicyTicket(
{ ...space, subject: user.id, event: 'sign_in', returnUrl: 'https://app.example.com/policy/return' },
process.env.FORUMULATE_POLICY_GATE_KEYS!, // "pgk_…:pgs_…"
);
return redirect(policyGatePageUrl({ ...space, ticket }));
}
// On the way back (or from the widget): verify the acceptance token offline, with key rotation.
const r = await verifyAcceptanceToken(token, {
keys: process.env.FORUMULATE_POLICY_GATE_KEYS,
subject: user.id,
requireSatisfied: true,
});
还有 client.policies.accept 编号 : claim 编号 : verify 编号 : acceptances 编号 : export 编号 : erase,以及
client.policies.list({ spaceId, secret }) 读取 Forumulate 策略 feed v1.
verifyPolicyWebhook(headers, rawBody, secret) 检查a policy.published 编号 : policy.retired
通知,以及 signPolicyFeedRequest 编号 : verifyPolicyFeedRequest 执行种子的要求
签名,所以任何平台都可以消耗feed或服务自己的. Auth0的食谱 苏帕基地的办事员,
Firebase, Auth.js 和一个自定义后端 : 政策门秘方。 。 。 .
每个终点 : typed()
以上帮助者覆盖了通路. typed(method, path, init) 覆盖 OpenAPI 光谱中的每个路由, 并包含路径、 参数、 查询、 车体和响应全部推断, 以及路径和方法上的自动完成 :
const { spaces } = await forumulate.typed('get', '/orgs/{orgId}/spaces', {
params: { orgId },
query: { limit: 50 },
});
await forumulate.typed('post', '/orgs/{orgId}/spaces', {
params: { orgId },
body: { name: 'Docs', slug: 'docs', type: 'DOCS', visibility: 'PUBLIC' },
});
过 raw: true 对于未受伤害者 Response (二进制下载). 为了其他一切, request(method, path, { body, query, headers }) 是未键入的逃生舱门 typed 援助者们必定要援助他们.
响应文件( PDF, PNG, ZIP) 或流分辨率到 Response; 文本答案( CSV, Markdown, iCal) 解析为 a string。 。 。 .
错误
每个非-2xx响应都会发出一个 ForumulateError 带有 API 错误信封 : status, (中文(简体) ), code, (中文(简体) ),
message, (中文(简体) ), details (外地一级,用于 validation.failed, (中文(简体) ), requestId (在支持请求中引用)
财务报告和审定财务报表 retryAfter (秒,当API说).
import { ForumulateError } from '@forumulate/sdk';
try {
await forumulate.orgs.create({ body: { name: '', slug: '' } });
} catch (err) {
if (err instanceof ForumulateError && err.code === 'validation.failed') {
console.log(err.details); // { name: ['Required'], … }
}
}
A 类 401 表示该令牌已丢失或被撤销. A 类 403 说明它缺乏范围或能力 API 令牌和范围。一个网络失败,其寿命超过其重试是 ForumulateError 与 status: 0 财务报告和审定财务报表 code: 'network.error'。 。 。 .
报复和自责
安全重复的请求将在 408, (中文(简体) ), 429, (中文(简体) ), 502, (中文(简体) ), 503, (中文(简体) ), 504 网络
失败: 默认重复两次, 等待什么 Retry-After 说:
而不是睡觉过, 否则指数回落与焦躁。 A 类 500 未重审.
每个POST和PACH都带有 Idempotency-Key,每次呼叫一次,再使用一次
重试,所以写一个超时的回试,没有跑两次. 传出你自己去去除尘器
跨进程:
await forumulate.items.create({ orgId, spaceId, body, idempotencyKey: message.id });
每通电话都要 headers, (中文(简体) ), signal (单位:千美元) AbortSignal和 maxRetries。 。 。 .
选项
new ForumulateClient({
baseUrl: 'https://forumulate.com/api',
token,
apiVersion: '2027-02-01', // Forumulate-Version for every request (default: generated methods only)
maxRetries: 2, // 0 turns retries off
autoIdempotencyKey: true, // generated Idempotency-Key on POST/PATCH
userAgent: 'my-app/1.0', // default forumulate-sdk-js/<version> outside a browser
headers: { 'X-Trace': id }, // sent on every request
fetch: customFetch, // any fetch-compatible function
credentials: true, // browser on the same origin: send the session cookie instead of a token
});
默认 User-Agent 浏览器外的事项:边缘防火墙挑战默认库用户代理.
软件包没有运行时的依赖性,并且标记为无副作用 命名空间大约为100个KB未被忽略(约20个KB gziped),命名空间是先用后建.
链接
- API 参考: 每条路由, OpenAPI 文档位于
https://forumulate.com/api/openapi.json。 。 。 . - 指南 • 妇女 社区
@forumulate/cli:来自终端或CI的同名API.@forumulate/mcp: 连接克劳德, Cursor 或 VS 代码到您的组织.
维护者: 重新生成类型
src/schema.ts 备份 typed()。它是生成的,而且只有类型;不要手工编辑。 从部署的光谱中刷新它 :
curl -s https://forumulate.com/api/openapi.json -o packages/sdk/openapi.json
pnpm --filter @forumulate/sdk generate-schema # → src/schema.ts, then src/operations.gen.ts
src/operations.gen.ts 是命名空间后面的路由表( y)generate-operations 重建它
(仅此一次)。 安置在 scripts/generate-operations.mjs: 操作ID标签的短地图
前缀为命名空间,所以新标签在某个地方登陆时不需要编辑. src/coverage.test.ts 失败
当表格僵化时,当操作没有方法时,或者当一个0.5助手和生成一个
方法共享名称而不是操作.
许可证
麻省理工学院