REST API reference
Complete reference for the ScaiAtlas REST API. All routes are under /api/v1.
Base URL#
1 | |
Interactive OpenAPI docs (Swagger UI at /docs, ReDoc at /redoc) are available on
deployments running in debug mode.
Authentication#
Send a bearer token on every request (except the public auth endpoints noted below):
1 | |
Tokens prefixed sk_ are API keys; other tokens are validated as ScaiKey JWTs.
Missing or invalid credentials return 401 with WWW-Authenticate: Bearer. See
Authentication for the permission model.
Pagination#
List endpoints accept page (≥1) and page_size (1–100) query parameters and return
a data array alongside flat pagination fields:
1 2 3 4 5 6 7 | |
The Python SDK wraps this as PaginatedResponse[T]: iterate .items (an alias for
data) or use the list_all() / *_all() helpers to walk every page. Its
.pagination object surfaces page, per_page (= page_size), total_count
(= total), total_pages, and computed has_next / has_prev.
Errors#
Errors return an appropriate 4xx/5xx status and a JSON body:
1 | |
See Errors for the full list.
Conventions#
- A version path segment may be the literal
latestto resolve the highest semver. 201 Createdfor creates,202 Acceptedfor async work (variant conversion),204 No Contentfor deletes.
Namespaces#
Base: /api/v1/namespaces
| Method | Path | Summary | Permission |
|---|---|---|---|
| GET | /namespaces |
List accessible namespaces | authenticated |
| POST | /namespaces |
Create a namespace | authenticated |
| GET | /namespaces/{namespace} |
Get namespace details | view |
| PATCH | /namespaces/{namespace} |
Update namespace | update_namespace |
| DELETE | /namespaces/{namespace} |
Delete namespace | delete_namespace |
| GET | /namespaces/{namespace}/members |
List members | manage_members |
| POST | /namespaces/{namespace}/members |
Add a member | manage_members |
| PATCH | /namespaces/{namespace}/members/{member_id} |
Update a member's role | manage_members |
| DELETE | /namespaces/{namespace}/members/{member_id} |
Remove a member | manage_members |
| GET | /namespaces/{namespace}/my-permissions |
Your effective permissions | authenticated |
List accepts a visibility filter (public|tenant|private). Results are
limited to public namespaces plus those in your tenant.
Create body (NamespaceCreate):
1 | |
name— 2–64 chars,^[a-z0-9][a-z0-9-]*[a-z0-9]$(required)display_name— ≤255 chars, optionaldescription— optionalvisibility—public|tenant|private(defaulttenant)
Returns 201 with a Namespace. The creator becomes owner. 409 if the name exists.
Namespace object:
| Field | Type |
|---|---|
id |
string |
name |
string |
display_name |
string | null |
description |
string | null |
visibility |
public|tenant|private |
owner_tenant_id |
string |
model_count |
int |
created_at, updated_at |
datetime |
PATCH body (NamespaceUpdate): display_name, description, visibility (all optional).
DELETE: 204; 409 if the namespace still has models.
Members. Add-member body (NamespaceMemberCreate): exactly one of user_id /
group_id, plus role (owner|admin|member|viewer). A NamespaceMember
is { id, user: UserSummary|null, group: GroupSummary|null, role, granted_by, granted_at }
where UserSummary = {id, email, display_name, tenant_id} and
GroupSummary = {id, name, tenant_id}. The last owner cannot be demoted/removed (400).
my-permissions returns { role, permissions: [...], inherited_from: [{type, role, group_id, group_name}] }.
Models#
Base: /api/v1/namespaces/{namespace}/models
| Method | Path | Summary | Permission |
|---|---|---|---|
| GET | /models |
List models | view |
| POST | /models |
Create a model | create_model |
| GET | /models/{model} |
Get model | view |
| PATCH | /models/{model} |
Update model | update_own_model (or update_any_model) |
| DELETE | /models/{model} |
Delete model | delete_own_model (or delete_any_model) |
List accepts a type filter (a model type).
Returns ModelSummary items.
Create body (ModelCreate):
1 2 3 4 5 6 7 8 9 10 11 | |
name— 1–128 chars,^[a-zA-Z0-9][a-zA-Z0-9._-]*$(required)type— model type (required)display_name(≤255),description,license(≤64),capabilities(object),modalities({input[], output[]}),metadata(object),tags(string[]) — all optional
Returns 201 with a Model. 409 if the name exists in the namespace.
Model object: id, namespace, name, display_name, description, type,
license, capabilities, modalities, metadata, tags, version_count,
download_count, created_by, created_at, updated_at. (ModelSummary is a
lighter projection used in list/search: id, namespace, name, display_name,
type, license, modalities, tags, latest_version, download_count, timestamps.)
PATCH body (ModelUpdate): display_name, description, license,
capabilities, modalities, metadata, tags — all optional.
Versions#
Base: /api/v1/namespaces/{namespace}/models/{model}/versions
| Method | Path | Summary | Permission |
|---|---|---|---|
| GET | /versions |
List versions (semver desc) | view |
| POST | /versions |
Create a version (starts upload) | upload_version |
| GET | /versions/{version} |
Get version (latest supported) |
authenticated |
| PATCH | /versions/{version} |
Update version | update_own_model (or any) |
| DELETE | /versions/{version} |
Delete version | delete_own_model (or any) |
Create body (VersionCreate):
1 2 3 4 5 6 7 8 | |
version— semver^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$(required)format— 1–32 chars, validated against active formats, lowercased (required)release_notes,requirements(object),quantization— optionalfiles— list of{ path, size, checksum_sha256 }
Returns 201 with a Version (total_size summed from files, checksum pending
until finalized). 400 for an unsupported format, 409 if the version exists.
Version object: id, model_id, namespace, model, version, full_name
(namespace/model:version), version_major/minor/patch, version_prerelease,
release_notes, requirements, format, quantization, files[] (each {path, size, checksum_sha256}, also exposed as name/sha256), total_size,
checksum_sha256, status, variant_count, created_by, created_at,
updated_at, published_at.
PATCH body (VersionUpdate): release_notes, status
(version status) — optional.
Variants#
Base: /api/v1/namespaces/{namespace}/models/{model}/versions/{version}/variants
| Method | Path | Summary |
|---|---|---|
| GET | /variants |
List variants |
| POST | /variants |
Request a variant conversion (202) |
| GET | /variants/{variant_name} |
Get a variant |
| DELETE | /variants/{variant_name} |
Delete a variant |
Create body (VariantCreate):
1 | |
variant_name— ≤64 chars (required)format—safetensors|pytorch|gguf|onnx|other(required)quantization— ≤32 chars, optional
Returns 202 Accepted with a Variant in pending status. 409 if the variant
name exists; deleting the primary variant returns 400.
Variant object: id, version_id, variant_name, format, quantization,
total_size, checksum_sha256, is_primary, status
(variant status), files[]
({path, size, checksum_sha256}), created_at.
Upload#
Base: …/versions/{version}/upload — multipart, proxied to storage. Requires
upload_version.
| Method | Path | Body | Returns |
|---|---|---|---|
| POST | /upload/init |
UploadInit |
UploadInitResponse |
| PUT | /upload/{upload_id}/parts/{part_number} |
raw bytes | { part_number, etag } |
| GET | /upload/{upload_id}/parts |
— | { upload_id, parts: [...] } |
| POST | /upload/{upload_id}/complete |
UploadComplete |
{ status: "complete", upload_id } |
| DELETE | /upload/{upload_id} |
— | 204 (abort) |
UploadInit={ file_path, file_size (≥1), checksum_sha256 (64 hex), content_type }UploadInitResponse={ upload_id, file_id, part_size, part_count }UploadComplete={ parts: [ { part_number, etag } ] }
init returns 400 if the version isn't in uploading state.
Download#
Base: …/versions/{version}/download — proxied streaming. Requires download.
| Method | Path | Summary |
|---|---|---|
| GET | /download |
File metadata + URLs for the version |
| GET | /download/files/{file_path} |
Stream a file (Range-aware) |
/downloadquery:variant(name),file(specific path). Returns{ version_id, variant, total_size, file_count, files: [{path, size, checksum_sha256, url}] }./download/files/{path}query:variant. HonorsRange: bytes=start-end(→206 Partial Content,Content-Range); setsX-Checksum-SHA256,Accept-Ranges: bytes.416for an unsatisfiable range.
The version/variant must be in an available/ready state (400 otherwise).
Manifests (P2P)#
Base: …/versions/{version}/manifest
| Method | Path | Summary |
|---|---|---|
| GET | /manifest |
Get the distribution manifest |
| POST | /manifest |
Generate it (query chunk_size, default 64 MB) → 201 |
DistributionManifest: { version_id, variant_id, chunk_size, total_size, total_chunks, checksum_sha256, files: [ { path, size, checksum_sha256, chunks: [ { index, offset, size, checksum_sha256 } ] } ] }. GET returns 404 until generated;
POST returns 409 if it already exists.
Search#
Base: /api/v1/search
| Method | Path | Query |
|---|---|---|
| GET | /search/models |
q, type, namespace, tag, sort (updated|created|name|downloads), order (asc|desc), pagination |
| GET | /search/namespaces |
q, pagination |
Model search returns ModelSummary items; namespace search returns lightweight
{id, name, display_name, description, visibility} objects. Both are scoped to public
- same-tenant results.
Options#
Base: /api/v1/options — the catalog of what the deployment supports.
| Method | Path | Summary |
|---|---|---|
| GET | /options/formats |
Active formats |
| GET | /options/quantizations |
Active quantizations (filter format_name) |
Format = { id, name, display_name, description, file_extensions[], is_active, created_at, updated_at }. Quantization = { id, name, display_name, description, format_id, bits, is_active, created_at, updated_at }.
P2P#
Base: /api/v1/p2p — returns 503 if P2P is disabled on the deployment.
| Method | Path | Summary |
|---|---|---|
| POST | /p2p/announce |
Announce peer availability |
| GET | /p2p/peers |
List peers (active_only, pagination) |
| GET | /p2p/peers/by-version |
Peers for a version_id (+ variant_id) |
| GET | /p2p/stats |
Tracker statistics |
| DELETE | /p2p/peers/{node_id} |
Remove a peer |
by-version returns { version_id, variant_id, total_peers, seeders, leechers, peers: [ { node_id, address, port, is_seeding, chunks_available, chunks_total, last_seen } ] }.
Auth#
Base: /api/v1/auth. Endpoints marked public need no token.
| Method | Path | Auth | Summary |
|---|---|---|---|
| GET | /auth/config |
public | OAuth config for clients |
| GET | /auth/me |
required | Current user info |
| POST | /auth/token |
public | Exchange auth code for tokens (PKCE) |
| POST | /auth/refresh |
public | Refresh an access token |
| POST | /auth/revoke |
public | Revoke a token |
| POST | /auth/cli/init |
public | Start a device-flow session |
| GET | /auth/cli/status |
public | Poll device-flow status |
| GET | /auth/cli/session |
public | Look up a session by user code |
| POST | /auth/cli/authorize |
required | Authorize a device-flow session from the browser |
GET /auth/me → { id, email, display_name, tenant_id, permissions[], groups[] }.
Admin#
Base: /api/v1/admin. Elevated permissions required (tiered admin; formats/quantizations
require super-admin).
| Area | Representative endpoints |
|---|---|
| Stats | GET /admin/stats — dashboard counters |
| Formats | GET/POST /admin/formats, GET/PATCH/DELETE /admin/formats/{id} |
| Quantizations | GET/POST /admin/quantizations, PATCH/DELETE /admin/quantizations/{id} |
| API keys | GET/POST /admin/api-keys, GET/PATCH/DELETE /admin/api-keys/{id} |
| Users | GET /admin/users, GET /admin/users/{id}, GET/POST/DELETE …/{id}/permissions[/…] |
| Groups | GET /admin/groups, GET /admin/groups/{id}, GET …/{id}/members |
Creating an API key (POST /admin/api-keys) returns the plaintext key once.
Key bodies support permission_mode (explicit|inherited|restricted),
linked_user_id, namespace_ids, and expires_in_days (1–365).
Access logs#
GET /api/v1/access-logs — filter by model_id, user_id, action, plus pagination;
ordered newest-first. Non-super-admins see only their own tenant's logs. Each entry:
{ id, model_id, version_id, user_id, tenant_id, action, ip_address, user_agent, created_at, model_name, user_email }.
Files (filesystem backend)#
When the deployment uses filesystem storage, byte transfer happens through short-lived signed URLs (no bearer token; HMAC signature + expiry):
PUT /api/v1/files/part?upload_id=…&part_number=…&expires=…&signature=…— upload a part.GET /api/v1/files/{key}?expires=…&signature=…— download a file.
(The same routes are also mounted under /api/v1/upload.) Invalid/expired signatures
return 403.