@aishorty/sdk

中文文档
0.1.0发布于 2026/07/29MITTypeScriptESM + CJS零依赖近 30 天下载 1.6万

Official TypeScript SDK for the Shorty public API (aishorty.com/v1).

原始使用文档
文档目录12 节
  1. Install
  2. Quickstart
  3. Authentication
  4. Client options
  5. Resources
  6. Pagination
  7. Jobs & waitFor
  8. Errors
  9. Error code reference
  10. Retries & idempotency
  11. Webhooks
  12. License

@aishorty/sdk

The official TypeScript SDK for the Shorty public API (https://aishorty.com/v1). Transcribe media, summarize videos and web pages, generate subtitles, and poll async jobs — with typed errors, automatic retries, cursor pagination, and Standard-Webhooks signature verification.

  • Zero runtime dependencies. Native fetch, node:crypto, AbortSignal.
  • Node.js >= 20. ESM and CommonJS builds, full type declarations.
  • Server-side only — your API key is a secret. The client throws if it detects a browser environment.

Install

npm install @aishorty/sdk
# or: pnpm add @aishorty/sdk  /  yarn add @aishorty/sdk

Quickstart

import { Shorty } from '@aishorty/sdk'

const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY })

// Start a summary job for a YouTube video…
const job = await shorty.summaries.create({
    source: 'youtube',
    url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
})

// …then wait for it to finish.
const finished = await shorty.jobs.waitFor(job.job_id)
console.log(finished.status, finished.output)

Authentication

Shorty accepts exactly one auth form: a Bearer API key (shk_live_…), sent on every request as Authorization: Bearer <key>. Create keys in your developer console.

// Explicit:
const shorty = new Shorty({ apiKey: 'shk_live_…' })

// Or omit it and the SDK reads process.env.SHORTY_API_KEY:
const shorty = new Shorty()

The key is held in a closure — it never appears in JSON.stringify(client), util.inspect(client), thrown errors, or debug logs.

Client options

OptionDefaultDescription
apiKeyprocess.env.SHORTY_API_KEYYour shk_live_… key. Throws if unset.
baseUrlhttps://aishorty.comAPI origin (paths already include /v1).
timeoutMs60000Per-attempt timeout.
maxRetries2Retries after the first attempt.
fetchglobalThis.fetchOverride the fetch implementation.
debugfalsetrue logs to stderr; a function receives redacted log lines.
dangerouslyAllowBrowserfalseAllow construction where window exists (not recommended).

Resources

await shorty.usage.get()

const page = await shorty.articles.list({ limit: 20 })
await shorty.articles.search({ q: 'transformers', article_type: 'YOUTUBE_ARTICLE' })
await shorty.articles.get(articleId)

await shorty.transcriptions.list()
await shorty.transcriptions.get(id)
await shorty.transcriptions.create({ url: 'https://example.com/audio.mp3' })

await shorty.summaries.create({ source: 'url', url: 'https://example.com/post' })
await shorty.summaries.create({ source: 'text', content: 'Long text to summarize…' })

await shorty.subtitles.create({ url: 'https://example.com/clip.mp4', style: 'TIKTOK' })
await shorty.subtitles.download(jobId, { kind: 'srt' })

await shorty.jobs.get(jobId)
await shorty.jobs.waitFor(jobId)

Every method takes a trailing opts? with { signal, timeoutMs, headers } (and idempotencyKey on the create methods).

Pagination

list() returns a Page. Iterate it with for await to walk every item across all pages (the SDK follows the opaque cursor for you, reusing your original filters), or page manually.

// Auto-iterate all items:
for await (const article of await shorty.articles.list({ limit: 50 })) {
    console.log(article.id, article.title)
}

// Manual page control:
let page = await shorty.articles.list({ limit: 50 })
while (page) {
    console.log(page.data.length, 'items; hasMore =', page.hasMore)
    page = (await page.nextPage()) ?? null
    if (!page) break
}

Cursors are filter-bound: a cursor minted under one query cannot be replayed against a different filter. The SDK never mutates your params while paging, so this is handled automatically — just don't hand a cursor to a different list() call.

Jobs & waitFor

Writes (transcriptions.create, summaries.create, subtitles.create) return a 202 { job_id, status, tracking_url }. Poll with jobs.get(id), or use the convenience poller:

try {
    const done = await shorty.jobs.waitFor(job.job_id, {
        pollIntervalMs: 2000, // default
        timeoutMs: 600_000, // default
        // signal: abortController.signal,
    })
    console.log('done:', done.output)
} catch (err) {
    if (err instanceof JobFailedError) {
        // The job ended in ERROR/CANCELLED — err.jobError has the reason.
    }
    if (err instanceof APITimeoutError) {
        // The deadline elapsed before the job finished.
    }
}

waitFor is a client-side loop — not a server feature. It resolves on SUCCESS, throws JobFailedError on ERROR/CANCELLED, and APITimeoutError on the deadline.

Errors

Every non-2xx response throws a typed error. The class is chosen by the problem code first, falling back to the HTTP status class for any unknown/absent code (so a new server code never breaks your instanceof handling).

import {
    APIError,
    AuthenticationError,
    PermissionDeniedError,
    NotFoundError,
    ConflictError,
    ValidationError,
    RateLimitError,
    QuotaExhaustedError,
    APIServerError,
    APIConnectionError,
    APITimeoutError,
} from '@aishorty/sdk'

try {
    await shorty.usage.get()
} catch (err) {
    if (err instanceof QuotaExhaustedError) {
        // Period allowance spent — back off until the next window.
    } else if (err instanceof RateLimitError) {
        console.log('retry after', err.retryAfterSeconds, 's')
    } else if (err instanceof APIError) {
        console.log(err.status, err.code, err.title, err.detail, err.requestId)
        console.log(err.errors) // field-level validation errors, when present
    }
}

Every APIError carries status, code, problemType, title, detail, requestId, errors?, headers, and (on 429s) retryAfterSeconds. The .message is title: detail (request_id) and never contains your API key.

Error code reference

CodeStatusSDK error class
unauthorized401AuthenticationError
invalid_api_key401AuthenticationError
insufficient_scope403PermissionDeniedError
feature_not_enabled403PermissionDeniedError
resource_not_found404NotFoundError
idempotency_conflict409ConflictError
idempotency_in_progress409ConflictError
resource_not_ready409ConflictError
validation_failed400ValidationError
idempotency_key_reused422ValidationError
request_too_large413ValidationError
rate_limited429RateLimitError
quota_exhausted429QuotaExhaustedError
internal_error500APIServerError
service_unavailable503APIServerError

Transport-level failures throw APIConnectionError (with cause) or APITimeoutError.

Retries & idempotency

The SDK automatically retries transient failures up to maxRetries times (default 2) after the first attempt.

Retried: connection errors · per-attempt timeouts · 408 · 429 rate_limited (or a 429 with no parseable code) · 500 / 502 / 503 / 504.

Never retried: 429 quota_exhausted (a spent period allowance — retrying won't help) · any other 4xx · requests you aborted via your own signal.

Backoff: a Retry-After header is honored when present (both delta-seconds and HTTP-date forms, capped at 60s); otherwise full-jitter exponential backoff, random(0, min(0.5 · 2^attempt, 8)) seconds.

POST safety: an unsafe POST is retried only when it carries an Idempotency-Key. The three create methods auto-generate one (a UUID) and reuse the same key across every retry attempt, so retries are deduplicated server-side. Supply your own via opts.idempotencyKey to make a call idempotent across process restarts:

await shorty.summaries.create(
    { source: 'text', content: '…' },
    { idempotencyKey: 'my-stable-key-123' },
)

client.request(...) POSTs are not retried unless you pass an idempotencyKey yourself.

Webhooks

Verify inbound Shorty webhooks with verifyWebhookSignature. It implements the Standard Webhooks v1 scheme (HMAC-SHA256, webhook-id / webhook-timestamp / webhook-signature headers, rotation via multiple space-separated signatures, a 5-minute replay tolerance, constant-time comparison).

import { verifyWebhookSignature } from '@aishorty/sdk'

// Express example — note express.raw(), NOT express.json():
app.post('/webhooks/shorty', express.raw({ type: '*/*' }), (req, res) => {
    const result = verifyWebhookSignature({
        payload: req.body, // the RAW body (Buffer/Uint8Array or string)
        headers: req.headers, // Headers object or lowercase-keyed record
        secret: process.env.SHORTY_WEBHOOK_SECRET!, // whsec_…
    })
    if (!result.valid) {
        return res.status(400).json({ error: result.reason })
    }
    // Safe to handle. Parse the body only AFTER verifying.
    const event = JSON.parse(Buffer.from(req.body).toString('utf8'))
    res.sendStatus(200)
})

Raw body only. You must verify the exact bytes you received. Do not JSON.parse and re-JSON.stringify the body before verifying — re-serialization reorders keys and changes whitespace, which will fail verification. In frameworks that auto-parse JSON, capture the raw body first (e.g. express.raw(), or await request.text() in a Next.js route).

verifyWebhookSignature returns a discriminated result:

type WebhookVerifyResult =
    | { valid: true }
    | {
          valid: false
          reason:
              | 'missing_headers'
              | 'malformed_timestamp'
              | 'timestamp_out_of_tolerance'
              | 'no_matching_signature'
      }

License

MIT © Devino Solutions

版本与发布记录

@aishorty/sdk 共发布 1 个版本,首次发布于 2026/07/29;过去 12 个月发布了 1 个版本。

版本发布日期距今
0.1.0latest2026/07/293 个月前

关于 @aishorty/sdk 的常见问题

@aishorty/sdk 是什么?

Official TypeScript SDK for the Shorty public API (aishorty.com/v1).

如何安装 @aishorty/sdk?

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

@aishorty/sdk 支持 TypeScript 吗?

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

@aishorty/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@aishorty/sdk 需要什么版本的 Node.js?

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

@aishorty/sdk 可以商用吗?

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

@aishorty/sdk 的下载量有多少?

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

@aishorty/sdk 还在维护吗?

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