# API Reference

> Reference for the current token-protected API routes: user, workspaces, sites, uploads, immutable deployments, rollback, deployment deletion, domains, environment variables, and logs.

Canonical: https://staticx.site/documentation/api

Base URL

## https://staticx.site/api/v1

 Every route requires `Accept: application/json` and a bearer token with the ability and scope needed for that route.

 

 ```
Authorization: Bearer STATICX_API_TOKEN
Accept: application/json
```

  LLM-readable contract

## Give agents the schema before they call the API.

 StaticX publishes a concise LLM index, a full agent context file, and an OpenAPI 3.1 document that mirrors the current `/api/v1` routes. Use these files for AI agents, generated clients, internal tools, and CI automation.

 

 [ TXT **llms.txt** Short index for agents and crawlers. ](https://staticx.site/llms.txt) [ TXT **llms-full.txt** Expanded product, API, MCP, and safety context. ](https://staticx.site/llms-full.txt) [ JSON **openapi.json** OpenAPI 3.1 schema for the public API. ](https://staticx.site/openapi.json) 

 ```
curl https://staticx.site/openapi.json
curl https://staticx.site/llms-full.txt
```

  CLI authentication

## The npm CLI is a thin client over the same API.

 `staticx login` stores the base URL and bearer token locally, then verifies access with `GET /user`. Every later command keeps using the same token scope and route contract shown on this page.

 

 ```
npm install -g staticx

staticx login --base-url "https://staticx.site/api/v1" --token "STATICX_API_TOKEN"
staticx guide
staticx whoami
```

  Token scopes

## Match the token to the job.

 

### Global

Account-wide access for internal tools, dashboards, or broad admin automation.

Common access levels in the UI: **Full access** and **Read only**.

 

### Site-scoped

Limited to one site. Best for CI jobs, deploy bots, and one-off agent runs.

Common access levels in the UI: **Full access** and **Read only**.

 

### Workspace-scoped

Limited to one workspace. Best when one runner needs several sites but should stay inside one team or client boundary.

Common access levels in the UI: **Full access** and **Read only**.

 

 

 The API still returns internal preset ids in `current_token.preset` for debugging. The dashboard shows the user-facing labels **Full access** and **Read only**.

 

  Route index

## Current API contract.

 

### Account and access

 | GET | `/user` | Return the authenticated user for the current token. |
|---|---|---|
| GET | `/workspaces` | List workspaces visible to the token owner. |
| POST | `/workspaces` | Create a workspace. Body: name. |
| GET | `/workspaces/{workspace}` | Read a workspace with members and project count. |
| DELETE | `/workspaces/{workspace}` | Delete a workspace. Body: site\_action=move/delete, target\_workspace\_id when moving. |
| GET | `/domains` | List visible custom domains and generated subdomains. |

 

### Projects

 | GET | `/projects` | List projects visible to the token owner. Optional query: workspace\_id. |
|---|---|---|
| POST | `/projects` | Create a project. Body: workspace\_id, name, description, archive, source\_url, or versioning\_enabled=false. |
| GET | `/projects/{project}` | Read one project, including workspace, host, status, and public URL. |
| PATCH | `/projects/{project}` | Update project metadata. Body: name, description, versioning\_enabled, analytics\_enabled, ga\_measurement\_id (GA4 Measurement ID, e.g. G-XXXXXXXXXX; null/blank disables gtag.js). |
| DELETE | `/projects/{project}` | Delete a project the token owner can write to. |
| POST | `/projects/{project}/domain` | Start manual custom domain setup. Body: domain. Returns the one DNS record to create. |
| GET | `/projects/{project}/domain` | Read custom domain status, DNS instructions, and SSL activation state. |

 

### Files and imports

 | GET | `/projects/{project}/files` | List workspace files. Optional query: path. |
|---|---|---|
| POST | `/projects/{project}/files` | Upload files, queue a ZIP import, or queue URL import. Body depends on mode. |
| GET | `/projects/{project}/imports/status` | Read the latest ZIP or URL import status before publishing. |
| DELETE | `/projects/{project}/files` | Delete one or more workspace files or folders. Body: file\_keys\[\]. |

 

### Deployments

 | GET | `/projects/{project}/deployments` | List release history for the project. Sites with history disabled keep one baseline release. |
|---|---|---|
| POST | `/projects/{project}/deployments` | Publish the current project state. Asynchronous: returns the deployment with status=running; poll the deployment detail endpoint until succeeded or failed. Body may include versioning\_enabled=false to keep one analytics-ready baseline release. Requires a root index.html or index.htm and 404.html. |
| GET | `/projects/{project}/deployments/{deployment}` | Read one published release or baseline release. |
| POST | `/projects/{project}/deployments/{deployment}/rollback` | Activate a previous successful versioned deployment. |
| DELETE | `/projects/{project}/deployments/{deployment}` | Delete one inactive deployment record. Active deployments are protected. |
| DELETE | `/projects/{project}/deployments/delete-selected` | Delete multiple inactive deployments. Body: deployment\_ids\[\]. |

 

### Environment and logs

 | GET | `/projects/{project}/environment-variables` | List project variables. |
|---|---|---|
| POST | `/projects/{project}/environment-variables` | Create one variable. Body: key, value, is\_secret. |
| PUT | `/projects/{project}/environment-variables` | Replace/sync variables. Body: variables\[\]. |
| DELETE | `/projects/{project}/environment-variables/{environmentVariable}` | Delete one variable. |
| GET | `/projects/{project}/logs` | Read recent project activity. |

 

### Indexing

 | GET | `/indexing/balance` | Read the account owner SpeedyIndex balance: tokens, indexer, checker. |
|---|---|---|
| GET | `/indexing/pages` | List account-wide page inventory. Query: engine, status, site, workspace, page. Default engine=google, status=unchecked. status supports requested and not\_requested. |
| POST | `/projects/{project}/indexing/submit` | Submit URLs to SpeedyIndex. Body: engine=google, type=indexer, urls\[\]. Omit urls for the sitemap-diff set. Returns created, submitted, est\_tokens. |
| POST | `/projects/{project}/indexing/check` | Create a checker task. Body: engine=google, urls\[\]. Omit urls to check the whole site inventory. |
| GET | `/projects/{project}/indexing/tasks` | List the site indexing and checker tasks. |
| GET | `/projects/{project}/indexing/tasks/{task}` | Read one task with its per-URL indexing report. |

 

  Indexing errors

## SpeedyIndex error codes.

 Indexing endpoints that call SpeedyIndex return a machine-readable `error_code` in the JSON body alongside the HTTP status, so agents can recover without parsing the message. The SpeedyIndex key is never included in any response.

 

 | error\_code | HTTP | Meaning | How to recover |
|---|---|---|---|
| `no_key` | 401 | No SpeedyIndex key is connected for the account owner. | Connect a key in Settings → Indexing. |
| `insufficient_balance` | 402 | The owner SpeedyIndex balance is too low for the request. | Top up SpeedyIndex credits. |
| `engine_unsupported` | 422 | Invalid engine/type, for example bing + indexer (bing has no indexer). | Use a supported engine and type. indexer = google or yandex; checker = google, yandex, or bing. |
| `rate_limited` | 429 | SpeedyIndex rate limit reached. | Wait briefly, then retry. |
| `server` | 502 | SpeedyIndex returned an upstream error. | Retry later. |

  Current token

## Read the active token context with `/user`.

 

 ```
{
  "data": {
    "id": 18,
    "name": "Amina Hart",
    "email": "ops@example.com",
    "current_token": {
      "id": 44,
      "name": "Morning Peak deploy bot",
      "kind": "project",
      "preset": "project_editor",
      "preset_label": "Full access",
      "scope_type": "project",
      "scope_id": 81,
      "scope_label": "Site · Morning Peak",
      "abilities": [
        "user.read",
        "projects.read",
        "projects.write",
        "files.read",
        "files.write",
        "deploy.read",
        "deploy.run",
        "logs.read"
      ],
      "expires_at": "2026-05-22T10:00:00+00:00"
    }
  }
}
```

  Command mapping

## What the public CLI is expected to call.

 

 | CLI command | API routes | Notes |
|---|---|---|
| `staticx login` | `GET /user` | Stores the token client-side and confirms the current token metadata. |
| `staticx workspaces` | `GET /workspaces` | Best with a Global token because it may cross workspace boundaries. |
| `staticx sites --workspace-id WORKSPACE_ID` | `GET /projects?workspace_id=...` | Best with a Workspace token when the job should stay inside one workspace. |
| `staticx create --workspace-id ... --name "Site"` | `POST /projects` | Creates a site inside the given workspace. |
| `staticx deploy --site-id SITE_ID --dir dist` | `POST /projects/{project}/files`, `POST /projects/{project}/deployments` | Upload, then publish. Add `versioning_enabled=false` when the site should keep one baseline release instead of growing history. |
| `staticx domain --site-id SITE_ID --domain app.example.com` | `POST /projects/{project}/domain` | Start manual custom domain setup and return the DNS record to create. |
| `staticx domain-status --site-id SITE_ID` | `GET /projects/{project}/domain` | Read domain activation progress after the DNS record is created. |
| `staticx logs --site-id SITE_ID` | `GET /projects/{project}/logs` | Use after uploads, builds, and deployments. |

  Custom domain setup

## API custom domains use the one-record DNS flow.

 Send the domain you want, create the DNS record from the response, and StaticX monitors activation. Automatic DNS setup is dashboard-only because the domain owner must approve provider access in a browser.

 

 ```
curl -X POST "https://staticx.site/api/v1/projects/81/domain" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"domain":"app.example.com"}'

curl "https://staticx.site/api/v1/projects/81/domain" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
```

 If an agent or CI job is asked to automate DNS, it should either use this manual DNS endpoint or ask the user to approve DNS Connect from the dashboard. Do not pretend a StaticX token can grant external DNS-provider access automatically.

 

 Generated subdomain suffix changes are dashboard-only. When an account has an active publishing domain, **Site → Settings → Domain** can save a free subdomain on either the StaticX default domain or that active publishing domain. API clients should read the resulting host from `GET /projects/{project}` or `GET /domains`.

 

  Project creation

## Create an empty site, a ZIP-backed site, or a URL import.

 

 Empty project

Create a blank site first, then upload files or deploy content later.

 

 ```
curl -X POST "https://staticx.site/api/v1/projects" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"name":"Marketing Site"}'
```

  Create without release history

Use one baseline release from the start while keeping analytics, forms, and the live URL active.

 

 ```
curl -X POST "https://staticx.site/api/v1/projects" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"name":"Marketing Site","versioning_enabled":false}'
```

  Create from ZIP

Start the site with a ready-built archive when the project already exists locally.

 

 ```
curl -X POST "https://staticx.site/api/v1/projects" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -F "name=Marketing Site" \
  -F "archive=@site.zip"
```

  Create from URL

Queue a remote import when StaticX should mirror a public site for you.

 

 ```
curl -X POST "https://staticx.site/api/v1/projects" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -F "source_url=https://example.com"
```

  

  File upload modes

## Upload, import, and delete workspace files.

 Plan limits are enforced here. Free accounts can upload up to 500 MB and store up to 500 MB total. Plus and Pro accounts can upload up to 50 GB per request with higher storage limits. Unlimited accounts use custom storage capacity.

 

 | Mode | Required fields | Use case |
|---|---|---|
| `files` | `files[]`, optional `path`, optional `overwrite_confirmed` | Upload one or more files into a project path. |
| `zip` | `archive`, optional `path`, optional `overwrite_confirmed` | Queue a built static site import. The ZIP root should contain `index.html` or `index.htm` plus `404.html`. |
| `url` | `source_url`, optional `path` | Queue an import from a public URL. |

 ```
curl -X POST "https://staticx.site/api/v1/projects/{project}/files" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -F "mode=zip" \
  -F "overwrite_confirmed=1" \
  -F "archive=@site.zip"
```

 ```
# ZIP and URL imports are asynchronous. Poll until state is completed before publishing.
curl -X GET "https://staticx.site/api/v1/projects/{project}/imports/status" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
```

 ```
curl -X DELETE "https://staticx.site/api/v1/projects/{project}/files" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"file_keys":["assets/old.xml","assets/unused-folder"]}'
```

 If a request exceeds storage or upload capacity, the API returns `error_code=PLAN_QUOTA_EXCEEDED`. Stop, show the exact message, then either remove storage, delete old form entries, or upgrade.

 

  Deploy

## Upload first, publish second.

 

 ```
# The uploaded ZIP must include index.html or index.htm plus 404.html at the root.
curl -X POST "https://staticx.site/api/v1/projects/{project}/deployments" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
```

 ```
# Disable deployment history for this site and update one baseline release.
curl -X POST "https://staticx.site/api/v1/projects/{project}/deployments" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"versioning_enabled":false}'
```

 ZIP upload/import and deployment creation are both asynchronous. First poll `GET /projects/{project}/imports/status` until the import state is `completed`. Then create the deployment and poll `GET /projects/{project}/deployments/{deployment}` until `status` is `succeeded` or `failed`. If either step fails, stop and return the exact API message plus project logs.

 

  Deployment versioning

## Choose release history or one baseline release.

 By default, every successful publish creates a rollback-ready release. If `versioning_enabled` is false, StaticX keeps one baseline release active so analytics, forms, and the live URL keep working without adding more versions. Rollback only applies when there is previous versioned history to activate.

 

 ```
# Persist the site setting without publishing.
curl -X PATCH "https://staticx.site/api/v1/projects/{project}" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"versioning_enabled":false}'
```

Rollback

 ```
curl -X POST "https://staticx.site/api/v1/projects/{project}/deployments/{deployment}/rollback" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
```

 

Delete one inactive deployment

 ```
curl -X DELETE "https://staticx.site/api/v1/projects/{project}/deployments/{deployment}" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
```

 

Delete selected deployments

 ```
curl -X DELETE "https://staticx.site/api/v1/projects/{project}/deployments/delete-selected" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"deployment_ids":[101,102]}'
```

 

 

 The API rejects deletion of the active deployment. Publish another release or roll back first, then delete inactive versions.

 

  Environment variables

## Store per-project settings for automation.

 

Create one variable

 ```
curl -X POST "https://staticx.site/api/v1/projects/{project}/environment-variables" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"key":"PUBLIC_API_URL","value":"https://api.example.com","is_secret":false}'
```

 

Sync variables

 ```
curl -X PUT "https://staticx.site/api/v1/projects/{project}/environment-variables" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"variables":[{"key":"TOKEN","value":"secret","is_secret":true}]}'
```

 

 

  Responses

## Predictable JSON shapes.

 

Project response

 ```
{
  "data": {
    "id": 18,
    "name": "Marketing Site",
    "description": "Production landing pages",
    "host": "marketing.example.test",
    "public_url": "https://marketing.example.test",
    "status": "active",
    "workspace": {
      "id": 3,
      "name": "Personal Workspace",
      "role": "owner"
    },
    "subdomain": "marketing",
    "custom_domain": null,
    "versioning_enabled": true,
    "analytics_enabled": true,
    "ga_measurement_id": "G-XXXXXXXXXX",
    "deployment_mode": "versioned_history",
    "created_at": "2026-05-11T12:10:44+00:00",
    "updated_at": "2026-05-11T12:10:45+00:00"
  }
}
```

 

Deployment response

 ```
{
  "data": {
    "id": 245,
    "version_name": "Release #245",
    "version_summary": "Versioned release - live now",
    "kind": "deploy",
    "status": "succeeded",
    "message": "Deployment published.",
    "is_active": true,
    "is_versioned": true,
    "versioning_enabled": true,
    "deployment_mode": "versioned_history",
    "file_count": 128,
    "size_bytes": 934122,
    "created_at": "2026-06-02T10:00:00+00:00",
    "activated_at": "2026-06-02T10:00:01+00:00"
  }
}
```

 

Write response

 ```
{
  "message": "Deployment created.",
  "data": {
    "id": 245,
    "kind": "deploy",
    "status": "running"
  }
}
```

Single reads return `{ data }`; writes add a top-level `message`. Collections return `{ data: [...] }` (plus `pagination` when server-paginated). `GET /indexing/pages` is a documented flat exception.

 

Validation error

 ```
{
  "message": "Validation failed",
  "errors": {
    "source_url": [
      "The source url field must be a valid URL."
    ]
  }
}
```

[View on staticx.site](https://staticx.site/documentation/api)
