@apicity/xai

中文文档
0.26.0发布于 2026/11/05MITTypeScriptES Module近 30 天下载 4050

X.AI / Grok provider for chat and search.

原始使用文档
文档目录36 节
  1. Installation
  2. Quick Start
  3. Real-world example: structured vision analysis with Grok-4
  4. Imagine Files API integration
  5. Reference-to-video, pinned frames and preset voices
  6. Stored input field map
  7. Files Public URLs
  8. Realtime server transport
  9. API Reference
  10. apiKey
  11. batches
  12. chat
  13. customVoices
  14. documents
  15. files
  16. imageGenerationModels
  17. images
  18. languageModels
  19. managementApi
  20. models
  21. realtime
  22. responses
  23. stt
  24. tokenizeText
  25. tts
  26. videoGenerationModels
  27. videos
  28. Middleware
  29. Rate Limiting
  30. Using xAI tier presets
  31. Custom limits
  32. Shared limiter across endpoints
  33. Composing with retry
  34. Batch processing
  35. xAI rate limit tiers
  36. License

@apicity/xai

npm dependencies TypeScript

X.AI / Grok provider for chat and search.

Runtime dependencies:

  • ws@^8.21.0 — authenticated server WebSocket transport exported from @apicity/xai/realtime
  • zod@^4.6.6 — request schemas attached to every POST endpoint as .schema

Installation

npm install @apicity/xai
# or
pnpm add @apicity/xai

Quick Start

import { createXai } from "@apicity/xai";

const xai = createXai({ apiKey: process.env.XAI_API_KEY! });

Real-world example: structured vision analysis with Grok-4

Hand Grok-4 a portrait, a system prompt that nails down the output schema, and text.format.type: "json_object" — get back a reproduction-ready JSON description with deterministic shot/pose vocabulary. The flow below is taken verbatim from tests/integration/xai-vision-json.test.ts and replays against tests/recordings/xai_3613880225/vision-analysis-json_243984103/recording.har, so the response shapes match what xAI actually returns.

import { readFile } from "node:fs/promises";
import { createXai } from "@apicity/xai";

const xai = createXai({ apiKey: process.env.XAI_API_KEY! });

// 1. Load the image and inline it as a data URL. xAI also accepts
//    https:// URLs, but inlining keeps the call self-contained and
//    works against private hosts.
const image = await readFile("./portrait.jpg");
const base64 = image.toString("base64");

// 2. The system prompt enumerates the legal vocabulary for `shot` and
//    constrains `pose` to body geometry only. Combined with
//    `text.format.type: "json_object"` this gives Grok no room to drift
//    off-schema — temperature 0 keeps the result reproducible.
const SYSTEM_PROMPT = [
  "You are an expert image-to-prompt analyst.",
  "Return only a JSON object with keys prompt, shot, and pose.",
  "prompt: a single-paragraph reproduction-ready image prompt, 1900 characters or fewer, with no line breaks.",
  'shot: exactly "<size>, <angle>" where size is one of extreme close-up, close-up, medium close-up, medium shot, medium long shot, long shot, or extreme long shot, and angle is one of eye-level, low-angle, high-angle, overhead, or dutch.',
  "pose: only body geometry for human figures, with no clothing, hair, background, or lighting details.",
].join(" ");

// 3. Multimodal Responses request: system turn + a user turn whose
//    content is an array of `input_image` + `input_text` parts.
const result = await xai.post.v1.responses({
  model: "grok-4",
  input: [
    { role: "system", content: SYSTEM_PROMPT },
    {
      role: "user",
      content: [
        {
          type: "input_image",
          image_url: `data:image/jpeg;base64,${base64}`,
          detail: "high",
        },
        {
          type: "input_text",
          text: 'Analyze this image and produce a reproduction-ready JSON description with keys "prompt", "shot", and "pose".',
        },
      ],
    },
  ],
  text: { format: { type: "json_object" } },
  store: false,
  temperature: 0,
  max_output_tokens: 300,
});

// 4. The Responses API wraps output in a typed item array. Find the
//    assistant message, then the first `output_text` part inside it.
//    Discriminated unions narrow `item.type === "message"` so
//    `item.content` is statically typed.
const message = result.output.find((item) => item.type === "message");
const outputText =
  message?.type === "message"
    ? message.content.find((part) => part.type === "output_text")?.text
    : undefined;

if (!outputText) throw new Error("Grok did not return output_text");

const analysis = JSON.parse(outputText) as {
  prompt: string;
  shot: string;
  pose: string;
};

console.log(analysis.shot);
// → "medium close-up, eye-level"

console.log(analysis.pose);
// → "upright torso facing forward, head straight and centered, shoulders squared, arms relaxed downward (implied)"

// 5. Reasoning-token accounting. Grok-4 spent 623 of its 728 output
//    tokens reasoning before emitting the 105-token JSON answer —
//    surfaced in `usage.output_tokens_details.reasoning_tokens`.
console.log(result.usage);
// → {
//     input_tokens: 2684,
//     input_tokens_details: { cached_tokens: 679 },
//     output_tokens: 728,
//     output_tokens_details: { reasoning_tokens: 623 },
//     total_tokens: 3412,
//   }

Notes

  • store: false keeps the response off xAI's history surface. Flip to true to chain follow-ups via previous_response_id — useful for multi-turn refinement ("now describe the wardrobe") without re-uploading the image each time.
  • The Responses output array also carries reasoning items and tool calls when present. Always discriminate on item.type before reading content; TypeScript's narrowing keeps you honest.
  • For raw chat-style usage without the Responses wrapping, use xai.post.v1.chat.completions instead — same auth, same model catalog, just OpenAI-compatible request/response shapes.
  • Errors surface as XaiError with status and the parsed body attached, so try { ... } catch (e) { if (e instanceof XaiError) ... } gives you the upstream error directly.

Imagine Files API integration

xAI Imagine image and video endpoints can reference private Files API assets directly and can persist generated assets back to Files storage.

  • Inputs: anywhere Imagine accepts a public URL or base64 image/video, pass a stored file_id instead. Apicity accepts the raw REST shape (image: { file_id }, images: [{ file_id }], last_frame: { file_id }, video: { file_id }, reference_images: [{ file_id }]) plus convenience aliases (image_file_id, image_file_ids, last_frame_file_id, video_file_id, and reference_image_file_ids) that are normalized before the HTTP request.
  • Outputs: pass storage_options with a required filename to persist the generated image or video. Omit public_url or set it to false for a private file; set public_url: true or public_url: { expires_after: 86400 } to create a shareable URL.
  • Responses still include the default ephemeral imgen.x.ai or vidgen.x.ai generation URL. When storage is requested, the persistent Files metadata is returned as file_output on the generated image or completed video.

Reference-to-video, pinned frames and preset voices

xai.post.v1.videos.generations supports reference-to-video requests with up to 14 reference images on the grok-imagine-video-1.5 family (7 on grok-imagine-video), one to three preset voices, or both. Image references may use public HTTPS URLs, data URIs, or Files API IDs in any mixture. Preset voices use open, case-insensitive xAI voice identifiers; Apicity preserves each voice_id exactly and leaves voice recognition to xAI. Preset voices and pinned frames require the grok-imagine-video-1.5 family, including its -preview and dated aliases. Model-less requests with references, a last frame or keyframes default to the canonical grok-imagine-video-1.5 model.

const video = await xai.post.v1.videos.generations({
  prompt: "The person from <IMAGE_0> speaks the line with the voice from <AUDIO_0>",
  model: "grok-imagine-video-1.5",
  reference_images: [{ url: "https://example.com/character.png" }],
  reference_audios: [{ voice_id: "eve" }],
  duration: 15,
});

On the 1.5 family a request can also pin frames. image (or image_file_id) is the first frame, alone or beside references; last_frame (or last_frame_file_id) is the last frame; and keyframes pins up to four images inside the clip. Each keyframe's timestamp_s must be greater than 0 and less than duration (8 seconds when omitted). xAI snaps keyframes to a 1/3-second grid, so two keyframes that round to the same slot are rejected. prompt is optional whenever a frame is pinned; text-to-video and reference-only requests still need one. A pinned first frame takes <IMAGE_0>, and the reference images then start at <IMAGE_1>.

const timeLapse = await xai.post.v1.videos.generations({
  model: "grok-imagine-video-1.5",
  image: { url: "https://example.com/dawn.png" },
  keyframes: [
    { image: { url: "https://example.com/noon.png" }, timestamp_s: 3.5 },
    { image: { url: "https://example.com/dusk.png" }, timestamp_s: 6.5 },
  ],
  last_frame: { url: "https://example.com/night.png" },
  duration: 10,
  resolution: "720p",
});

The same callable accepts image-only and audio-only reference requests. Empty reference and keyframe arrays are inactive. A source video (video or video_file_id) excludes references and pinned frames on every model, and grok-imagine-video also refuses an image beside references. Prompt markers such as <IMAGE_0> and <AUDIO_0> are passed through unchanged.

Stored input field map

Use these fields when an image or video already lives in xAI Files storage. Apicity normalizes the convenience aliases into the REST file_id object shape before sending the request.

Apicity callStored input fieldSent to xAI
xai.post.v1.images.editsimage_file_idimage: { file_id }
xai.post.v1.images.editsimage_file_idsimages: [{ file_id }]
xai.post.v1.videos.generationsimage_file_idimage: { file_id }
xai.post.v1.videos.generationslast_frame_file_idlast_frame: { file_id }
xai.post.v1.videos.generationsreference_image_file_idsreference_images: [{ file_id }]
xai.post.v1.videos.generations.imageToVideoimage_file_idimage: { file_id }
xai.post.v1.videos.editsvideo_file_idvideo: { file_id }
xai.post.v1.videos.extensionsvideo_file_idvideo: { file_id }

You can also pass the raw REST fields directly. images and reference_images entries can mix { file_id } and { url } items in the same request, which is useful when only some references are already private Files assets. Stored images must be PNG, JPEG, or WebP; stored videos must be MP4; and the file upload must be complete before it is referenced by an Imagine endpoint.

const gen = await xai.post.v1.images.generations({
  prompt: "A futuristic city skyline at night",
  model: "grok-imagine-image-2.0",
  quality: "low",
  storage_options: { filename: "city.jpg" },
});
const city = gen.data[0].file_output!.file_id!;

const edit = await xai.post.v1.images.edits({
  prompt: "Add neon signs to the buildings",
  model: "grok-imagine-image-2.0",
  quality: "low",
  image_file_id: city,
  storage_options: { filename: "city-neon.jpg" },
});
const neonCity = edit.data[0].file_output!.file_id!;

const video = await xai.post.v1.videos.generations({
  prompt: "A camera pulls back through the city",
  model: "grok-imagine-video",
  duration: 5,
  image_file_id: neonCity,
  storage_options: {
    filename: "city-loop.mp4",
    public_url: true,
  },
});

const done = await xai.get.v1.videos(video.request_id);
console.log(done.video?.url);
console.log(done.video?.file_output?.public_url);

See xAI's Imagine Files API integration, Referencing Files as Input, Persisting Generated Output, Managing Files, and Files Public URLs docs for uploads, expiration, and public URL lifecycle details.

Files Public URLs

Files uploaded to xAI storage are private by default. Use xai.post.v1.files.publicUrl(fileId) to create a shareable xAI CDN URL for an existing file, then revoke that URL independently with xai.post.v1.files.publicUrl.revoke(fileId) when sharing should stop. Revoking the public URL leaves the private file intact.

const file = await xai.post.v1.files(
  new Blob(["diagram"], { type: "image/png" }),
  "diagram.png",
  "assistants"
);

const created = await xai.post.v1.files.publicUrl(file.id, {
  expires_after: 86400,
});
console.log(created.public_url);
console.log(created.expires_at);

const withPublicUrls = await xai.get.v1.files({
  filter: "public_url != null",
});
console.log(withPublicUrls.data[0]?.public_url);

await xai.post.v1.files.publicUrl.revoke(file.id);

Public URL lifecycle

  • Empty create bodies use xAI defaults. Pass expires_after in seconds to auto-revoke the URL after 1 hour to 30 days.
  • A public URL cannot outlive its file. If the file has its own expires_at, an omitted public URL expiry inherits the file expiry; an explicit expires_after must fit inside the file's remaining lifetime.
  • Create is idempotent while a file already has an active public URL: repeated calls return the same URL token and can update its expiry.
  • get.v1.files(fileId) and get.v1.files({ filter }) preserve public_url and public_url_expires_at metadata so callers can audit which files are currently public.

See xAI's Files Public URLs, Managing Files, and Imagine Files API integration docs for supported content types, size limits, and the storage_options.public_url generation path.

Realtime server transport

@apicity/xai/realtime exports connectXaiRealtime, a WebSocket transport for server-side voice sessions built on ws. It sends your API key as an Authorization: Bearer header, so keep it on the server; browsers should connect with an ephemeral secret from xai.post.v1.realtime.clientSecrets instead.

import { connectXaiRealtime } from "@apicity/xai/realtime";

const connection = await connectXaiRealtime({
  apiKey: process.env.XAI_API_KEY!,
  model: "grok-voice-latest",
  onEvent: (event) => console.log(event.type),
});

connection.send({
  type: "session.update",
  session: { voice: "eve", audio: { output: { speed: 1.1 } } },
});

// Speak a scripted line without model inference. The force_message
// item is the whole turn, so do not follow it with response.create.
connection.send({
  type: "conversation.item.create",
  item: {
    type: "force_message",
    role: "assistant",
    interruptible: false,
    content: [{ type: "output_text", text: "This call is being recorded." }],
  },
});

connection.close();
await connection.closed;
  • Pass callId instead of model to attach to an inbound SIP call.
  • audio.output.speed is a playback multiplier from 0.7 to 1.5 (default 1.0).
  • interruptible: false drops caller audio until the force message finishes playing (default true).
  • timeout bounds the handshake (default 15 s) and signal aborts it or an open connection. A rejected handshake rejects with XaiError carrying the HTTP status and parsed body.
  • A server event that is not a JSON text frame with a type closes the connection and reports Invalid Realtime event through onError.

See xAI's Speech to Speech docs for session parameters and events.

API Reference

55 endpoints across 18 groups. Each method mirrors an upstream URL path.

apiKey

GET xai.v1.apiKey

GET https://api.x.ai/v1/api-key

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.apiKey();

Source: packages/provider/xai/src/xai.ts

batches

GET xai.v1.batches

GET https://api.x.ai/v1/batches/{paramsOrIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.batches({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.v1.batches.requests

GET https://api.x.ai/v1/batches/{batchId}/requests{query}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.batches.requests({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.v1.batches.results

GET https://api.x.ai/v1/batches/{batchId}/results{query}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.batches.results({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.batches

POST https://api.x.ai/v1/batches

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.batches({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.batches.cancel

POST https://api.x.ai/v1/batches/{batchId}:cancel

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.batches.cancel({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.batches.requests

POST https://api.x.ai/v1/batches/{batchId}/requests

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.batches.requests({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

chat

GET xai.v1.chat.deferredCompletion

GET https://api.x.ai/v1/chat/deferred-completion/{requestId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.chat.deferredCompletion({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.chat.completions

POST https://api.x.ai/v1/chat/completions

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.chat.completions({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

customVoices

DELETE xai.v1.customVoices

DELETE https://api.x.ai/v1/custom-voices/{voiceId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.delete.v1.customVoices({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.v1.customVoices

GET https://api.x.ai/v1/custom-voices/{paramsOrVoiceIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.customVoices({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.v1.customVoices.audio

GET https://api.x.ai/v1/custom-voices/{voiceId}/audio

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.customVoices.audio({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

PATCH xai.v1.customVoices

PATCH https://api.x.ai/v1/custom-voices/{voiceId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.patch.v1.customVoices({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.customVoices

POST https://api.x.ai/v1/custom-voices

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.customVoices({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

documents

POST xai.v1.documents.search

POST https://api.x.ai/v1/documents/search

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.documents.search({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

files

DELETE xai.v1.files

DELETE https://api.x.ai/v1/files/{fileId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.delete.v1.files({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.v1.files

GET https://api.x.ai/v1/files/{paramsOrFileIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.files({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.v1.files.content

GET https://api.x.ai/v1/files/{fileId}/content

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.files.content({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.files

POST https://api.x.ai/v1/files

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.files({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.files.publicUrl

POST https://api.x.ai/v1/files/{fileId}/public-url

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.files.publicUrl({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.files.publicUrl.revoke

POST https://api.x.ai/v1/files/{fileId}/public-url/revoke

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.files.publicUrl.revoke({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

imageGenerationModels

GET xai.v1.imageGenerationModels

GET https://api.x.ai/v1/image-generation-models/{modelIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.imageGenerationModels({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

images

GET xai.v1.images

GET https://api.x.ai/v1/images/{requestId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.images({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.images.edits

POST https://api.x.ai/v1/images/edits

Cost tier: prohibitive

Upstream docs ↗

const res = await xai.post.v1.images.edits({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.images.generations

POST https://api.x.ai/v1/images/generations

Cost tier: prohibitive

Upstream docs ↗

const res = await xai.post.v1.images.generations({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

languageModels

GET xai.v1.languageModels

GET https://api.x.ai/v1/language-models/{modelIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.languageModels({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

managementApi

DELETE xai.managementApi.v1.collections

DELETE https://management-api.x.ai/v1/collections/{collectionId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.delete.managementApi.v1.collections({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

DELETE xai.managementApi.v1.collections.documents

DELETE https://management-api.x.ai/v1/collections/{collectionId}/documents/{fileId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.delete.managementApi.v1.collections.documents({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.managementApi.auth.teams.apiKeys

GET https://management-api.x.ai/auth/teams/{teamId}/api-keys{query}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.managementApi.auth.teams.apiKeys({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.managementApi.v1.billing.teams.postpaid.invoice.preview

GET https://management-api.x.ai/v1/billing/teams/{teamId}/postpaid/invoice/preview

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.managementApi.v1.billing.teams.postpaid.invoice.preview({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.managementApi.v1.billing.teams.postpaid.spendingLimits

GET https://management-api.x.ai/v1/billing/teams/{teamId}/postpaid/spending-limits

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.managementApi.v1.billing.teams.postpaid.spendingLimits({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.managementApi.v1.billing.teams.prepaid.balance

GET https://management-api.x.ai/v1/billing/teams/{teamId}/prepaid/balance

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.managementApi.v1.billing.teams.prepaid.balance({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.managementApi.v1.collections

GET https://management-api.x.ai/v1/collections/{paramsOrIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.managementApi.v1.collections({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.managementApi.v1.collections.documents

GET https://management-api.x.ai/v1/collections/{collectionId}/documents/{paramsOrFileId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.managementApi.v1.collections.documents({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.managementApi.v1.collections.documents.batchGet

GET https://management-api.x.ai/v1/collections/{collectionId}/documents:batchGet{query}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.managementApi.v1.collections.documents.batchGet({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

PATCH xai.managementApi.v1.collections.documents

PATCH https://management-api.x.ai/v1/collections/{collectionId}/documents/{fileId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.patch.managementApi.v1.collections.documents({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.managementApi.v1.billing.teams.usage

POST https://management-api.x.ai/v1/billing/teams/{teamId}/usage

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.managementApi.v1.billing.teams.usage({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.managementApi.v1.collections

POST https://management-api.x.ai/v1/collections

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.managementApi.v1.collections({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.managementApi.v1.collections.documents

POST https://management-api.x.ai/v1/collections/{collectionId}/documents/{fileId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.managementApi.v1.collections.documents({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

PUT xai.managementApi.v1.collections

PUT https://management-api.x.ai/v1/collections/{collectionId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.put.managementApi.v1.collections({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

models

GET xai.v1.models

GET https://api.x.ai/v1/models/{modelIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.models({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

realtime

POST xai.v1.realtime.clientSecrets

POST https://api.x.ai/v1/realtime/client_secrets

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.realtime.clientSecrets({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

responses

DELETE xai.v1.responses

DELETE https://api.x.ai/v1/responses/{id}

Cost tier: expensive

Upstream docs ↗

const res = await xai.delete.v1.responses({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

GET xai.v1.responses

GET https://api.x.ai/v1/responses/{id}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.responses({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.responses

POST https://api.x.ai/v1/responses

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.responses({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.responses.compact

POST https://api.x.ai/v1/responses/compact

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.responses.compact({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

stt

POST xai.v1.stt

POST https://api.x.ai/v1/stt

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.stt({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

tokenizeText

POST xai.v1.tokenizeText

POST https://api.x.ai/v1/tokenize-text

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.tokenizeText({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

tts

POST xai.v1.tts

POST https://api.x.ai/v1/tts

Cost tier: expensive

Upstream docs ↗

const res = await xai.post.v1.tts({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

videoGenerationModels

GET xai.v1.videoGenerationModels

GET https://api.x.ai/v1/video-generation-models/{modelIdOrSignal}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.videoGenerationModels({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

videos

GET xai.v1.videos

GET https://api.x.ai/v1/videos/{requestId}

Cost tier: expensive

Upstream docs ↗

const res = await xai.get.v1.videos({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.videos.edits

POST https://api.x.ai/v1/videos/edits

Cost tier: prohibitive

Upstream docs ↗

const res = await xai.post.v1.videos.edits({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.videos.extensions

POST https://api.x.ai/v1/videos/extensions

Cost tier: prohibitive

Upstream docs ↗

const res = await xai.post.v1.videos.extensions({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.videos.generations

POST https://api.x.ai/v1/videos/generations

Cost tier: prohibitive

Upstream docs ↗

const res = await xai.post.v1.videos.generations({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

POST xai.v1.videos.generations.imageToVideo

POST https://api.x.ai/v1/videos/generations

Cost tier: prohibitive

Upstream docs ↗

const res = await xai.post.v1.videos.generations.imageToVideo({ /* ... */ });

Source: packages/provider/xai/src/xai.ts

Middleware

import { createXai, withRetry } from "@apicity/xai";

const xai = createXai({ apiKey: process.env.XAI_API_KEY! });
const models = withRetry(xai.get.v1.models, { retries: 3 });

Rate Limiting

Client-side rate limiting that queues requests to stay within xAI API limits.

import {
  createXai,
  withRateLimit,
  withRetry,
  createRateLimiter,
  XAI_RATE_LIMITS,
} from "@apicity/xai";

const xai = createXai({ apiKey: process.env.XAI_API_KEY! });

Using xAI tier presets

// Use built-in tier presets (free, tier1, tier2, tier3, tier4)
const limiter = createRateLimiter(XAI_RATE_LIMITS.tier1);
// => { rpm: 60, concurrent: 10 }

const chat = withRateLimit(xai.post.v1.chat.completions, limiter);

Custom limits

const limiter = createRateLimiter({ rpm: 30, concurrent: 5 });
const chat = withRateLimit(xai.post.v1.chat.completions, limiter);

Shared limiter across endpoints

RPM limits apply globally, so share a single limiter across all endpoints:

const limiter = createRateLimiter(XAI_RATE_LIMITS.tier2);

const chat = withRateLimit(xai.post.v1.chat.completions, limiter);
const responses = withRateLimit(xai.post.v1.responses, limiter);

Composing with retry

Place withRateLimit innermost so retries count against the limit:

const limiter = createRateLimiter(XAI_RATE_LIMITS.tier1);

const chat = withRetry(
  withRateLimit(xai.post.v1.chat.completions, limiter),
  { retries: 2 }
);

Batch processing

Fire requests in parallel — the limiter handles pacing automatically:

const limiter = createRateLimiter(XAI_RATE_LIMITS.tier1);
const chat = withRateLimit(xai.post.v1.chat.completions, limiter);
const prompts = ["Name a prime number.", "Name a noble gas."];

const results = await Promise.all(
  prompts.map((p) =>
    chat({
      model: "grok-3",
      messages: [{ role: "user", content: p }],
    })
  )
);

xAI rate limit tiers

PresetRPMConcurrentSpend threshold
free52$0
tier16010$0+
tier220025$100+
tier350050$500+
tier41000100$1,000+

Part of the apicity monorepo.

License

MIT — see LICENSE.

版本与发布记录

@apicity/xai 共发布 89 个版本,首次发布于 2026/05/11;过去 12 个月发布了 89 个版本。

版本发布日期距今
0.26.0latest2026/11/051 天前
0.25.02026/11/033 天前
0.24.12026/11/024 天前
0.24.02026/11/014 天前
0.23.12026/11/015 天前
0.23.02026/11/015 天前

关于 @apicity/xai 的常见问题

@apicity/xai 是什么?

X.AI / Grok provider for chat and search.

如何安装 @apicity/xai?

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

@apicity/xai 支持 TypeScript 吗?

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

@apicity/xai 支持 ESM 还是 CommonJS?

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

@apicity/xai 需要什么版本的 Node.js?

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

@apicity/xai 可以商用吗?

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

@apicity/xai 的下载量有多少?

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

@apicity/xai 还在维护吗?

最新版本 0.26.0 发布于 2026/11/05(1 天前);至今共发布 89 个版本,其中过去 12 个月发布了 89 个;首次发布于 2026/05/11。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。