# Shorty API — full developer documentation > The complete documentation of the Shorty developer platform (API v1.0.0) in one file. Each section below is also served as an individual page at the URL in its header, and every /docs page has a raw Markdown twin at the same URL with a .md suffix. Base URL: https://aishorty.com/v1 · OpenAPI spec: https://aishorty.com/api/openapi · MCP endpoint: https://aishorty.com/api/mcp ## Operations index | Method | Path | Operation | | --- | --- | --- | | GET | `/v1/usage` | [Get plan, limits, and usage](https://aishorty.com/docs/api/operations/getUsage) | | GET | `/v1/articles` | [List your articles](https://aishorty.com/docs/api/operations/listArticles) | | GET | `/v1/articles/search` | [Search your articles](https://aishorty.com/docs/api/operations/searchArticles) | | GET | `/v1/articles/{articleId}` | [Get an article](https://aishorty.com/docs/api/operations/getArticle) | | GET | `/v1/transcriptions` | [List your transcriptions](https://aishorty.com/docs/api/operations/listTranscriptions) | | POST | `/v1/transcriptions` | [Create a transcription](https://aishorty.com/docs/api/operations/createTranscription) | | GET | `/v1/transcriptions/{transcriptionId}` | [Get a transcription](https://aishorty.com/docs/api/operations/getTranscription) | | GET | `/v1/jobs/{jobId}` | [Get job status](https://aishorty.com/docs/api/operations/getJob) | | GET | `/v1/subtitles/{jobId}/download` | [Download a subtitle artifact](https://aishorty.com/docs/api/operations/downloadSubtitles) | | POST | `/v1/summaries` | [Create a summary](https://aishorty.com/docs/api/operations/createSummary) | | POST | `/v1/subtitles` | [Create subtitles](https://aishorty.com/docs/api/operations/createSubtitles) | --- URL: https://aishorty.com/docs/api # Shorty API — REST for summaries, transcription & subtitles > The Shorty developer API turns YouTube videos, web pages, and media into summaries, transcriptions, and subtitles over a versioned REST surface — the same engine behind the Shorty app, with API keys, idempotency, rate limits, webhooks, and a TypeScript SDK. ## What it does Shorty summarizes YouTube videos, web pages, and pasted text; transcribes audio and video; and generates subtitles. The API exposes those capabilities as async jobs you start with one call and poll (or receive a webhook) when they finish. Every resource is scoped to the authenticated account. ## Base URL All endpoints live under one versioned base. Paths in this documentation are relative to it: ```text https://aishorty.com/v1 ``` ## Authentication Send a `shk_live_` API key (create one in Settings → API keys) or an OAuth 2.1 access token as a Bearer token on every request. Keys are shown once at creation — store them server-side; never ship a key to a browser. ```http Authorization: Bearer shk_live_your_key ``` ## Quickstart Reach your first `200` in under five minutes: 1. Create an API key in Settings → API keys and copy it (shown once). 2. Confirm the key works by reading your usage. 3. Start a summary job — it returns `202` with a `job_id`. 4. Poll the job until it finishes (or subscribe to a webhook). **1. Check your usage (verifies the key)** curl: ```bash curl https://aishorty.com/v1/usage \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const usage = await shorty.usage.get() console.log(usage.plan, usage.limits) ``` **2. Start a summary job** curl: ```bash curl -X POST https://aishorty.com/v1/summaries \ -H "Authorization: Bearer shk_live_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "youtube", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }' ``` TypeScript (@aishorty/sdk): ```typescript const job = await shorty.summaries.create({ source: 'youtube', url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', }) ``` **3. Poll the job until it is done** curl: ```bash curl https://aishorty.com/v1/jobs/JOB_ID \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript const done = await shorty.jobs.waitFor(job.job_id) console.log(done.status, done.output) ``` ## Conventions - **Async jobs.** Write endpoints return `202 Accepted` with a `job_id` and a `tracking_url`; poll `GET /v1/jobs/{id}` or receive a webhook. - **Idempotency.** Send an `Idempotency-Key` header on POSTs; a retry with the same key and body replays the original response for 24 hours. - **Rate limits.** 60 requests/minute on the free tier, 300/minute on paid. The bucket is per API key when you authenticate with a `shk_live_` key, and per user when you call as an OAuth/MCP client. Responses carry IETF `RateLimit` headers. - **Pagination.** List endpoints return `{ data, has_more, next_cursor }`; pass `next_cursor` back as `?cursor=`. - **Errors.** Every error is an RFC 9457 `application/problem+json` body with a stable `code` you can switch on. ## Explore - [API reference](/api-docs) — Interactive reference for every endpoint (Scalar). - [Operations](/docs/api/operations) — One page per endpoint, with examples. - [OpenAPI spec](/api/openapi) — The machine-readable OpenAPI 3.1 document. - [TypeScript SDK](https://github.com/DevinoSolutions/shorty-sdk) — @aishorty/sdk — the official Node client. - [Webhooks](/docs/api/webhooks) — Receive events instead of polling. - [Errors](/docs/api/errors) — Every error code and how to fix it. - [Changelog](/changelog) — Dated API changes and additions. - [For AI agents](/docs/ai) — MCP, llms.txt, and Markdown twins for agents. - [Developers overview](/developers) — The platform at a glance — REST, SDK, and MCP. --- URL: https://aishorty.com/docs/api/errors # API error reference — Shorty API > Every error the Shorty API can return, as RFC 9457 problem responses with a stable machine-readable code. Switch on the code, not the HTTP status. Every error is an RFC 9457 `application/problem+json` body carrying a stable `code`. Switch on `code`, never on the human `title` or (alone) the HTTP status. Each code links to a page explaining when it happens and how to fix it. | Code | HTTP | Meaning | | --- | --- | --- | | [`unauthorized`](/docs/api/errors/unauthorized) | 401 | Unauthorized | | [`invalid_api_key`](/docs/api/errors/invalid_api_key) | 401 | Invalid API key | | [`insufficient_scope`](/docs/api/errors/insufficient_scope) | 403 | Insufficient scope | | [`feature_not_enabled`](/docs/api/errors/feature_not_enabled) | 403 | Feature not enabled | | [`rate_limited`](/docs/api/errors/rate_limited) | 429 | Rate limit exceeded | | [`quota_exhausted`](/docs/api/errors/quota_exhausted) | 429 | Quota exhausted | | [`idempotency_conflict`](/docs/api/errors/idempotency_conflict) | 409 | Idempotency conflict | | [`idempotency_key_reused`](/docs/api/errors/idempotency_key_reused) | 422 | Idempotency key reused | | [`idempotency_in_progress`](/docs/api/errors/idempotency_in_progress) | 409 | Idempotency request in progress | | [`resource_not_found`](/docs/api/errors/resource_not_found) | 404 | Resource not found | | [`resource_not_ready`](/docs/api/errors/resource_not_ready) | 409 | Resource not ready | | [`validation_failed`](/docs/api/errors/validation_failed) | 400 | Validation failed | | [`request_too_large`](/docs/api/errors/request_too_large) | 413 | Request too large | | [`service_unavailable`](/docs/api/errors/service_unavailable) | 503 | Service unavailable | | [`internal_error`](/docs/api/errors/internal_error) | 500 | Internal server error | --- URL: https://aishorty.com/docs/api/operations # API operations — Shorty API > Every endpoint of the Shorty developer API, grouped by resource, with request and response schemas and curl + TypeScript examples. Every Shorty API endpoint, grouped by resource. Each page shows the method, path, parameters, request and response schemas, error codes, and curl + SDK examples. ## Usage - [GET /v1/usage](/docs/api/operations/getUsage) — Get plan, limits, and usage ## Articles - [GET /v1/articles](/docs/api/operations/listArticles) — List your articles - [GET /v1/articles/search](/docs/api/operations/searchArticles) — Search your articles - [GET /v1/articles/{articleId}](/docs/api/operations/getArticle) — Get an article ## Transcriptions - [GET /v1/transcriptions](/docs/api/operations/listTranscriptions) — List your transcriptions - [POST /v1/transcriptions](/docs/api/operations/createTranscription) — Create a transcription - [GET /v1/transcriptions/{transcriptionId}](/docs/api/operations/getTranscription) — Get a transcription ## Jobs - [GET /v1/jobs/{jobId}](/docs/api/operations/getJob) — Get job status ## Subtitles - [GET /v1/subtitles/{jobId}/download](/docs/api/operations/downloadSubtitles) — Download a subtitle artifact - [POST /v1/subtitles](/docs/api/operations/createSubtitles) — Create subtitles ## Summaries - [POST /v1/summaries](/docs/api/operations/createSummary) — Create a summary --- URL: https://aishorty.com/docs/api/webhooks # Webhooks — Shorty API > Receive signed Standard Webhooks events when Shorty jobs finish, instead of polling. Event catalog, signature verification (SDK and raw Node crypto), retry schedule, failure ladder, and secret rotation. Instead of polling, register an HTTPS endpoint and Shorty POSTs a signed event to it when a job finishes. Deliveries follow the [Standard Webhooks](https://www.standardwebhooks.com) specification, so you can verify them with any Standard-Webhooks library or the `verifyWebhookSignature` helper exported by @aishorty/sdk. ## Event catalog Every event `data` is the SAME shape you get from [GET /v1/jobs/{id}](/docs/api/operations/getJob), so one parser works for both polling and webhooks. | Event type | When it fires | | --- | --- | | `job.succeeded` | A job finished successfully. The payload `data` is the same shape as GET /v1/jobs/{id}. | | `job.failed` | A job terminated with an error. The payload `data` is the same shape as GET /v1/jobs/{id}. | ### `job.succeeded` payload ```json { "type": "job.succeeded", "timestamp": "2026-07-20T12:00:00.000Z", "data": { "jobId": "j1b2c3d4-0000-4000-8000-000000000000", "status": "success", "progress": 100, "step": null, "error": null, "output": { "jobType": "YOUTUBE_ARTICLE", "articleId": "a1b2c3d4-…" } } } ``` ### `job.failed` payload ```json { "type": "job.failed", "timestamp": "2026-07-20T12:00:00.000Z", "data": { "jobId": "j1b2c3d4-0000-4000-8000-000000000000", "status": "error", "progress": 40, "step": null, "error": "Transcription failed: unreadable audio", "output": {} } } ``` ## Verifying signatures Each delivery carries three headers: `webhook-id`, `webhook-timestamp`, and `webhook-signature`. The signature is `v1,` + base64 HMAC-SHA256 over `${webhook-id}.${webhook-timestamp}.${rawBody}`, keyed by the base64-decoded bytes of your endpoint secret (the part after `whsec_`). > **Warning:** Raw-body rule: verify the EXACT bytes you received. Parsing then re-serializing the JSON (`JSON.stringify(JSON.parse(body))`) reorders keys and WILL fail verification. Capture the raw request body before any JSON parsing. ### With @aishorty/sdk ```typescript import { verifyWebhookSignature } from '@aishorty/sdk' // rawBody is the exact string received, before JSON.parse. const result = verifyWebhookSignature({ payload: rawBody, headers: req.headers, secret: process.env.SHORTY_WEBHOOK_SECRET, // whsec_... }) if (!result.valid) { return res.status(400).send(result.reason) } const event = JSON.parse(rawBody) ``` ### With Node crypto (no dependency) ```typescript import { createHmac, timingSafeEqual } from 'node:crypto' function verify(rawBody, headers, secret) { const id = headers['webhook-id'] const timestamp = headers['webhook-timestamp'] const signatureHeader = headers['webhook-signature'] const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') const signed = `${id}.${timestamp}.${rawBody}` const expected = createHmac('sha256', key).update(signed, 'utf8').digest('base64') // Header may hold several space-separated "v1," tokens (rotation). return signatureHeader.split(' ').some(token => { const [version, sig] = token.split(',') if (version !== 'v1' || !sig) return false const a = Buffer.from(sig, 'base64') const b = Buffer.from(expected, 'base64') return a.length === b.length && timingSafeEqual(a, b) }) } ``` Timestamps are rejected outside a ±5-minute tolerance (replay defense). Deliveries are sent with the `Shorty-Webhooks/1.0` user agent and a 10-second per-attempt timeout. ## Retries A non-2xx response (or a connection error) is retried with exponential backoff and jitter: 9 attempts total (the initial attempt plus 8 retries) spread over roughly 45 hours (30s → 2m → 10m → 30m → 2h → 6h → 12h → 24h). Return any 2xx to acknowledge; a delivery that never succeeds is marked exhausted. ## Failure ladder We email the endpoint owner as consecutive failures mount — at 5, 10, 15 failures in a row — and AUTO-DISABLE the endpoint at 20 consecutive failures. A single successful delivery resets the counter to zero. Re-enable a disabled endpoint from Settings. ## Secret rotation Rotating an endpoint's secret opens a 24-hour dual-signing window: deliveries are signed with BOTH the new and previous secret (space-separated tokens in `webhook-signature`), so you can roll the secret on your side without dropping events. Accept a delivery if ANY token verifies. ## Delivery security - Endpoints must be HTTPS. Requests to private, loopback, or link-local addresses are blocked (SSRF protection). - Only response snippets are stored for the delivery log — never full response bodies. - Manage endpoints, view the delivery log, send a test event, and rotate secrets in Settings. --- URL: https://aishorty.com/docs/ai # Shorty for AI agents — MCP, llms.txt & Markdown docs > How AI assistants and agents connect to Shorty: the remote MCP server, machine-readable llms.txt / llms-full.txt, Markdown twins of every docs page, the OpenAPI spec, and agent interaction guidelines. Shorty is built to be read and used by agents. This page routes you to the machine-readable surfaces. ## Connect over MCP Shorty runs a remote Model Context Protocol server behind OAuth 2.1. Add it to Claude Code with one command: ```bash claude mcp add --transport http shorty https://aishorty.com/api/mcp ``` The endpoint is `https://aishorty.com/api/mcp`. It exposes 12 scoped tools (each appears only when your grant includes its scope). See the [MCP server page](/mcp) for scopes and consent details. | Tool | Access | Description | | --- | --- | --- | | `search_articles` | Read | Search the current user's Shorty library by free-text query (title, description, URL). Returns matching article cards with id, title, url. | | `list_recent_articles` | Read | List the current user's most recently summarized articles. Use when the user asks about their recent summaries, library overview, or 'what have I saved lately'. | | `get_article` | Read | Fetch a single article (summary, highlights, body) so you can answer questions about it. Pass a real article UUID as articleId. | | `list_transcriptions` | Read | List the current user's audio/video transcriptions (newest first) with status and progress. Optional free-text query matches the transcript text. | | `get_transcription` | Read | Fetch a single transcription (status, language, full transcript text) by its UUID so you can answer questions about it. | | `get_usage_quota` | Read | Read the current user's Shorty plan (Free / Premium / Pro), tier limits (upload sizes, realtime minutes, conversion caps), and current cloud-conversion usage against the daily quota. | | `get_job_status` | Read | Check the status of a job you started (summary, transcription, or subtitles) by its jobId. Returns { found, status, jobType, outputId? }: status is QUEUED \| PROCESSING \| SUCCESS \| ERROR \| CANCELLED. Poll until terminal (SUCCESS/ERROR/CANCELLED). | | `search_docs` | Read | Search Shorty's developer documentation (API reference, error codes, webhooks, MCP guide) by free-text query. Returns matching pages with a snippet, the page URL, and a raw-Markdown twin URL you can fetch directly. | | `create_youtube_summary` | Write | Start an AI summary job for a YouTube URL. Returns { jobId, articleId? }: if articleId is present the summary already exists (navigate directly); otherwise the job started — poll it with get_job_status. Pass a stable idempotencyKey to make retries safe (a repeat returns the original job). | | `create_content_summary` | Write | Start an AI summary job for a webpage URL, pasted text, or an uploaded file (PDF/Word/Audio) already stored in Shorty. Supply exactly one of url/text/fileKey. sourceType selects the summarizer: 'webpage' \| 'article' \| 'pdf' \| 'word' \| 'text' \| 'audio'. Returns { jobId, articleId? } — poll get_job_status. Pass a stable idempotencyKey to make retries safe. | | `create_transcription` | Write | Transcribe a publicly accessible audio/video URL via the GPU service. Returns { jobId, trackingUrl } to follow, or an { error } if it did not start (relay that reason and suggest uploading the file instead). Poll progress with get_job_status. Pass a stable idempotencyKey to make retries safe. | | `create_subtitles` | Write | Generate subtitles for a public video/audio URL. Style is CLEAN \| TIKTOK \| PODCAST \| MINIMAL (only CLEAN on the free plan). Returns { jobId, trackingUrl } — poll get_job_status; user can download SRT/VTT (and, on Premium, a captioned MP4). If upgradeRequired is true, explain the limit and suggest upgrading. Pass a stable idempotencyKey to make retries safe. | ## Machine-readable surfaces - [llms.txt](/llms.txt) — Site + API index for LLMs. - [llms-full.txt](/llms-full.txt) — Full API docs + operation summary in one file. - [OpenAPI 3.1 spec](/api/openapi) — The machine-readable contract. - [API reference](/docs/api) — Human docs (each has a .md twin). ## Markdown twins Every documentation page under `/docs` serves a raw Markdown version at the same URL with a `.md` suffix — e.g. `/docs/api.md`, `/docs/api/operations/createSummary.md`. Fetch the `.md` variant for clean, token-efficient context. ## Agent guidelines - Authenticate every request with `Authorization: Bearer ` (a `shk_live_` key or OAuth token). - Respect rate limits: 60 req/min free, 300/min paid — counted per API key for `shk_live_` callers and per user for OAuth/MCP callers; read the `RateLimit` headers and back off on 429. - Usage quotas are shared across the app, MCP, and API — a 429 `quota_exhausted` means the plan allowance is spent, not a transient limit. - Send an `Idempotency-Key` on writes so retries never create duplicate jobs. - Writes are async: expect `202` + a `job_id`, then poll `GET /v1/jobs/{id}`. Never assume synchronous completion. - On any error, read the RFC 9457 `code` and branch on it — do not parse human message text. --- URL: https://aishorty.com/docs/api/errors/unauthorized # unauthorized — Shorty API error > Unauthorized (HTTP 401): when the Shorty API returns the unauthorized error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `unauthorized` | 401 | `https://aishorty.com/docs/api/errors/unauthorized` | ## When it happens The request carried no credentials — a missing or empty `Authorization` header. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/unauthorized", "title": "Unauthorized", "status": 401, "detail": "Missing Authorization header.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "unauthorized", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Send `Authorization: Bearer ` with a `shk_live_` API key (create one in Settings → API keys) or a valid OAuth 2.1 access token. ## Related errors - [invalid_api_key](/docs/api/errors/invalid_api_key) — Invalid API key (HTTP 401) - [insufficient_scope](/docs/api/errors/insufficient_scope) — Insufficient scope (HTTP 403) --- URL: https://aishorty.com/docs/api/errors/invalid_api_key # invalid_api_key — Shorty API error > Invalid API key (HTTP 401): when the Shorty API returns the invalid_api_key error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `invalid_api_key` | 401 | `https://aishorty.com/docs/api/errors/invalid_api_key` | ## When it happens An `Authorization` header was present but the key is unknown, malformed, disabled, or revoked. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/invalid_api_key", "title": "Invalid API key", "status": 401, "detail": "The provided API key is not valid.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "invalid_api_key", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Check the key was copied whole (it is shown only once at creation), that it has not been rolled or revoked, and that you are hitting the right host. ## Related errors - [unauthorized](/docs/api/errors/unauthorized) — Unauthorized (HTTP 401) - [insufficient_scope](/docs/api/errors/insufficient_scope) — Insufficient scope (HTTP 403) --- URL: https://aishorty.com/docs/api/errors/insufficient_scope # insufficient_scope — Shorty API error > Insufficient scope (HTTP 403): when the Shorty API returns the insufficient_scope error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `insufficient_scope` | 403 | `https://aishorty.com/docs/api/errors/insufficient_scope` | ## When it happens The credential is valid but lacks the scope this operation requires (e.g. calling a write endpoint with a read-only key). ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/insufficient_scope", "title": "Insufficient scope", "status": 403, "detail": "This operation requires the articles:write scope.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "insufficient_scope", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Grant the missing scope: re-create the API key with the needed scopes, or re-consent the connected app so its token carries them. ## Related errors - [unauthorized](/docs/api/errors/unauthorized) — Unauthorized (HTTP 401) - [feature_not_enabled](/docs/api/errors/feature_not_enabled) — Feature not enabled (HTTP 403) --- URL: https://aishorty.com/docs/api/errors/feature_not_enabled # feature_not_enabled — Shorty API error > Feature not enabled (HTTP 403): when the Shorty API returns the feature_not_enabled error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `feature_not_enabled` | 403 | `https://aishorty.com/docs/api/errors/feature_not_enabled` | ## When it happens Your account or plan does not have this capability turned on. Distinct from a scope problem — the key is allowed, the plan is not. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/feature_not_enabled", "title": "Feature not enabled", "status": 403, "detail": "This subtitle style requires Shorty Premium.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "feature_not_enabled", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Upgrade the plan that unlocks the feature, or contact support if you believe it should already be enabled. ## Related errors - [insufficient_scope](/docs/api/errors/insufficient_scope) — Insufficient scope (HTTP 403) - [quota_exhausted](/docs/api/errors/quota_exhausted) — Quota exhausted (HTTP 429) --- URL: https://aishorty.com/docs/api/errors/rate_limited # rate_limited — Shorty API error > Rate limit exceeded (HTTP 429): when the Shorty API returns the rate_limited error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `rate_limited` | 429 | `https://aishorty.com/docs/api/errors/rate_limited` | ## When it happens You exceeded the request burst limit (60 requests/minute on the free tier, 300/minute on paid), counted per API key for `shk_live_` callers and per user for OAuth/MCP callers. A short-term throughput cap, not a usage quota. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/rate_limited", "title": "Rate limit exceeded", "status": 429, "detail": "Rate limit exceeded. Retry after the window resets.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "rate_limited", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Back off until the window resets. Read the `RateLimit` / `Retry-After` response headers and pace requests; the SDK retries these automatically with backoff. ## Related errors - [quota_exhausted](/docs/api/errors/quota_exhausted) — Quota exhausted (HTTP 429) --- URL: https://aishorty.com/docs/api/errors/quota_exhausted # quota_exhausted — Shorty API error > Quota exhausted (HTTP 429): when the Shorty API returns the quota_exhausted error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `quota_exhausted` | 429 | `https://aishorty.com/docs/api/errors/quota_exhausted` | ## When it happens A plan usage quota (shared across the app, MCP, and API) is used up for its period — e.g. the free MONTHLY subtitle allowance, the free daily API summary quota, or the free monthly API transcription quota. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/quota_exhausted", "title": "Quota exhausted", "status": 429, "detail": "Your quota for this resource is exhausted.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "quota_exhausted", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Wait for the quota period to reset, or upgrade the plan for a higher allowance. Quota is product-level, so app and API usage count against the same pool. ## Related errors - [rate_limited](/docs/api/errors/rate_limited) — Rate limit exceeded (HTTP 429) - [feature_not_enabled](/docs/api/errors/feature_not_enabled) — Feature not enabled (HTTP 403) --- URL: https://aishorty.com/docs/api/errors/idempotency_conflict # idempotency_conflict — Shorty API error > Idempotency conflict (HTTP 409): when the Shorty API returns the idempotency_conflict error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `idempotency_conflict` | 409 | `https://aishorty.com/docs/api/errors/idempotency_conflict` | ## When it happens An `Idempotency-Key` was reused, but the stored request it maps to conflicts with this one in a way that is not a clean replay. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/idempotency_conflict", "title": "Idempotency conflict", "status": 409, "detail": "This idempotency key conflicts with an earlier request.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "idempotency_conflict", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Use a fresh `Idempotency-Key` for a genuinely new request; reuse a key only to safely retry the exact same call. ## Related errors - [idempotency_key_reused](/docs/api/errors/idempotency_key_reused) — Idempotency key reused (HTTP 422) - [idempotency_in_progress](/docs/api/errors/idempotency_in_progress) — Idempotency request in progress (HTTP 409) --- URL: https://aishorty.com/docs/api/errors/idempotency_key_reused # idempotency_key_reused — Shorty API error > Idempotency key reused (HTTP 422): when the Shorty API returns the idempotency_key_reused error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `idempotency_key_reused` | 422 | `https://aishorty.com/docs/api/errors/idempotency_key_reused` | ## When it happens An `Idempotency-Key` was reused with a DIFFERENT request body. Keys bind to the exact payload they first succeeded with. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/idempotency_key_reused", "title": "Idempotency key reused", "status": 422, "detail": "This idempotency key was already used with a different request body.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "idempotency_key_reused", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Generate a new key when the body changes. Reuse a key only for byte-identical retries of the same operation. ## Related errors - [idempotency_conflict](/docs/api/errors/idempotency_conflict) — Idempotency conflict (HTTP 409) - [idempotency_in_progress](/docs/api/errors/idempotency_in_progress) — Idempotency request in progress (HTTP 409) --- URL: https://aishorty.com/docs/api/errors/idempotency_in_progress # idempotency_in_progress — Shorty API error > Idempotency request in progress (HTTP 409): when the Shorty API returns the idempotency_in_progress error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `idempotency_in_progress` | 409 | `https://aishorty.com/docs/api/errors/idempotency_in_progress` | ## When it happens A request with the same `Idempotency-Key` is still being processed — two identical calls raced. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/idempotency_in_progress", "title": "Idempotency request in progress", "status": 409, "detail": "A request with this idempotency key is still in progress.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "idempotency_in_progress", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Wait and retry the same call; the original will complete and the retry will replay its result. The 24h idempotency ledger dedupes for you. ## Related errors - [idempotency_conflict](/docs/api/errors/idempotency_conflict) — Idempotency conflict (HTTP 409) - [idempotency_key_reused](/docs/api/errors/idempotency_key_reused) — Idempotency key reused (HTTP 422) --- URL: https://aishorty.com/docs/api/errors/resource_not_found # resource_not_found — Shorty API error > Resource not found (HTTP 404): when the Shorty API returns the resource_not_found error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `resource_not_found` | 404 | `https://aishorty.com/docs/api/errors/resource_not_found` | ## When it happens The referenced id does not exist, or belongs to another account. Resources are always scoped to the authenticated account. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/resource_not_found", "title": "Resource not found", "status": 404, "detail": "No article with that id exists for this account.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "resource_not_found", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Verify the id and that it was created by the same account. A foreign id is reported as not found, never as forbidden. ## Related errors - [resource_not_ready](/docs/api/errors/resource_not_ready) — Resource not ready (HTTP 409) --- URL: https://aishorty.com/docs/api/errors/resource_not_ready # resource_not_ready — Shorty API error > Resource not ready (HTTP 409): when the Shorty API returns the resource_not_ready error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `resource_not_ready` | 409 | `https://aishorty.com/docs/api/errors/resource_not_ready` | ## When it happens The resource exists but is not yet in a usable state — e.g. downloading a subtitle artifact before its job has finished. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/resource_not_ready", "title": "Resource not ready", "status": 409, "detail": "The subtitle artifact is not ready — the job is still running.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "resource_not_ready", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Poll `GET /v1/jobs/{id}` (or use the SDK `jobs.waitFor`) until the job reaches a terminal state, then retry. ## Related errors - [resource_not_found](/docs/api/errors/resource_not_found) — Resource not found (HTTP 404) --- URL: https://aishorty.com/docs/api/errors/validation_failed # validation_failed — Shorty API error > Validation failed (HTTP 400): when the Shorty API returns the validation_failed error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `validation_failed` | 400 | `https://aishorty.com/docs/api/errors/validation_failed` | ## When it happens The request body or parameters failed schema validation. The `errors` array lists each offending field with a JSON pointer. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/validation_failed", "title": "Validation failed", "status": 400, "detail": "One or more fields are invalid.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "validation_failed", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Fix the fields named in `errors[].pointer`. Validate against the operation schema before sending; the SDK gives you typed request bodies. ## Related errors - [request_too_large](/docs/api/errors/request_too_large) — Request too large (HTTP 413) --- URL: https://aishorty.com/docs/api/errors/request_too_large # request_too_large — Shorty API error > Request too large (HTTP 413): when the Shorty API returns the request_too_large error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `request_too_large` | 413 | `https://aishorty.com/docs/api/errors/request_too_large` | ## When it happens The request payload exceeded the accepted size limit for the endpoint. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/request_too_large", "title": "Request too large", "status": 413, "detail": "The request payload is too large.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "request_too_large", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Reduce the payload — for large media, pass a URL the service fetches instead of inlining bytes. ## Related errors - [validation_failed](/docs/api/errors/validation_failed) — Validation failed (HTTP 400) --- URL: https://aishorty.com/docs/api/errors/service_unavailable # service_unavailable — Shorty API error > Service unavailable (HTTP 503): when the Shorty API returns the service_unavailable error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `service_unavailable` | 503 | `https://aishorty.com/docs/api/errors/service_unavailable` | ## When it happens A known upstream dependency (the GPU transcription service or the summary queue) could not start the requested job right now. A deliberate, mapped unavailability — not an unexpected crash. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/service_unavailable", "title": "Service unavailable", "status": 503, "detail": "The transcription service is temporarily unavailable.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "service_unavailable", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Retry with backoff; this is transient. The SDK retries 503s automatically. ## Related errors - [internal_error](/docs/api/errors/internal_error) — Internal server error (HTTP 500) --- URL: https://aishorty.com/docs/api/errors/internal_error # internal_error — Shorty API error > Internal server error (HTTP 500): when the Shorty API returns the internal_error error, why, and how to fix it. | Code | HTTP status | Type URI | | --- | --- | --- | | `internal_error` | 500 | `https://aishorty.com/docs/api/errors/internal_error` | ## When it happens An unexpected error occurred on our side. Unlike service_unavailable, this is not a mapped/known condition. ## Example response ```json { "type": "https://aishorty.com/docs/api/errors/internal_error", "title": "Internal server error", "status": 500, "detail": "An unexpected error occurred.", "instance": "urn:request:req_01J9Z8ABCDEFGH0123456789", "code": "internal_error", "request_id": "req_01J9Z8ABCDEFGH0123456789" } ``` ## How to fix it Retry after a short delay. If it persists, contact support with the `request_id` from the problem body so we can trace it. ## Related errors - [service_unavailable](/docs/api/errors/service_unavailable) — Service unavailable (HTTP 503) --- URL: https://aishorty.com/docs/api/operations/getUsage # Get plan, limits, and usage — Shorty API > Returns the authenticated account’s plan (Free / Premium / Pro), tier limits (upload sizes, real-time minutes, conversion caps), and current cloud-conversion usage against the daily quota. ```http GET /v1/usage ``` Returns the authenticated account’s plan (Free / Premium / Pro), tier limits (upload sizes, real-time minutes, conversion caps), and current cloud-conversion usage against the daily quota. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/usage \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const usage = await shorty.usage.get() console.log(usage.plan, usage.limits) ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `plan` | `UsagePlan` | Yes | — | | `subscription` | `UsageSubscription` | Yes | — | | `limits` | `UsageLimits` | Yes | — | | `usage` | `UsageCounters` | Yes | — | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `401` - `403` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/listArticles # List your articles — Shorty API > Returns the authenticated account’s articles (summaries), newest first, cursor-paginated. Pass the previous response’s `next_cursor` as `?cursor=` to fetch the next page; a cursor is only valid against the SAME query it was minted under. ```http GET /v1/articles ``` Returns the authenticated account’s articles (summaries), newest first, cursor-paginated. Pass the previous response’s `next_cursor` as `?cursor=` to fetch the next page; a cursor is only valid against the SAME query it was minted under. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `cursor` | query | No | — | | `limit` | query | No | — | ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/articles?cursor=cursorValue&limit=20 \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) for await (const article of await shorty.articles.list({ limit: 20 })) { console.log(article.id, article.title) } ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `data` | `Article[]` | Yes | — | | `has_more` | `boolean` | Yes | Whether another page exists after this one. | | `next_cursor` | `string or null` | Yes | Opaque cursor for the next page (pass as `?cursor=`), or null on the last page. Only valid against the SAME query filter. | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/searchArticles # Search your articles — Shorty API > Full-text search over the authenticated account’s own articles (matches title, description, source URL). `q` is required. Results are recency-ranked; pass `limit` (1–20, default 8) to size the page. This endpoint is page-limited rather than cursor-paginated because search is a ranked top-N, which a keyset cursor cannot express. ```http GET /v1/articles/search ``` Full-text search over the authenticated account’s own articles (matches title, description, source URL). `q` is required. Results are recency-ranked; pass `limit` (1–20, default 8) to size the page. This endpoint is page-limited rather than cursor-paginated because search is a ranked top-N, which a keyset cursor cannot express. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `q` | query | Yes | — | | `limit` | query | No | — | | `article_type` | query | No | — | ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/articles/search?q=transformers&limit=8&article_type=article_typeValue \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const hits = await shorty.articles.search({ q: 'transformers', article_type: 'YOUTUBE_ARTICLE', }) ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `data` | `Article[]` | Yes | — | | `count` | `integer` | Yes | Number of hits returned on this page. (-9007199254740991–9007199254740991) | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/getArticle # Get an article — Shorty API > Returns one article the authenticated account owns, including its generated summary parts and extracted body text. A non-existent or foreign id returns 404. ```http GET /v1/articles/{articleId} ``` Returns one article the authenticated account owns, including its generated summary parts and extracted body text. A non-existent or foreign id returns 404. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `articleId` | path | Yes | — | ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/articles/{articleId} \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const article = await shorty.articles.get(articleId) ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | `string` | Yes | Stable article id (use it on GET /v1/articles/{id}). | | `title` | `string or null` | Yes | Article title, or null if not yet summarized. | | `description` | `string or null` | Yes | Short description/summary lead, or null. | | `article_type` | `string` | Yes | Source kind: YOUTUBE_ARTICLE, WEBPAGE_ARTICLE, PDF_ARTICLE, WORD_ARTICLE, TEXT_ARTICLE, or AUDIO_ARTICLE. | | `source_url` | `string or null` | Yes | The original source URL, or null (e.g. text/file input). | | `created_at` | `string` | Yes | ISO-8601 UTC timestamp the article was created. | | `summary` | `object or null` | Yes | The generated summary, or null if not yet produced. | | `body_text` | `string or null` | Yes | The extracted source text / transcript backing the summary, or null. | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `404` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/listTranscriptions # List your transcriptions — Shorty API > Returns the authenticated account’s transcriptions, newest first, cursor-paginated. Pass the previous response’s `next_cursor` as `?cursor=` for the next page; a cursor is only valid against the SAME query it was minted under. ```http GET /v1/transcriptions ``` Returns the authenticated account’s transcriptions, newest first, cursor-paginated. Pass the previous response’s `next_cursor` as `?cursor=` for the next page; a cursor is only valid against the SAME query it was minted under. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `cursor` | query | No | — | | `limit` | query | No | — | ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/transcriptions?cursor=cursorValue&limit=20 \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) for await (const t of await shorty.transcriptions.list()) { console.log(t.id, t.status) } ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `data` | `Transcription[]` | Yes | — | | `has_more` | `boolean` | Yes | Whether another page exists after this one. | | `next_cursor` | `string or null` | Yes | Opaque cursor for the next page (pass as `?cursor=`), or null on the last page. Only valid against the SAME query filter. | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/createTranscription # Create a transcription — Shorty API > Starts an async transcription of media at the given URL. Responds 202 with a `Location` header and a `job_id` you can poll at GET /v1/jobs/{id}. Send an `Idempotency-Key` header to make retries safe. If the transcription service cannot start the job, responds 503. ```http POST /v1/transcriptions ``` Starts an async transcription of media at the given URL. Responds 202 with a `Location` header and a `job_id` you can poll at GET /v1/jobs/{id}. Send an `Idempotency-Key` header to make retries safe. If the transcription service cannot start the job, responds 503. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | header | No | Optional client-generated key (1–255 visible ASCII chars) that makes this POST replay-safe: a retry with the SAME key and body returns the original response (with `Idempotency-Replayed: true`) instead of creating a duplicate. Reusing a key with a different body is rejected (422). | ## Request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | `string` | Yes | A URL to audio/video media to transcribe. | | `language` | `string` | No | Language name ("english") or ISO-639-1 code ("en"). Defaults to english. (2–20 chars) Default: "english". | ## Example request curl: ```bash curl -X POST https://aishorty.com/v1/v1/transcriptions \ -H "Authorization: Bearer shk_live_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "url": "https://example.com/podcast.mp3" }' ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const job = await shorty.transcriptions.create({ url: 'https://example.com/audio.mp3', }) const done = await shorty.jobs.waitFor(job.job_id) ``` ## Response · 202 | Field | Type | Required | Description | | --- | --- | --- | --- | | `job_id` | `string` | Yes | The id of the accepted async job. | | `status` | `string` | Yes | Initial job status (typically "queued"). | | `tracking_url` | `string` | Yes | Relative path to poll this job’s status (GET /v1/jobs/{id}). | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `409` - `422` - `429` - `500` - `503` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. > **Note:** This endpoint honors an `Idempotency-Key` header: a retry with the same key and body replays the original response for 24 hours instead of creating a duplicate job. --- URL: https://aishorty.com/docs/api/operations/getTranscription # Get a transcription — Shorty API > Returns one transcription the authenticated account owns, including the transcript text. A non-existent or foreign id returns 404. ```http GET /v1/transcriptions/{transcriptionId} ``` Returns one transcription the authenticated account owns, including the transcript text. A non-existent or foreign id returns 404. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `transcriptionId` | path | Yes | — | ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/transcriptions/{transcriptionId} \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const transcription = await shorty.transcriptions.get(id) ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `id` | `string` | Yes | Stable transcription id. | | `status` | `string` | Yes | Processing status: PENDING, PROCESSING, SUCCESS, or ERROR. | | `progress` | `number` | Yes | Completion fraction 0–100. | | `model_type` | `string` | Yes | Whisper model used (e.g. WHISPER_BASE, WHISPER_LARGE_V3). | | `language` | `string or null` | Yes | Requested/detected language, or null for auto-detect. | | `input_file` | `string` | Yes | The source media URL that was transcribed. | | `error` | `string or null` | Yes | Error message if the job failed, else null. | | `created_at` | `string` | Yes | ISO-8601 UTC timestamp the transcription was created. | | `output_text` | `string or null` | Yes | The transcript text, or null if not yet produced. | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `404` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/getJob # Get job status — Shorty API > Returns the status of an async job the authenticated account started (a summary, transcription, or subtitle job created via a POST here). The body is the canonical nested job-status shape used across every Shorty surface. A non-existent or foreign job id returns 404. ```http GET /v1/jobs/{jobId} ``` Returns the status of an async job the authenticated account started (a summary, transcription, or subtitle job created via a POST here). The body is the canonical nested job-status shape used across every Shorty surface. A non-existent or foreign job id returns 404. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `jobId` | path | Yes | — | ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/jobs/{jobId} \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const job = await shorty.jobs.get(jobId) console.log(job.status, job.progress, job.output) ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `jobId` | `string` | Yes | The job id. | | `status` | `string` | Yes | Un-normalized status string (queued / processing / success / error / cancelled). | | `progress` | `number` | No | Completion fraction 0–100, when known. | | `step` | `string or null` | No | Human-readable current step, or null. | | `error` | `string or null` | No | Error message if the job failed, or null. | | `output` | `object` | No | Job-type-specific extras (e.g. jobType, title, outputId, articleId, transcriptionId). | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `404` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/downloadSubtitles # Download a subtitle artifact — Shorty API > Returns a short-lived presigned URL for a completed subtitle job’s artifact. Use `?kind=` to pick srt (default), vtt, ass, or the burned-in mp4. A job that is not complete returns 409; a missing or foreign job id returns 404; an artifact kind not generated for the job returns 404. ```http GET /v1/subtitles/{jobId}/download ``` Returns a short-lived presigned URL for a completed subtitle job’s artifact. Use `?kind=` to pick srt (default), vtt, ass, or the burned-in mp4. A job that is not complete returns 409; a missing or foreign job id returns 404; an artifact kind not generated for the job returns 404. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `jobId` | path | Yes | — | | `kind` | query | No | — | ## Example request curl: ```bash curl -X GET https://aishorty.com/v1/v1/subtitles/{jobId}/download?kind=srt \ -H "Authorization: Bearer shk_live_your_key" ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const srt = await shorty.subtitles.download(jobId, { kind: 'srt' }) ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | `string` | Yes | Short-lived presigned URL to download the subtitle artifact. | | `expires_at` | `string` | Yes | ISO-8601 UTC timestamp the presigned URL expires. | | `kind` | `"srt" \| "vtt" \| "ass" \| "burned"` | Yes | Artifact kind: srt/vtt/ass subtitle file, or the burned-in mp4. | | `filename` | `string` | Yes | Suggested download filename. | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `404` - `409` - `429` - `500` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. --- URL: https://aishorty.com/docs/api/operations/createSummary # Create a summary — Shorty API > Starts an async summary job for a YouTube video, a web page, or pasted text, selected by the `source` field. Usually responds 202 with a `Location` header and a `job_id` you can poll at GET /v1/jobs/{id} (send an `Idempotency-Key` header to make retries safe). If the exact source was already summarized in the requested language it responds 200 `already_complete` with the `article_id` and no job to poll. ```http POST /v1/summaries ``` Starts an async summary job for a YouTube video, a web page, or pasted text, selected by the `source` field. Usually responds 202 with a `Location` header and a `job_id` you can poll at GET /v1/jobs/{id} (send an `Idempotency-Key` header to make retries safe). If the exact source was already summarized in the requested language it responds 200 `already_complete` with the `article_id` and no job to poll. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | header | No | Optional client-generated key (1–255 visible ASCII chars) that makes this POST replay-safe: a retry with the SAME key and body returns the original response (with `Idempotency-Replayed: true`) instead of creating a duplicate. Reusing a key with a different body is rejected (422). | ## Request body ### source: "youtube" | Field | Type | Required | Description | | --- | --- | --- | --- | | `source` | `"youtube"` | Yes | — | | `url` | `string` | Yes | A YouTube video URL. | | `language` | `string` | No | Language name ("english") or ISO-639-1 code ("en"). Defaults to english. (2–20 chars) Default: "english". | ### source: "url" | Field | Type | Required | Description | | --- | --- | --- | --- | | `source` | `"url"` | Yes | — | | `url` | `string` | Yes | A web page / article URL to summarize. | | `language` | `string` | No | Language name ("english") or ISO-639-1 code ("en"). Defaults to english. (2–20 chars) Default: "english". | ### source: "text" | Field | Type | Required | Description | | --- | --- | --- | --- | | `source` | `"text"` | Yes | — | | `content` | `string` | Yes | The raw text to summarize. | | `title` | `string` | No | Optional title hint (reserved — currently derived by the summarizer). | | `language` | `string` | No | Language name ("english") or ISO-639-1 code ("en"). Defaults to english. (2–20 chars) Default: "english". | ## Example request curl: ```bash curl -X POST https://aishorty.com/v1/v1/summaries \ -H "Authorization: Bearer shk_live_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "source": "youtube", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" }' ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const job = await shorty.summaries.create({ source: 'youtube', url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', }) const done = await shorty.jobs.waitFor(job.job_id) console.log(done.status, done.output) ``` ## Response · 200 | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | `"already_complete"` | Yes | Marks a cache hit: this exact source was already summarized, so no new job was started. | | `article_id` | `string` | Yes | The id of the already-complete article — fetch it at GET /v1/articles/{id}. | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `409` - `422` - `429` - `500` - `503` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. > **Note:** This endpoint honors an `Idempotency-Key` header: a retry with the same key and body replays the original response for 24 hours instead of creating a duplicate job. --- URL: https://aishorty.com/docs/api/operations/createSubtitles # Create subtitles — Shorty API > Starts an async subtitle job for media at the given URL. Responds 202 with a `Location` header and a `job_id` you can poll at GET /v1/jobs/{id}. Send an `Idempotency-Key` header to make retries safe. Exceeding the monthly free quota returns 429 (quota_exhausted); a Premium-only subtitle style or a free-plan duration/size cap returns 403 (feature_not_enabled); media over the hard duration/size ceiling returns 413 (request_too_large); an upstream start failure returns 503. ```http POST /v1/subtitles ``` Starts an async subtitle job for media at the given URL. Responds 202 with a `Location` header and a `job_id` you can poll at GET /v1/jobs/{id}. Send an `Idempotency-Key` header to make retries safe. Exceeding the monthly free quota returns 429 (quota_exhausted); a Premium-only subtitle style or a free-plan duration/size cap returns 403 (feature_not_enabled); media over the hard duration/size ceiling returns 413 (request_too_large); an upstream start failure returns 503. ## Authentication Requires a Bearer credential — a `shk_live_` API key or an OAuth 2.1 access token with the scope for this resource. See [Authentication](/docs/api). ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `Idempotency-Key` | header | No | Optional client-generated key (1–255 visible ASCII chars) that makes this POST replay-safe: a retry with the SAME key and body returns the original response (with `Idempotency-Replayed: true`) instead of creating a duplicate. Reusing a key with a different body is rejected (422). | ## Request body | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | `string` | Yes | A URL to the media to caption. | | `language` | `string` | No | Language name ("english") or ISO-639-1 code ("en"). Defaults to english. (2–20 chars) Default: "english". | | `style` | `"CLEAN" \| "TIKTOK" \| "PODCAST" \| "MINIMAL"` | No | Subtitle style. CLEAN is free; TIKTOK/PODCAST/MINIMAL require Premium. Default: "CLEAN". | | `duration_seconds` | `number` | No | Measured media duration in seconds, if known. Lets the API reject media over your plan’s duration cap up front (413) instead of failing the job later. | ## Example request curl: ```bash curl -X POST https://aishorty.com/v1/v1/subtitles \ -H "Authorization: Bearer shk_live_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "url": "https://example.com/clip.mp4" }' ``` TypeScript (@aishorty/sdk): ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const job = await shorty.subtitles.create({ url: 'https://example.com/clip.mp4', style: 'TIKTOK', }) ``` ## Response · 202 | Field | Type | Required | Description | | --- | --- | --- | --- | | `job_id` | `string` | Yes | The id of the accepted async job. | | `status` | `string` | Yes | Initial job status (typically "queued"). | | `tracking_url` | `string` | Yes | Relative path to poll this job’s status (GET /v1/jobs/{id}). | ## Errors On failure this endpoint returns an RFC 9457 problem body. Possible statuses: - `400` - `401` - `403` - `409` - `413` - `422` - `429` - `500` - `503` Switch on the problem `code`, not the status. See the [error reference](/docs/api/errors) for every code and how to fix it. > **Note:** This endpoint honors an `Idempotency-Key` header: a retry with the same key and body replays the original response for 24 hours instead of creating a duplicate job. --- URL: https://aishorty.com/changelog # Changelog — Shorty API > Dated changes to the Shorty developer platform: API endpoints, SDK releases, and webhook features. Every developer-facing change to the Shorty API, SDK, and webhooks — newest first. Subscribe via [RSS](/changelog/rss.xml). ## [Shorty API v1](/changelog/shorty-api-v1) _July 20, 2026_ The public Shorty developer API: a versioned /v1 REST surface with API keys, RFC 9457 errors, rate-limit headers, idempotency, and cursor pagination. ## [Outbound webhooks](/changelog/outbound-webhooks) _July 20, 2026_ Standard-Webhooks-signed event deliveries for job completion, with retries, a failure ladder, and 24-hour dual-signing secret rotation. ## [@aishorty/sdk 0.1.0](/changelog/sdk-0-1-0) _July 20, 2026_ The official TypeScript SDK: typed resources, auto-idempotent writes, retry with backoff, cursor auto-pagination, jobs.waitFor, and webhook verification. --- URL: https://aishorty.com/changelog/shorty-api-v1 # Shorty API v1 — Shorty changelog > The public Shorty developer API: a versioned /v1 REST surface with API keys, RFC 9457 errors, rate-limit headers, idempotency, and cursor pagination. The Shorty API is live at `https://aishorty.com/v1`, authenticated with `shk_live_` API keys (created in Settings) or OAuth 2.1 access tokens. Endpoints: - `GET /v1/usage` — plan, limits, and usage counters. - `GET /v1/articles` · `GET /v1/articles/search` · `GET /v1/articles/{id}` — your summarized articles. - `GET /v1/transcriptions` · `POST /v1/transcriptions` · `GET /v1/transcriptions/{id}` — transcription jobs. - `POST /v1/summaries` — summarize YouTube videos, web pages, or text. - `POST /v1/subtitles` · `GET /v1/subtitles/{jobId}/download` — subtitle jobs and artifacts. - `GET /v1/jobs/{id}` — async job status. Writes are async (`202` + `job_id`), retry-safe via the `Idempotency-Key` header, and burst-limited per key with IETF `RateLimit` response headers. Errors are RFC 9457 `application/problem+json` with a stable `code` — see the [error reference](/docs/api/errors). --- URL: https://aishorty.com/changelog/outbound-webhooks # Outbound webhooks — Shorty changelog > Standard-Webhooks-signed event deliveries for job completion, with retries, a failure ladder, and 24-hour dual-signing secret rotation. Register HTTPS endpoints in Settings and Shorty delivers `job.succeeded` / `job.failed` events signed per the [Standard Webhooks](https://www.standardwebhooks.com) spec. Event `data` mirrors `GET /v1/jobs/{id}`, so one parser serves polling and webhooks. - 9 delivery attempts over ~45 hours with jittered backoff. - Warning emails at 5/10/15 consecutive failures; auto-disable at 20. - Secret rotation with a 24-hour dual-signing window. - Delivery log, test events, and replay in the developer console. Full details in the [webhook docs](/docs/api/webhooks). --- URL: https://aishorty.com/changelog/sdk-0-1-0 # @aishorty/sdk 0.1.0 — Shorty changelog > The official TypeScript SDK: typed resources, auto-idempotent writes, retry with backoff, cursor auto-pagination, jobs.waitFor, and webhook verification. A hand-written, dependency-light TypeScript client for the Shorty API, open-sourced at [github.com/DevinoSolutions/shorty-sdk](https://github.com/DevinoSolutions/shorty-sdk). `@aishorty/sdk` is the package name. ```typescript import { Shorty } from '@aishorty/sdk' const shorty = new Shorty({ apiKey: process.env.SHORTY_API_KEY }) const job = await shorty.summaries.create({ source: 'youtube', url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', }) const done = await shorty.jobs.waitFor(job.job_id) ``` - Typed wire DTOs for every resource. - Automatic `Idempotency-Key` on writes; safe retries with backoff. - `for await` cursor auto-pagination. - `verifyWebhookSignature` for Standard-Webhooks receivers.