Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

REST API reference

Complete reference for the ScaiAtlas REST API. All routes are under /api/v1.

Base URL#

text
1
https://api.scaiatlas.io/api/v1

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):

text
1
Authorization: Bearer <token>

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:

json
1
2
3
4
5
6
7
{
  "data": [ /* … */ ],
  "page": 1,
  "page_size": 20,
  "total": 137,
  "total_pages": 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:

json
1
{ "error": { "code": "ATLAS_001", "message": "Human-readable message", "field": "name" } }

See Errors for the full list.

Conventions#

  • A version path segment may be the literal latest to resolve the highest semver.
  • 201 Created for creates, 202 Accepted for async work (variant conversion), 204 No Content for 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):

json
1
{ "name": "poolnoodle", "display_name": "Pool Noodle", "description": "…", "visibility": "tenant" }
  • name — 2–64 chars, ^[a-z0-9][a-z0-9-]*[a-z0-9]$ (required)
  • display_name — ≤255 chars, optional
  • description — optional
  • visibilitypublic|tenant|private (default tenant)

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):

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "name": "noodletron",
  "type": "llm",
  "display_name": "Noodletron",
  "description": "…",
  "license": "apache-2.0",
  "capabilities": { "context_length": 8192 },
  "modalities": { "input": ["text"], "output": ["text"] },
  "metadata": { "params": "8B" },
  "tags": ["demo"]
}
  • 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):

json
1
2
3
4
5
6
7
8
{
  "version": "1.0.0",
  "format": "safetensors",
  "release_notes": "Initial release.",
  "requirements": { "transformers": ">=4.40" },
  "quantization": null,
  "files": [ { "path": "model.safetensors", "size": 1234567, "checksum_sha256": "…" } ]
}
  • 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 — optional
  • files — 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):

json
1
{ "variant_name": "gguf-q4_k_m", "format": "gguf", "quantization": "Q4_K_M" }
  • variant_name — ≤64 chars (required)
  • formatsafetensors|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)
  • /download query: variant (name), file (specific path). Returns { version_id, variant, total_size, file_count, files: [{path, size, checksum_sha256, url}] }.
  • /download/files/{path} query: variant. Honors Range: bytes=start-end (→ 206 Partial Content, Content-Range); sets X-Checksum-SHA256, Accept-Ranges: bytes. 416 for 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.


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.

Updated 2026-07-16 04:44:25 View source (.md) rev 2