REST API + SDKs

Full API reference for Mags.

Submit jobs, poll for status, stream logs, manage workspaces, upload files, and enable URL or SSH access. Every endpoint includes curl, Python, and Node.js examples.

curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "script": "echo hello world",
    "type": "inline"
  }'
from mags import Mags

m = Mags()
result = m.run_and_wait("echo hello world")
print(result["status"])
const Mags = require('@magpiecloud/mags');
const mags = new Mags();

const result = await mags.runAndWait('echo hello world');
console.log(result.status);

Submit a job and get a request_id back in ~50ms.

Authentication

All requests require a Bearer token.

Authorization header

Authorization: Bearer <your-api-token>

Generate tokens at mags.run/tokens or via mags login.

from mags import Mags
m = Mags(api_token="your-token")
# or: export MAGS_API_TOKEN="your-token"
const mags = new Mags({ apiToken: 'your-token' });
// or: export MAGS_API_TOKEN="your-token"
export MAGS_API_TOKEN="your-token"
# Works with CLI, Python SDK, and Node.js SDK

Workflow

Base image + ephemeral workers.

1. Create a base workspace

Install dependencies once and save the workspace. This becomes your reusable base image.

POST /api/v1/mags-jobs
{
  "script": "apk add python3 py3-pip nodejs npm && pip install requests flask",
  "type": "inline",
  "workspace_id": "python-base"
}

2. Spawn ephemeral workers

Use the base as a read-only starting point. Workers run in parallel, base is never modified.

POST /api/v1/mags-jobs
{
  "script": "python3 run.py",
  "type": "inline",
  "base_workspace_id": "python-base"
}

Workspace modes

workspace_id base_workspace_id Behavior
omit omit Fully ephemeral. No S3 sync. Fastest.
"my-ws" omit Local workspace. Data stays on the VM only (not synced to S3). Add persistent: true to sync to S3.
omit "my-base" Base mounted read-only. Changes discarded after run.
"fork-1" "my-base" Fork: starts from base, saves changes to fork-1.

Endpoints

Base URL: https://api.magpiecloud.com/api/v1

Submit Job

POST /api/v1/mags-jobs

Creates a VM, runs a script, and returns. The VM boots in ~300ms from a pool.

Request body

FieldTypeRequiredDescription
script string YES Shell script to execute inside the VM.
type string YES Always "inline".
workspace_id string no Workspace name. With persistent: false, data stays local to the VM only. With persistent: true, filesystem changes are synced to S3 and restored on next run. Omit for ephemeral.
base_workspace_id string no Mount an existing workspace read-only as the starting filesystem.
persistent bool no If true, VM stays alive after script finishes and workspace is synced to S3. Required for cloud persistence.
no_sleep bool no If true, VM will never be auto-slept by the idle timeout. Requires persistent: true.
startup_command string no Command to run when a sleeping persistent VM wakes up.
environment object no Key-value env vars injected into the VM.
file_ids string[] no File IDs from the upload endpoint. Files are downloaded into /root/ before script runs.
disk_gb int no Custom disk size in GB. Default is 2GB. The rootfs is resized on-the-fly after VM boot.

Response (202)

{
  "request_id": "eab1e214-39c8-4ecc-b941-75551976e0fa",
  "status": "accepted",
  "message": "Mags job submitted successfully"
}

SDK examples

curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "script": "echo hello",
    "type": "inline",
    "workspace_id": "myproject"
  }'
job = m.run("echo hello", workspace_id="myproject")
print(job["request_id"])

# Or run and wait for completion:
result = m.run_and_wait("echo hello", workspace_id="myproject")
print(result["status"])  # "completed"
const job = await mags.run('echo hello', { workspaceId: 'myproject' });
console.log(job.requestId);

// Or run and wait:
const result = await mags.runAndWait('echo hello', { workspaceId: 'myproject' });
console.log(result.status); // "completed"

Get Job Status

GET /api/v1/mags-jobs/:id/status

Poll this endpoint to wait for job completion.

Response (200)

{
  "request_id": "eab1e214-...",
  "status": "running",
  "vm_id": "a488ceef",
  "persistent": true,
  "subdomain": "28dfed70c32e",
  "url": "https://28dfed70c32e.apps.magpiecloud.com",
  "sleeping": false,
  "exit_code": 0,
  "script_duration_ms": 1200
}

Status values

StatusDescription
pendingJob queued, waiting for VM.
runningScript is executing.
completedScript finished successfully.
errorScript failed or VM error.
sleepingPersistent VM is idle. Auto-wakes on next request.

SDK examples

curl https://api.magpiecloud.com/api/v1/mags-jobs/$ID/status \
  -H "Authorization: Bearer $TOKEN"
status = m.status("eab1e214-...")
print(status["status"])  # "running"
const status = await mags.status('eab1e214-...');
console.log(status.status); // "running"

Get Job Logs

GET /api/v1/mags-jobs/:id/logs

Response (200)

{
  "logs": [
    { "timestamp": "...", "level": "info", "message": "hello world", "source": "stdout" },
    { "timestamp": "...", "level": "error", "message": "warning: ...", "source": "stderr" }
  ]
}

SDK examples

curl https://api.magpiecloud.com/api/v1/mags-jobs/$ID/logs \
  -H "Authorization: Bearer $TOKEN"
logs = m.logs("eab1e214-...")
for log in logs["logs"]:
    print(f"[{log['source']}] {log['message']}")
const logs = await mags.logs('eab1e214-...');
logs.logs.forEach(l => console.log(`[${l.source}] ${l.message}`));

List Jobs

GET /api/v1/mags-jobs?page=1&page_size=20

Response (200)

{
  "jobs": [{ "request_id": "...", "status": "completed", "workspace_id": "my-project", ... }],
  "total": 42,
  "page": 1,
  "page_size": 20
}

SDK examples

curl "https://api.magpiecloud.com/api/v1/mags-jobs?page=1&page_size=10" \
  -H "Authorization: Bearer $TOKEN"
resp = m.list_jobs(page=1, page_size=10)
for job in resp["jobs"]:
    print(job["request_id"], job["status"])
const resp = await mags.list({ page: 1, pageSize: 10 });
resp.jobs.forEach(j => console.log(j.requestId, j.status));

Stop Job

POST /api/v1/mags-jobs/:id/stop

Stop a running or sleeping job. The VM is terminated and the workspace is synced to S3 if persistent.

Response (200)

{ "success": true, "message": "Job stopped" }

SDK examples

curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs/$ID/stop \
  -H "Authorization: Bearer $TOKEN"
m.stop("myproject")  # accepts name, workspace ID, or request ID
await mags.stop('myproject'); // accepts name, workspace ID, or request ID
mags stop myproject

Enable Access (SSH or HTTP)

POST /api/v1/mags-jobs/:id/access

Enables external access to a running VM. Use port: 22 for SSH, any other port for HTTP proxy.

Request body

{ "port": 22 }

Response — SSH (port 22)

{
  "success": true,
  "ssh_host": "api.magpiecloud.com",
  "ssh_port": 40000,
  "ssh_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
  "access_type": "ssh_proxy"
}

Response — HTTP (other ports)

{
  "success": true,
  "ipv6_address": "2a01:4f9:...",
  "port": 8080,
  "access_type": "http_proxy"
}

SDK examples

curl -X POST https://api.magpiecloud.com/api/v1/mags-jobs/$ID/access \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "port": 22 }'
# SSH access
ssh = m.enable_access(request_id, port=22)
print(f"ssh root@{ssh['ssh_host']} -p {ssh['ssh_port']}")

# HTTP access
access = m.enable_access(request_id, port=8080)
print(access.get("url"))
// SSH access
const ssh = await mags.enableAccess(requestId, 22);
console.log(`ssh root@${ssh.sshHost} -p ${ssh.sshPort}`);

// HTTP access
const access = await mags.enableAccess(requestId, 8080);

Update Job

PATCH /api/v1/mags-jobs/:id

Update a job's settings. All fields are optional — only include the ones you want to change.

Request body

FieldTypeDescription
startup_commandstringCommand to run when VM wakes from sleep
no_sleepboolIf true, VM never auto-sleeps. If false, re-enables auto-sleep. Requires persistent VM.
{ "no_sleep": true }

SDK examples

# Enable no-sleep
curl -X PATCH https://api.magpiecloud.com/api/v1/mags-jobs/$ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "no_sleep": true }'

# Set startup command
curl -X PATCH https://api.magpiecloud.com/api/v1/mags-jobs/$ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "startup_command": "python3 -m http.server 8080" }'
# Enable no-sleep on an existing VM
mags set myvm --no-sleep

# Re-enable auto-sleep
mags set myvm --sleep
# Enable no-sleep
m.update_job(request_id, no_sleep=True)

# Set startup command
m.update_job(request_id, startup_command="python3 -m http.server 8080")
await mags.updateJob(requestId, { noSleep: true });

Upload File

POST /api/v1/mags-files

Upload a file (max 100MB) that can be attached to jobs via file_ids.

Request

Content-Type: multipart/form-data
Field name: file

Response (201)

{
  "file_id": "a1b2c3d4-...",
  "file_name": "script.py",
  "size": 4096
}

SDK examples

curl -X POST https://api.magpiecloud.com/api/v1/mags-files \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@script.py"
file_id = m.upload_file("script.py")
file_ids = m.upload_files(["script.py", "data.csv"])
const fileId = await mags.uploadFile('script.py');

Sync Workspace

POST /api/v1/mags-jobs/:id/sync

Trigger an immediate workspace sync to S3 without stopping the VM.

Response (200)

{ "success": true, "message": "Sync initiated" }

SDK examples

m.sync(request_id)
await mags.sync(requestId);
mags sync myproject

List Workspaces

GET /api/v1/mags-workspaces

List all persistent workspaces for the authenticated user.

Response (200)

{
  "workspaces": [
    { "workspace_id": "myproject", "job_count": 12, "last_used": "2026-02-04T...", "has_active": false }
  ],
  "total": 3
}

SDK examples

curl https://api.magpiecloud.com/api/v1/mags-workspaces \
  -H "Authorization: Bearer $TOKEN"
ws = m.list_workspaces()
for w in ws["workspaces"]:
    print(w["workspace_id"], w["job_count"])
const ws = await mags.listWorkspaces();
ws.workspaces.forEach(w => console.log(w.workspaceId));
mags workspace list

Delete Workspace

DELETE /api/v1/mags-workspaces/:id

Permanently delete a workspace and all its S3 data. Active jobs using this workspace must be stopped first.

Response (200)

{ "success": true, "message": "Workspace 'myproject' deleted" }

Error (409 Conflict)

{ "error": "workspace 'myproject' has 1 active job(s); stop them first" }

SDK examples

m.delete_workspace("myproject")
mags workspace delete myproject
mags workspace delete myproject --force  # skip confirmation
curl -X DELETE https://api.magpiecloud.com/api/v1/mags-workspaces/myproject \
  -H "Authorization: Bearer $TOKEN"

URL Aliases

POST /api/v1/mags-url-aliases
GET /api/v1/mags-url-aliases
DELETE /api/v1/mags-url-aliases/:subdomain

Create stable, human-readable URL aliases for your sandboxes. Aliases point to a workspace and automatically follow the active VM.

Create request body

FieldTypeRequiredDescription
subdomainstringYESSubdomain for the alias (e.g. my-api).
workspace_idstringYESWorkspace to point the alias at.
domainstringnoCustom domain (default: apps.magpiecloud.com).

SDK examples

# Create alias
mags url alias my-api myproject

# List aliases
mags url alias list

# Delete alias
mags url alias remove my-api
m.url_alias_create("my-api", "myproject")
aliases = m.url_alias_list()
m.url_alias_delete("my-api")
await mags.urlAliasCreate('my-api', 'myproject');
const aliases = await mags.urlAliasList();
await mags.urlAliasDelete('my-api');

Cron Jobs

POST /api/v1/mags-cron
GET /api/v1/mags-cron
PATCH /api/v1/mags-cron/:id
DELETE /api/v1/mags-cron/:id

Create, list, update, and delete scheduled cron jobs.

Create request body

FieldTypeRequiredDescription
namestringYESName for the cron job.
cron_expressionstringYESStandard cron expression (e.g. 0 * * * *).
scriptstringYESScript to run on each trigger.
workspace_idstringnoWorkspace to use.
persistentboolnoKeep VM alive after script.

SDK examples

curl -X POST https://api.magpiecloud.com/api/v1/mags-cron \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "nightly-backup",
    "cron_expression": "0 0 * * *",
    "script": "tar czf backup.tar.gz /data",
    "workspace_id": "backups"
  }'
cron = m.cron_create(
    name="nightly-backup",
    cron_expression="0 0 * * *",
    script="tar czf backup.tar.gz /data",
    workspace_id="backups",
)
jobs = m.cron_list()
m.cron_update(cron["id"], enabled=False)
m.cron_delete(cron["id"])
const cron = await mags.cronCreate({
  name: 'nightly-backup',
  cronExpression: '0 0 * * *',
  script: 'tar czf backup.tar.gz /data',
  workspaceId: 'backups',
});
await mags.cronDelete(cron.id);
mags cron add --name "nightly-backup" --schedule "0 0 * * *" -w backups \
  'tar czf backup.tar.gz /data'
mags cron list
mags cron remove <id>

Errors

Error responses.

All errors return:

{ "error": "description of what went wrong" }
StatusMeaning
400Invalid request (missing fields, bad JSON).
401Missing or invalid auth token.
404Job or workspace not found.
409Workspace has active jobs (delete conflict).
500Server error.

VM Environment

What runs inside the VM.

System

OSAlpine Linux (~50MB rootfs)
Shell/bin/sh (ash)
Package managerapk add <package>
Userroot
Working directory/root

Performance

Boot time~300ms from pool
Default timeout300 seconds
FilesystemOverlayFS on base rootfs
Workspace syncS3-backed persistence