Artifacts API
Publish a page or a small folder, get a stable URL, and ship new versions to it.
An artifact is a page you publish: one .html or .md file, or a small folder with its styles, scripts and images. It gets a stable URL at https://anyrouter.dev/a/{id}. Publishing again makes a new numbered version at the same URL.
Artifacts are private by default. Only you can open a private artifact. Set visibility to link to share it with anyone who has the URL.
Authenticate with an inference key (sk-ar-v1-…), a management key (ak_…), or a signed-in session. A management key needs read:artifacts to read and write:artifacts to publish, change or delete. A key restricted to specific endpoints must allow the Artifacts endpoint.
Publish one file
curl -sS https://anyrouter.dev/api/v1/artifacts \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Hello","files":{"index.html":{"content":"<h1>Hi</h1>"}}}'{
"id": "k3Vx9TqLm2Ab7Zcd",
"version": 1,
"url": "https://anyrouter.dev/a/k3Vx9TqLm2Ab7Zcd",
"visibility": "private"
}| Field | Type | Notes |
|---|---|---|
files | object | Path to { "content": "...", "encoding": "utf8" | "base64" }. Use base64 for images and fonts. Required unless you send manifest. |
title | string | Up to 120 characters. Defaults to the page's <title> or first heading. |
description | string | Up to 300 characters. |
visibility | string | private (default), link or public. |
entry_path | string | The page to open. Defaults to index.html, then the only .html or .md file. |
label | string | A short note for this version, up to 60 characters. |
An inline publish carries up to 5 MB in total. For more, or for a folder, use the manifest flow.
Publish a folder
List every file with its size and SHA-256. The response names the files the server still needs. Upload each one, then commit.
# 1. Open the upload (202)
curl -sS https://anyrouter.dev/api/v1/artifacts \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Report",
"manifest": [
{"path": "index.html", "size": 1204, "sha256": "<64 hex chars>"},
{"path": "app.js", "size": 5310, "sha256": "<64 hex chars>"}
]
}'{
"upload_id": "up_8Hq2...",
"expires_at": "2026-10-23T12:00:00Z",
"missing": [
{"path": "index.html", "sha256": "…", "size": 1204,
"put_url": "https://anyrouter.dev/api/v1/artifacts/uploads/up_8Hq2.../files/…"}
]
}# 2. Upload each missing file to its put_url
curl -sS -X PUT "$PUT_URL" \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
--data-binary @index.html
# 3. Commit (201), same response as an inline publish
curl -sS -X POST https://anyrouter.dev/api/v1/artifacts/uploads/$UPLOAD_ID/commit \
-H "Authorization: Bearer $ANYROUTER_API_KEY"The upload is open for one hour. Committing twice is safe: the second call returns the same version. The server checks each file's SHA-256 and rejects a mismatch.
Publish a new version
Send the version you built on as base_version.
curl -sS https://anyrouter.dev/api/v1/artifacts/$ID/versions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"base_version":1,"label":"fix chart","files":{"index.html":{"content":"<h1>Hi again</h1>"}}}'Files you list are added or replaced. Files you leave out are kept. Set a path to null to remove it. The manifest flow accepts the same request plus delete (a list of paths to remove) and keep_unlisted: false (the manifest is the whole new file set).
If someone published a newer version first, you get 409 version_conflict with the version to merge against:
{
"error": {
"code": "version_conflict",
"message": "Someone published version 2 first. Read it, merge, and retry with base_version 2.",
"current_version": 2
}
}Set "force": true instead of base_version to publish on top of whatever is current.
Read, list and version history
| Request | Returns |
|---|---|
GET /api/v1/artifacts | Your live artifacts, newest first. Query limit (1 to 100, default 25) and cursor; the response carries next_cursor. |
GET /api/v1/artifacts/{id} | Metadata, the current version and its file list. ?version=n reads an older version (owner only). |
GET /api/v1/artifacts/{id}/versions | The version history. |
GET /api/v1/artifacts/{id}/files/{path} | The source of one file (owner only), as a download. |
Change visibility or details
curl -sS -X PATCH https://anyrouter.dev/api/v1/artifacts/$ID \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"visibility":"link"}'You can change title, description, icon and visibility. public is currently treated the same as link.
Delete
curl -sS -X DELETE https://anyrouter.dev/api/v1/artifacts/$ID \
-H "Authorization: Bearer $ANYROUTER_API_KEY"{ "id": "k3Vx9TqLm2Ab7Zcd", "deleted": true }The URL stops working for everyone else at once.
Limits
| Limit | Value |
|---|---|
| One file | 5 MB |
| One inline publish | 5 MB |
| One publish (manifest) | 50 files, 20 MB |
| One version | 100 files, 50 MB |
| Artifacts per account | 100 |
| Stored size per account (current versions) | 200 MB |
| Publishes per hour | 20 |
| Path length | 200 characters |
Allowed file types: html, md, css, js, mjs, json, txt, csv, png, jpg, jpeg, gif, webp, avif, svg, woff2, ico. The server decides the content type from the extension and ignores the one you send.
Errors
Errors look like { "error": { "code": "...", "message": "..." } }.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing or has the wrong shape. |
| 400 | base_version_required | A new version needs base_version (or force). |
| 400 | invalid_path, duplicate_path, invalid_entry, no_entry, no_files | The file set is not valid. |
| 400 | hash_mismatch, missing_blobs, unknown_blob | The upload does not match the manifest. |
| 400 | invalid_encoding | A base64 file is not valid base64. |
| 401 | No valid credential. | |
| 403 | The key lacks the scope or the endpoint. | |
| 403 | quota_exceeded | You reached the artifact limit. |
| 404 | not_found | No such artifact, or you cannot see it. |
| 409 | version_conflict | base_version is behind. Merge and retry. |
| 409 | already_committed | The upload was already used. |
| 410 | upload_expired | The upload was open longer than an hour. |
| 413 | file_too_large, inline_too_large, publish_too_large, version_too_large, too_many_files | A size limit was exceeded. |
| 415 | unsupported_media_type | The file type is not allowed. |
| 429 | rate_limited | More than 20 publishes in an hour. |
From the CLI
anyr artifact publish ./report # a file or a folder
anyr artifact publish ./report --id k3Vx9TqLm2Ab7Zcd --label "fix chart"
anyr artifact publish page.html --title "Weekly" --visibility link --jsonFlags: --title, --visibility private|link|public, --id (publish a new version), --label, --yes (skip the confirmation for public) and --json. The URL prints on stdout. Exit code 2 means a version conflict.
From an AI agent
The AnyRouter MCP server exposes five tools: publish_artifact, list_artifacts, get_artifact, delete_artifact and set_artifact_visibility. publish_artifact takes text files only, up to 1 MB, and publishes privately unless you say otherwise. Listing and reading need read:artifacts; the rest need write:artifacts.