AI assistants
Machine-readable docs and a copy-paste context block so an AI coding assistant writes correct Videohati code — llms.txt, per-page markdown, and setup for Claude Code, Codex, opencode, and Cursor.
If you write Videohati code with an AI assistant, give it the facts up front. This page has two things for that: docs it can read as plain text, and a context block you paste into your assistant's rules file so it stops guessing method names and states.
Machine-readable docs
Every page here is also served as plain markdown, plus two index files that follow the llms.txt convention. Point a tool at these instead of scraping the rendered HTML:
- /llms.txt — one line per page: title, markdown URL, and a short description. The map an assistant reads first.
- /llms-full.txt — every page concatenated into a single text stream, for a tool that wants the whole set in one fetch.
/md/en/<slug>.md— any page as raw markdown. For example /md/en/quickstart.md or /md/en/sdks/node.md. Arabic pages are at/md/ar/<slug>.md.
Teach your assistant Videohati
The block below is a compact, code-oriented brief: authentication, the upload → wait → play path, per-stack entry points, the webhook events, error classes, and the pitfalls that trip up first attempts. Copy it, or download it straight into a rules file.
# Videohati — context for AI coding assistants
Videohati is a video hosting API for developers and teams: upload a file, it is
processed into adaptive streams, and you embed a player that measures views and
deters copying. The player and docs are Arabic-first (RTL) and also English.
## Authentication
- Project API keys are Bearer tokens: `Authorization: Bearer vh_live_...` in
production, `vh_test_...` against test data. The SDK derives the mode from the
prefix.
- Never put an API key in a browser or client bundle. For playback, create the
session on your server and hand only the short-lived `sessionToken` to the page.
- Projects and API keys are created and managed in the dashboard, not through
the API.
- Base URL: `https://api.videohati.com`. Dashboard: `https://app.videohati.com`.
## Golden path (Node, @videohati/node 1.0.0-rc.0)
```ts
import { Videohati } from "@videohati/node";
const videohati = new Videohati({ apiKey: process.env.VIDEOHATI_API_KEY });
// 1. Upload — one call runs create -> start -> multipart parts -> complete.
const uploaded = await videohati.videos.upload("./lecture-01.mp4", {
onProgress: (p) => console.log(`${p.uploadedBytes}/${p.totalBytes}`),
});
// 2. Wait until it is playable end to end (polls; do not build your own loop).
const video = await videohati.videos.waitForState(uploaded.videoId);
console.log(video.state); // "ready"
// 3. Create a playback session on the server; hand the token to the page.
const session = await videohati.playback.createSession({
videoId: video.videoId,
viewerDisplayText: "Ahmed K.", // drawn into the on-video watermark
});
// 4. Embed the player with session.sessionToken (see per-stack notes below).
```
Video states: `uploading` -> `uploaded` -> `encoding` -> `ready` (or `failed`).
There is no "queued" state.
## Per-stack entry points
**Node (@videohati/node)** — `new Videohati({ apiKey?, env?, baseUrl?,
maxRetries?, timeoutMs?, fetch? })`. Resources:
- `videos` — create, list, get, delete, setThumbnail, resetThumbnail,
startUpload, completeUpload, abortUpload, upload, waitForState.
- `playback` — createSession. Your app only calls createSession; the player
performs the rest of the playback calls on its own.
- `webhooks` — register, list, get, update, delete, rotateSecret,
reopenCircuit, sendTest, listDeliveries, verify.
**React (@videohati/react 1.0.0-rc.0)** — wrap once in `<VideohatiProvider
baseUrl=...>`, read config with `useVideohatiConfig()`. Play with
`<VideohatiPlayer videoId projectId sessionToken autoplay lang="ar" ... />`
(sessionToken is required and comes from a server call). Hooks:
`useVideohatiUpload({ projectId })`, `useVideohatiVideo(videoId, {
pollIntervalMs })`, `useWebhookDeliveries(projectId, endpointId, {...})`.
**Laravel (videohati/laravel 1.0.0-rc.0, PHP >= 8.2)** — reach the client
through the `Videohati\Laravel\Facades\Videohati` facade: `Videohati::videos()`,
`Videohati::playback()`, `Videohati::webhookEndpoints()`. Note the circuit method
is `reopen(...)` here (it is `reopenCircuit(...)` in Node). Render the embed with
the Blade directive: `@videohatiPlayer(['videoId' => $video->id, 'sessionToken'
=> $session->sessionToken])`.
**Plain HTML (@videohati/player 1.0.0)** — load the script and set attributes:
```html
<script src="https://cdn.videohati.com/player.js" async></script>
<div
data-videohati-video-id="..."
data-videohati-project-id="..."
data-videohati-session-token="<sessionToken>"
data-videohati-autoplay="muted"
data-videohati-lang="ar"
></div>
```
It auto-mounts on every `[data-videohati-video-id]` element and auto-destroys on
pagehide. Programmatic API: `Videohati.create({ target, videoId, projectId,
sessionToken, ... })`.
## Webhooks
Eight event types are delivered as `{ id, type, created_at, project_id, data }`:
`video.upload.completed`, `video.encoding.ready`, `video.encoding.failed`,
`video.deleted`, `playback.started`, `playback.completed`, `payment.succeeded`,
`payment.failed`.
Verify every delivery with the raw request body (never re-serialized JSON):
```ts
import { verifyWebhookSignature, SIGNATURE_HEADER } from "@videohati/node";
const { valid, event } = verifyWebhookSignature({
payload: req.rawBody, // exactly as received
header: req.headers[SIGNATURE_HEADER], // "videohati-signature"
secret: process.env.VIDEOHATI_WEBHOOK_SECRET,
});
if (!valid) return res.status(400).end();
res.status(200).end(); // return 2xx within 10 seconds
```
Header: `Videohati-Signature: t=<unix_ts>,v1=<hex_sha256>`, HMAC-SHA256 over
`<unix_ts>.<raw_body>`, 300s tolerance. Return a 2xx within 10 seconds — a slow
or non-2xx response counts as a failed delivery. Failed deliveries retry with a
growing delay; a persistently failing endpoint trips its circuit breaker, which
you clear with `webhooks.reopenCircuit(projectId, id)`.
## Errors and retries
Every non-2xx throws a typed subclass of `VideohatiError` (each carries `.status`
and a machine `.code`): `VideohatiConnectionError`, `VideohatiTimeoutError`,
`VideohatiValidationError` (400), `VideohatiAuthenticationError` (401),
`VideohatiPermissionError` (403), `VideohatiNotFoundError` (404),
`VideohatiConflictError` (409), `VideohatiRateLimitError` (429, read
`.retryAfterSeconds`), `VideohatiServerError` (5xx). GET/DELETE, 429s, and 5xxs
retry automatically with exponential backoff capped at 8 seconds (2 retries by
default; 429 honors `Retry-After`).
## Pitfalls
- Do not hand-roll a status poll — call `videos.waitForState(videoId)` (default
target states `["ready","failed"]`, 3s interval, 15-minute timeout).
- Playback sessions expire by design; mint a fresh one per view on the server
rather than caching `sessionToken`.
- Custom thumbnails must be JPEG, PNG, or WebP and at most 5 MB.
- Uploads are multipart with resumable parts — pass an `idempotencyKey` to make
`videos.upload` resume-safe across restarts.
## Pointers
- Machine-readable index: https://docs.videohati.com/llms.txt
- Full text of every page: https://docs.videohati.com/llms-full.txt
- Any page as markdown: https://docs.videohati.com/md/en/<slug>.md
(for example /md/en/quickstart.md, /md/en/sdks/node.md, /md/en/guides/webhooks.md)
curl -o CLAUDE.md https://docs.videohati.com/ai/videohati-context.mdSet up your tool
Each assistant reads its project rules from a specific file. Drop the context block into the right one and it applies to every prompt in that project.
Claude Code reads CLAUDE.md at the repository root and treats it as always-on
project memory. Append the context block to it — this keeps any notes you
already have and adds Videohati underneath.
curl https://docs.videohati.com/ai/videohati-context.md >> CLAUDE.mdCodex reads AGENTS.md at the repository root. Append the context block so the
Videohati facts load with every task in this project.
curl https://docs.videohati.com/ai/videohati-context.md >> AGENTS.mdopencode also uses the AGENTS.md convention. The same one-liner works — if the
file already exists, this adds Videohati at the end without disturbing the rest.
curl https://docs.videohati.com/ai/videohati-context.md >> AGENTS.mdCursor reads rule files from .cursor/rules/. Save the block as an .mdc rule,
then add a two-line frontmatter header so Cursor pulls it in only when it is
relevant rather than on every prompt.
mkdir -p .cursor/rules
curl -o .cursor/rules/videohati.mdc https://docs.videohati.com/ai/videohati-context.mdAdd this frontmatter to the top of .cursor/rules/videohati.mdc:
---
description: Videohati video API — auth, upload, playback sessions, webhooks
alwaysApply: false
---Tools that browse documentation on their own pick up /llms.txt automatically —
no setup needed.