LINK42 SDKs
Server-side TypeScript and Python clients for models, streaming, private assets and paid video tasks.
Non-generating cost estimates
POST/v1/estimates
Use estimates.create with endpoint chat/completions, images/generations or videos and its request payload. The quote uses this key's authorization and effective discount, without calling an upstream or reserving money. estimated_micro_usd is in millionths of USD; is_free is an explicit free price, not just rounding. Expiry, upstream entitlement and the final bill are separate; use key budgets for hard spending limits.
quote = client.estimates.create(endpoint='images/generations', payload={'model': 'AUTHORIZED_MODEL', 'prompt': 'A product image', 'size': '2K'})
print(quote['estimated_micro_usd'], quote['is_free'])Install the release packages
The API origin is https://www.link42.ai. OpenAI-compatible clients use /v1, native Anthropic clients use /anthropic, and Gemini REST uses /gemini. Older unprefixed native routes remain available. These first-party archives are served with this website release; they have not been published to npm or PyPI.
Node.js 20+ or Python 3.10+ is required. Keep LINK42_API_KEY on the server, never in frontend code.
npm install https://www.link42.ai/downloads/sdk/link42-sdk-0.1.6.tgz
export LINK42_API_KEY="YOUR_LINK42_KEY"python -m pip install https://www.link42.ai/downloads/sdk/link42-0.1.6-py3-none-any.whl
export LINK42_API_KEY="YOUR_LINK42_KEY"Upload a reference and create a video
POST/v1/videos
Creating a video is billable. Save its ID before waiting. Uploading an asset does not start generation. Select an enabled model from the live catalogue.
import Link42 from '@link42/sdk';
const client = new Link42();
const image = await client.assets.uploadFile('./reference.png');
const task = await client.videos.create({
model: process.env.LINK42_VIDEO_MODEL,
content: [
{ type: 'text', text: 'Slow camera push in.' },
{ type: 'image_url', role: 'first_frame', image_url: { url: image.reference } }
],
duration: 4, resolution: '480p', generate_audio: false
});
console.log(task.id);
const result = await client.videos.wait(task.id);
console.log(result.stored_url ?? result.provider_url);import os
from link42 import Link42
client = Link42()
image = client.assets.upload_file('reference.png')
task = client.videos.create(
model=os.environ['LINK42_VIDEO_MODEL'],
content=[
{'type': 'text', 'text': 'Slow camera push in.'},
{'type': 'image_url', 'role': 'first_frame', 'image_url': {'url': image['reference']}}
], duration=4, resolution='480p', generate_audio=False
)
print(task['id'])
result = client.videos.wait(task['id'])
print(result.get('stored_url') or result.get('provider_url'))Streaming
POST/v1/chat/completions
const stream = await client.chat.completions.create({
model: process.env.LINK42_TEXT_MODEL,
messages: [{ role: 'user', content: 'Hello' }], stream: true
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta.content ?? '');
}import os
for chunk in client.chat.completions.create(
model=os.environ['LINK42_TEXT_MODEL'],
messages=[{'role': 'user', 'content': 'Hello'}], stream=True
):
print(chunk['choices'][0].get('delta', {}).get('content', ''), end='', flush=True)Retries and reconciliation
Only GET requests are automatically retried. Paid POST requests and interrupted streams are never replayed automatically. A polling timeout does not cancel the remote task; keep querying the same ID or explicitly cancel it.
Link42Error exposes status, code and request ID (requestID in TypeScript, request_id in Python). UploadError preserves stage, upload ID and asset ID so a failed confirmation can be resumed.
Remote API origins must use HTTPS. Plain HTTP is restricted to localhost, 127.0.0.1 and ::1 for local development. Redirects are refused. Python methods are synchronous; use a worker thread in asyncio applications.
Originals and model payload limits
JPG/PNG/WebP original uploads default to 100 MiB; administrators may configure a lower limit. Upload first and use the returned asset:// reference. Image generation and direct video references prepare owned originals to at most 10 MiB each and 20 MiB combined, without changing the stored original. Decoding is limited to 40 million pixels; invalid or unsafe images are rejected.
External URLs and inline data URIs remain subject to the selected model's limits. Model-library import accounts follow their provider's limits. Media upload does not grant reference capabilities to a model.
Supported SDK resources
Both clients expose models.list/get, chat, Responses, Anthropic Messages/count tokens, JSON images.generate/edit, embeddings, Gemini, image tasks, video tasks, private assets, usage and scores. Use the API model id returned by models.list for models.get, not a numeric website id. Catalogue entries are filtered by key and organization permissions; visibility is not a guarantee of current upstream health.
Audio, Realtime, Batch and multipart image edits are not implemented. JSON image edits are reference-based. Vendor fields are preserved, but each model only accepts its documented capabilities.
API Key asset endpoints
POST/v1/assets
SDKs reserve an upload, PUT directly to the short-lived storage URL without the LINK42 Key, then confirm at /v1/assets/uploads/{upload_id}/complete. assets.downloadURL(id) / assets.download_url(id) returns a short-lived owner-scoped attachment URL; do not persist or share it. Asset ownership, key status and IP allowlists are checked server-side.
Cost tags and the usage API
GET/v1/usage/report
Tag spend by setting cost tags on an API key in the console, or per request with the X-OneRelay-Tags header (comma separated, up to 10 tags of 64 bytes each). Tags never change routing or price; they label the usage record.
A key whose owner turned on usage access can read the whole account's spend: GET /v1/usage/report groups it by tag, model, key or day over at most 93 days (add format=csv for a file), and GET /v1/usage/logs pages the request records. A request with several tags counts on each tag line; the total counts it once.
curl https://www.link42.ai/v1/chat/completions \
-H "Authorization: Bearer $LINK42_API_KEY" \
-H "X-OneRelay-Tags: project:alpha, env:prod" \
-H "Content-Type: application/json" \
-d '{"model": "YOUR_MODEL", "messages": [{"role": "user", "content": "Hello"}]}'
curl "https://www.link42.ai/v1/usage/report?group_by=tag&start=2026-09-01T00:00:00Z&end=2026-09-18T00:00:00Z" \
-H "Authorization: Bearer $LINK42_API_KEY"Automatic routing, presets and prompt templates
POST/v1/chat/completions
Set model to auto to let the gateway choose among the models the operator made eligible: auto and auto:quality prefer the highest quality score, auto:cost the lowest list price, auto:latency the lowest recent time to first token. Only models your key may call for the endpoint are considered.
Save a routing preset in the console (API keys page) and call it as @preset/<slug>: its models are tried in order (or by the preset's strategy), and when a model has no available account or its upstreams fail with 5xx / 429 the next one is tried. Each model tried has its own usage record (the retry's request ID ends in -fb1, -fb2 …) and is charged at its own price; X-OneRelay-Model tells which model answered. Preferred providers route to those upstreams first, or only to them.
Prompt templates saved in the console are put at the front of the system prompt of chat, Messages, Responses and Gemini requests made with the keys they are bound to; binding several versions with weights runs an A/B test whose results the template's history compares.
curl -i https://www.link42.ai/v1/chat/completions \
-H "Authorization: Bearer $LINK42_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "@preset/main", "messages": [{"role": "user", "content": "Hello"}]}'
# X-OneRelay-Model: the model that answeredScores and datasets
POST/v1/scores
Score a request (request_id) or a Claude Code session (session_id) of your account with any API key of yours: name is 1-40 lower-case letters, digits, dots, hyphens or underscores, value a number from -1000000 to 1000000, comment optional. Scoring the same name again replaces the value. Scores can also be given in the console's session view.
The console exports a dataset as JSON Lines: each request with its usage, its own and its session's scores, and — where request content capture kept it — the request and response bodies, filtered by a score name and minimum value.
curl https://www.link42.ai/v1/scores \
-H "Authorization: Bearer $LINK42_API_KEY" \
-H "Content-Type: application/json" \
-d '{"request_id": "REQUEST_ID", "name": "helpful", "value": 1, "comment": "correct answer"}'