# @apicity/x 中文文档

规范地址：https://npmcn.com/package/%40apicity/x/

版本：0\.27\.0
npm 元数据更新时间：2026-11-06T10:02:49.002Z
中文译文更新时间：2026-11-06T10:11:56.893Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40apicity/x
原文对照：https://npmcn.com/package/%40apicity/x/?lang=original
项目主页：https://github.com/justintanner/apicity#readme
源码仓库：https://github.com/justintanner/apicity

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：97.8 kB，31 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=18\.0\.0
- 运行依赖：1 个
- 首次发布：2026-05-11T02:11:20.416Z
- 最新版本发布：2026-11-06T10:02:48.814Z
- 近 30 天下载量：4037（2026-10-06 至 2026-11-04，npm 官方下载统计）
- 已发布版本数：90（近 12 个月 90 个）

## 中文简介

X (原Twitter)社会API供应商发布内容 (api.x.com).

## 安装

```sh
npm install @apicity/x
```

也可以使用：`pnpm add @apicity/x`、`yarn add @apicity/x`、`bun add @apicity/x`

## README

# @ apicity/ x (韩语)

[![npm](https://img.shields.io/npm/v/@apicity/x?color=cb0000)](https://www.npmjs.com/package/@apicity/x) [![dependencies](https://img.shields.io/badge/dependencies-1-blue)](https://npmcn.com/package/%40apicity/x/package.json) [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue?logo=typescript&logoColor=white)](https://npmcn.com/package/%40apicity/x/tsconfig.json)

X (原Twitter)社会API供应商发布内容 (api.x.com).

运行时间依赖性 :

-   `zod@^4.6.6` - 请求在每一个 POST 端点上附加计划 `.schema`

## 安装

```bash
npm install @apicity/x
# or
pnpm add @apicity/x
```

## 快速启动

```typescript
import { createX } from "@apicity/x";

const x = createX({ accessToken: process.env.X_ACCESS_TOKEN! });
```

## 设置

X 需要一个 **OAuth 2.0 用户文本访问令牌** 发送或上传 媒体。 仅 App 的 Bearer 令牌只读而被上传拒绝 和推特的终点.

### 1\. 配置您的 X 应用程序

打开 [控制台.x.com](https://console.x.com/) 并确保你的应用生活 在一个 **按用途付费** 项目——遗产 _免费_ 项目折旧 而 v2 端点拒绝其标志 `client-not-enrolled`移动 如果需要, Apps 列表中的应用.

然后打开应用程序并点击 **用户认证设置 – 设置**数字 :

-   应用程序类型 : **Web App, 自动 App 或 瓶** (这产生了客户机密)
-   应用权限 : **读写**
-   回调 URI : `http://127.0.0.1:8765/callback`
-   网站 URL: 任何有效的 URL

保存并复制 **OAuth 2.0 客户端标识** 财务报告和审定财务报表 **客户端机密**。 。 。 .

### 2\. 入计量

每笔按用途支付账单(~0.015美元/邮资). 打开 **计费 —— 信贷** 并装入最小值(通常为5美元)。 没有信用, 写出终点 返回时 `402 Your enrolled account does not have any credits to fulfill this request` 即使认证本身成功.

### 3\. Mint 访问符号

将脚本另存为 `mint-x-token.mjs` 运行它:

```bash
X_CLIENT_ID=<your-client-id> \
X_CLIENT_SECRET=<your-client-secret> \
  node mint-x-token.mjs
```

它打印一个授权 URL —— 打开, 点击 **授权应用**,则 帮助获取方向 `127.0.0.1:8765` 并打印访问权限 符号+刷新符号。 访问令牌持续2小时;刷新令牌 (通过) `offline.access` 瞄准镜),允许您将一个新的薄荷,没有 重新授权.

`mint-x-token.mjs` — OAuth 2.0 PKCE 帮助器

```javascript
import http from "node:http";
import crypto from "node:crypto";
import { createXOAuth } from "@apicity/x";

const CLIENT_ID = process.env.X_CLIENT_ID;
const CLIENT_SECRET = process.env.X_CLIENT_SECRET;
const REDIRECT = "http://127.0.0.1:8765/callback";
const SCOPES = [
  "tweet.read",
  "tweet.write",
  "media.write",
  "users.read",
  "offline.access",
].join(" ");

if (!CLIENT_ID || !CLIENT_SECRET) {
  console.error("Set X_CLIENT_ID and X_CLIENT_SECRET");
  process.exit(1);
}

const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto
  .createHash("sha256")
  .update(verifier)
  .digest("base64url");
const state = crypto.randomBytes(16).toString("hex");

const authURL = new URL("https://x.com/i/oauth2/authorize");
authURL.searchParams.set("response_type", "code");
authURL.searchParams.set("client_id", CLIENT_ID);
authURL.searchParams.set("redirect_uri", REDIRECT);
authURL.searchParams.set("scope", SCOPES);
authURL.searchParams.set("state", state);
authURL.searchParams.set("code_challenge", challenge);
authURL.searchParams.set("code_challenge_method", "S256");

console.log("Open this URL and click \"Authorize app\":\n" + authURL.toString());

const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, REDIRECT);
  if (!url.pathname.startsWith("/callback")) {
    res.writeHead(404).end();
    return;
  }
  const code = url.searchParams.get("code");
  if (!code || url.searchParams.get("state") !== state) {
    res.writeHead(400).end("bad state");
    server.close();
    process.exit(1);
  }
  const oauth = createXOAuth({
    clientId: CLIENT_ID,
    clientSecret: CLIENT_SECRET,
  });
  const tok = await oauth.post.v2.oauth2.token({
    grant_type: "authorization_code",
    code,
    redirect_uri: REDIRECT,
    code_verifier: verifier,
  });
  console.log(JSON.stringify(tok, null, 2));
  res.writeHead(200).end("Authorized — check your terminal.");
  server.close();
});

server.listen(8765, "127.0.0.1");
```

### 4\. 使用标志

```typescript
import { createX } from "@apicity/x";

const x = createX({ accessToken: process.env.X_ACCESS_TOKEN! });

await x.post.v2.tweets({
  text: "hello from @apicity/x",
});
```

### 5\. 刷新标志

进入标志在2小时后失效. 使用 `createXOAuth` 与新鲜 从第3步到薄荷新鲜的,无需重新授权。 刷新 符号可旋转——持续 `refresh_token` 从 X 时的响应中 返回一个,在省略时保留旧的.

```typescript
import { createXOAuth } from "@apicity/x";

declare const storedRefreshToken: string; // the refresh_token from step 3

const oauth = createXOAuth({
  clientId: process.env.X_CLIENT_ID!,
  clientSecret: process.env.X_CLIENT_SECRET!,
});

const tok = await oauth.post.v2.oauth2.token({
  grant_type: "refresh_token",
  refresh_token: storedRefreshToken,
});
```

## 真实世界的例子:发布视频

在X上张贴视频是四通八达的舞蹈 媒体上传, 附加字节, 最终启动转码, 民意调查,直到媒体准备好,然后附上结果 `media_id` 在微博。 以下信息系按来文照登,并不意味着 [`tests/integration/x-post-video.test.ts`](https://npmcn.com/tests/integration/x-post-video.test.ts) 并重玩对 [`tests/recordings/x_*/post-video_*/recording.har`](https://npmcn.com/tests/recordings/), (中文(简体) ), 因此反应形状与 X 实际返回的匹配.

```typescript
import { readFileSync } from "node:fs";
import { createX } from "@apicity/x";

const x = createX({ accessToken: process.env.X_ACCESS_TOKEN! });

// 1. Initialize a chunked upload — declare the media type, total
//    byte length, and category up-front. X reserves a media_id we'll
//    thread through every later call.
const bytes = readFileSync("./jump.mp4"); // 1,318,021 bytes in the recording
const init = await x.post.v2.media.upload.initialize({
  media_type: "video/mp4",
  total_bytes: bytes.length,
  media_category: "tweet_video",
});
const mediaId = init.data.id;
// → "2050123807214718976"

// 2. Append the bytes. For files >5MB slice the buffer into
//    segments and call append once per chunk with segment_index 0..n.
await x.post.v2.media.upload.append(mediaId, {
  media: new Blob([bytes], { type: "video/mp4" }),
  segment_index: 0,
});

// 3. Finalize. X queues server-side transcoding and returns
//    processing_info.state = "pending" while the worker is busy.
const fin = await x.post.v2.media.upload.finalize(mediaId);
// fin.data.processing_info → { state: "pending", check_after_secs: 1 }

// 4. Poll status until the media is ready. Honor
//    `check_after_secs` so the loop respects X's pacing hint.
let state = fin.data.processing_info?.state ?? "succeeded";
let wait = fin.data.processing_info?.check_after_secs ?? 1;
while (state === "pending" || state === "in_progress") {
  await new Promise((r) => setTimeout(r, wait * 1000));
  const status = await x.get.v2.media.upload(mediaId);
  state = status.data.processing_info?.state ?? "succeeded";
  wait = status.data.processing_info?.check_after_secs ?? 1;
}
// status.data.processing_info → { state: "succeeded", progress_percent: 100 }

// 5. Post the tweet, attaching the now-ready media id.
const tweet = await x.post.v2.tweets({
  text: "jump",
  media: { media_ids: [mediaId] },
});

console.log(tweet.data.id);
// → "2050123819986378933"
console.log(tweet.data.text);
// → "jump https://t.co/X8cTIpcy3s"
//   X auto-appends the attached media's t.co URL to the returned
//   text — the literal request body just had "jump".
```

**页:1**

-   `media_category` 必须与资产相匹配: `tweet_video`, (中文(简体) ), `tweet_image`, (中文(简体) ), `tweet_gif`,或 `amplify_video` 对于长形。 错位被拒绝 在定稿时,不初始化.
-   上传于 `data.expires_after_secs` (24小时). 如果你最后决定 但永远不要提及 `media_id` 在推特上,它是垃圾收集.
-   任何步骤投出错误 `XError` 与 `status` 和被剖析出的身体 随附,故 `try { ... } catch (e) { if (e instanceof XError) ... }` 给你上游 `errors[0].message` 或者说 `detail` 直接说.

## API 参考

7个终点跨出3组. 每个方法都映射出上游的 URL 路径 .

### 媒体

`GET` **`x.v2.media.upload`**

`GET https://api.x.com/2/media/upload{query}`

成本等级 : `prohibitive`

[上游区域](https://docs.x.com/x-api/media/get-media-upload-status)

```typescript
const res = await x.get.v2.media.upload({ /* ... */ });
```

资料来源: [`packages/provider/x/src/x.ts`](https://npmcn.com/package/%40apicity/x/src/x.ts)

`POST` **`x.v2.media.upload.append`**

`POST https://api.x.com/2/media/upload/{id}/append`

成本等级 : `prohibitive`

[上游区域](https://docs.x.com/x-api/media/append-media-upload)

```typescript
const res = await x.post.v2.media.upload.append({ /* ... */ });
```

资料来源: [`packages/provider/x/src/x.ts`](https://npmcn.com/package/%40apicity/x/src/x.ts)

`POST` **`x.v2.media.upload.finalize`**

`POST https://api.x.com/2/media/upload/{id}/finalize`

成本等级 : `prohibitive`

[上游区域](https://docs.x.com/x-api/media/finalize-media-upload)

```typescript
const res = await x.post.v2.media.upload.finalize({ /* ... */ });
```

资料来源: [`packages/provider/x/src/x.ts`](https://npmcn.com/package/%40apicity/x/src/x.ts)

`POST` **`x.v2.media.upload.initialize`**

`POST [https://api.x.com/2/media/upload/initialize](https://api.x.com/2/media/upload/initialize)`

成本等级 : `prohibitive`

[上游区域](https://docs.x.com/x-api/media/media-upload-initialize)

```typescript
const res = await x.post.v2.media.upload.initialize({ /* ... */ });
```

资料来源: [`packages/provider/x/src/x.ts`](https://npmcn.com/package/%40apicity/x/src/x.ts)

### 微博

`GET` **`x.v2.tweets`**

`GET https://api.x.com/2/tweets/{id}{query}`

成本等级 : `expensive`

[上游区域](https://docs.x.com/x-api/posts/get-post-by-id)

```typescript
const res = await x.get.v2.tweets({ /* ... */ });
```

资料来源: [`packages/provider/x/src/x.ts`](https://npmcn.com/package/%40apicity/x/src/x.ts)

`POST` **`x.v2.tweets`**

`POST [https://api.x.com/2/tweets](https://api.x.com/2/tweets)`

成本等级 : `expensive`

[上游区域](https://docs.x.com/x-api/posts/create-post)

```typescript
const res = await x.post.v2.tweets({ /* ... */ });
```

资料来源: [`packages/provider/x/src/x.ts`](https://npmcn.com/package/%40apicity/x/src/x.ts)

### 用户

`GET` **`x.v2.users.me`**

`GET https://api.x.com/2/users/me{query}`

成本等级 : `expensive`

[上游区域](https://docs.x.com/x-api/users/get-my-user)

```typescript
const res = await x.get.v2.users.me({ /* ... */ });
```

资料来源: [`packages/provider/x/src/x.ts`](https://npmcn.com/package/%40apicity/x/src/x.ts)

部分 [辣点](https://github.com/justintanner/apicity) 单相机.

## 许可证

麻省理工学院——见 [自由主义](https://npmcn.com/package/%40apicity/x/LICENSE)。 。 。 .

## 常见问题

### @apicity/x 是什么？

X \(原Twitter\)社会API供应商发布内容 \(api\.x\.com\)\.

### 如何安装 @apicity/x？

在项目目录运行 npm install @apicity/x，或使用 pnpm add / yarn add / bun add。

### @apicity/x 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @apicity/x 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @apicity/x 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=18\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @apicity/x 可以商用吗？

@apicity/x 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @apicity/x 的下载量有多少？

根据 npm 官方下载统计，@apicity/x 在 2026/10/06 – 2026/11/04 这 30 天内被下载 4,037 次，日均约 135 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @apicity/x 还在维护吗？

最新版本 0\.27\.0 发布于 2026/11/06（今天）；至今共发布 90 个版本，其中过去 12 个月发布了 90 个；首次发布于 2026/05/11。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

