AI-authored, sandboxed JS scripts that automate admin work
/admin/api/ai-tasks
List AI Tasks with pagination and free-text search across name and description. Each row carries the most recent run's timestamp and status.
/admin/api/ai-tasks/{id}
Fetch a task with its currently-active version (full source, permissions, and parameter schema) plus a metadata listing of every version.
/admin/api/ai-tasks
Create a task and version 1 atomically. Permissions are validated against the supported catalogue: `admin_api.<resource>.<read|write>` and the literals `file_upload`, `file_download`, `prompt_input`, `llm`.
/admin/api/ai-tasks/{id}/versions
Append a new version. The new version becomes current. Returns a `permissions_diff` highlighting added/removed capabilities so the SPA can prompt for explicit approval before granting new powers.
/admin/api/ai-tasks/{id}
Soft-delete the task. Versions and runs are retained for audit. Pass `include_deleted=true` to the list endpoint to see them.
/admin/api/ai-tasks/{id}/runs
List recent runs for a task, newest first. Defaults to 50 rows, clamped 1–500.
/admin/api/ai-tasks/{id}/runs
Open a new run row. Snapshots the task's current_version_id so a mid-run version edit does not change which code the run is reporting against. Pass `resume_run_id` to resume an earlier `interrupted` run.
/admin/api/ai-tasks/{id}/runs/{run_id}
Fetch a specific run with its full log buffer, latest checkpoint, terminal status, and error message.
/admin/api/ai-tasks/{id}/runs/{run_id}
Apply a batch of run events. Refuses to mutate a run that isn't in `running` state. Each event has a `kind` discriminator; `log`/`progress`/`result`/`admin_api` append to the log, `checkpoint` overwrites the resume blob, `finalize` transitions the run to a terminal status.
/admin/api/ai-tasks/{id}/runs/{run_id}/stream
Server-Sent Events tail of new log lines for a run. Polls every 750ms and emits one `event: event` per appended line. When the run reaches a terminal state, emits a final `event: status` carrying `{status, error}` and closes the stream. Keep-alive comments are sent so proxies do not time out the connection.
/admin/api/ai-tasks
List AI Tasks with pagination and free-text search across name and description. Each row carries the most recent run's timestamp and status.
| Name | Type | Required | Description |
|---|---|---|---|
| search | string | optional | Case-insensitive substring match on name or description. |
| include_deleted | boolean | optional | When true, soft-deleted tasks are included. Default false. |
| limit | integer | optional | Max rows to return. Defaults to 50, clamped 1–200. |
| offset | integer | optional | Number of rows to skip. Default 0. |
{
"tasks": [
{
"id": 4,
"name": "Bulk-import CSV products",
"description": "Reads a CSV upload and creates products via the admin API.",
"last_run_at": "2026-05-02T10:14:00Z",
"last_run_status": "success",
"current_version": 11,
"updated_at": "2026-04-30T08:00:00Z"
},
{
"id": 7,
"name": "Audit empty SKUs",
"description": "Lists every variant whose SKU is blank.",
"last_run_at": null,
"last_run_status": null,
"current_version": 14,
"updated_at": "2026-05-01T09:30:00Z"
}
],
"total": 2
}
curl "https://yourshop.zeroshop.io/admin/api/ai-tasks?limit=50" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/ai-tasks",
params={"limit": 50},
headers={"Authorization": "Bearer zspat_..."}
)
tasks = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks?limit=50", {
headers: { "Authorization": "Bearer zspat_..." }
});
const data = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks?limit=50")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/ai-tasks/{id}
Fetch a task with its currently-active version (full source, permissions, and parameter schema) plus a metadata listing of every version.
{
"task": {
"id": 4,
"name": "Bulk-import CSV products",
"description": "Reads a CSV upload and creates products via the admin API.",
"current_version_id": 11,
"deleted_at": null,
"created_at": "2026-04-15T08:00:00Z",
"updated_at": "2026-04-30T08:00:00Z"
},
"current_version": {
"id": 11,
"version": 3,
"source_code": "// JS code that calls bridge.adminApi(...) etc.",
"permissions": "[\"admin_api.products.write\",\"file_upload\"]",
"parameters": "[{\"name\":\"file\",\"kind\":\"file\"}]",
"approved_by": 1,
"created_at": "2026-04-30T08:00:00Z"
},
"versions": [
{
"id": 11,
"version": 3,
"created_at": "2026-04-30T08:00:00Z",
"approved_by": 1
},
{
"id": 9,
"version": 2,
"created_at": "2026-04-22T09:00:00Z",
"approved_by": 1
},
{
"id": 5,
"version": 1,
"created_at": "2026-04-15T08:00:00Z",
"approved_by": 1
}
]
}
curl "https://yourshop.zeroshop.io/admin/api/ai-tasks/4" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/ai-tasks/4",
headers={"Authorization": "Bearer zspat_..."}
)
detail = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks/4", {
headers: { "Authorization": "Bearer zspat_..." }
});
const detail = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks/4")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
detail = JSON.parse(resp.body)
/admin/api/ai-tasks
Create a task and version 1 atomically. Permissions are validated against the supported catalogue: `admin_api.<resource>.<read|write>` and the literals `file_upload`, `file_download`, `prompt_input`, `llm`.
{
"name": "Bulk-import CSV products",
"description": "Reads a CSV upload and creates products via the admin API.",
"source_code": "export default async function run(ctx) { /* ... */ }",
"permissions": [
"admin_api.products.write",
"file_upload"
],
"parameters": [
{
"name": "file",
"kind": "file"
}
]
}
{
"task_id": 4,
"version_id": 5
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/ai-tasks" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Bulk-import CSV products",
"description": "Reads a CSV upload and creates products via the admin API.",
"source_code": "export default async function run(ctx) { /* ... */ }",
"permissions": ["admin_api.products.write", "file_upload"],
"parameters": [{"name": "file", "kind": "file"}]
}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/ai-tasks",
headers={"Authorization": "Bearer zspat_..."},
json={
"name": "Bulk-import CSV products",
"description": "Reads a CSV upload and creates products.",
"source_code": "export default async function run(ctx) {}",
"permissions": ["admin_api.products.write", "file_upload"],
"parameters": [{"name": "file", "kind": "file"}]
}
)
created = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks", {
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({
name: "Bulk-import CSV products",
description: "Reads a CSV upload and creates products.",
source_code: "export default async function run(ctx) {}",
permissions: ["admin_api.products.write", "file_upload"],
parameters: [{ name: "file", kind: "file" }]
})
});
const created = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = {
name: "Bulk-import CSV products",
description: "Reads a CSV upload and creates products.",
source_code: "export default async function run(ctx) {}",
permissions: ["admin_api.products.write", "file_upload"],
parameters: [{ name: "file", kind: "file" }]
}.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
created = JSON.parse(resp.body)
/admin/api/ai-tasks/{id}/versions
Append a new version. The new version becomes current. Returns a `permissions_diff` highlighting added/removed capabilities so the SPA can prompt for explicit approval before granting new powers.
{
"source_code": "export default async function run(ctx) { /* v2 */ }",
"permissions": [
"admin_api.products.write",
"admin_api.media.write",
"file_upload"
],
"parameters": [
{
"name": "file",
"kind": "file"
}
]
}
{
"version_id": 12,
"permissions_diff": {
"added": [
"admin_api.media.write"
],
"removed": []
}
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/ai-tasks/4/versions" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{
"source_code": "export default async function run(ctx) {}",
"permissions": ["admin_api.products.write", "admin_api.media.write", "file_upload"],
"parameters": [{"name": "file", "kind": "file"}]
}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/ai-tasks/4/versions",
headers={"Authorization": "Bearer zspat_..."},
json={
"source_code": "export default async function run(ctx) {}",
"permissions": ["admin_api.products.write", "admin_api.media.write", "file_upload"],
"parameters": [{"name": "file", "kind": "file"}]
}
)
result = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/versions", {
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({
source_code: "export default async function run(ctx) {}",
permissions: ["admin_api.products.write", "admin_api.media.write", "file_upload"],
parameters: [{ name: "file", kind: "file" }]
})
});
const result = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/versions")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = {
source_code: "export default async function run(ctx) {}",
permissions: ["admin_api.products.write", "admin_api.media.write", "file_upload"],
parameters: [{ name: "file", kind: "file" }]
}.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
result = JSON.parse(resp.body)
/admin/api/ai-tasks/{id}
Soft-delete the task. Versions and runs are retained for audit. Pass `include_deleted=true` to the list endpoint to see them.
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/ai-tasks/4" \
-H "Authorization: Bearer zspat_..."
import requests
requests.delete(
"https://yourshop.zeroshop.io/admin/api/ai-tasks/4",
headers={"Authorization": "Bearer zspat_..."}
)
await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks/4", {
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks/4")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/ai-tasks/{id}/runs
List recent runs for a task, newest first. Defaults to 50 rows, clamped 1–500.
| Name | Type | Required | Description |
|---|---|---|---|
| limit | integer | optional | Max rows to return. Defaults to 50. |
[
{
"id": 99,
"task_id": 4,
"version_id": 11,
"status": "success",
"parameters": "{\"file\":\"products.csv\"}",
"log": "{\"kind\":\"log\",\"line\":\"row 1 ok\"}\n…",
"last_checkpoint": null,
"error_message": null,
"started_at": "2026-05-02T10:14:00Z",
"finished_at": "2026-05-02T10:14:42Z"
},
{
"id": 98,
"task_id": 4,
"version_id": 9,
"status": "interrupted",
"parameters": "{\"file\":\"products.csv\"}",
"log": "…",
"last_checkpoint": "{\"cursor\":42}",
"error_message": null,
"started_at": "2026-04-29T09:00:00Z",
"finished_at": null
}
]
curl "https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs?limit=20" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs",
params={"limit": 20},
headers={"Authorization": "Bearer zspat_..."}
)
runs = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs?limit=20", {
headers: { "Authorization": "Bearer zspat_..." }
});
const runs = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs?limit=20")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
runs = JSON.parse(resp.body)
/admin/api/ai-tasks/{id}/runs
Open a new run row. Snapshots the task's current_version_id so a mid-run version edit does not change which code the run is reporting against. Pass `resume_run_id` to resume an earlier `interrupted` run.
{
"parameters": {
"file": "products.csv"
},
"resume_run_id": null
}
{
"run_id": 99,
"version_id": 11
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"parameters": {"file": "products.csv"}}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs",
headers={"Authorization": "Bearer zspat_..."},
json={"parameters": {"file": "products.csv"}}
)
run = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs", {
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ parameters: { file: "products.csv" } })
});
const run = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { parameters: { file: "products.csv" } }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
run = JSON.parse(resp.body)
/admin/api/ai-tasks/{id}/runs/{run_id}
Fetch a specific run with its full log buffer, latest checkpoint, terminal status, and error message.
{
"id": 99,
"task_id": 4,
"version_id": 11,
"status": "success",
"parameters": "{\"file\":\"products.csv\"}",
"log": "…",
"last_checkpoint": null,
"error_message": null,
"started_at": "2026-05-02T10:14:00Z",
"finished_at": "2026-05-02T10:14:42Z"
}
curl "https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs/99" \
-H "Authorization: Bearer zspat_..."
/admin/api/ai-tasks/{id}/runs/{run_id}
Apply a batch of run events. Refuses to mutate a run that isn't in `running` state. Each event has a `kind` discriminator; `log`/`progress`/`result`/`admin_api` append to the log, `checkpoint` overwrites the resume blob, `finalize` transitions the run to a terminal status.
{
"events": [
{
"kind": "log",
"line": "Importing row 1 of 200"
},
{
"kind": "progress",
"current": 1,
"total": 200,
"label": "Row 1/200"
},
{
"kind": "result",
"status": "success",
"item": "SKU-001",
"details": {
"rows": 1
}
},
{
"kind": "admin_api",
"method": "POST",
"path": "/admin/api/products",
"status": 201
},
{
"kind": "checkpoint",
"state": {
"cursor": 42
}
},
{
"kind": "finalize",
"status": "success",
"error_message": null
}
]
}
curl -X PATCH "https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs/99" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"events":[{"kind":"finalize","status":"success","error_message":null}]}'
import requests
requests.patch(
"https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs/99",
headers={"Authorization": "Bearer zspat_..."},
json={"events": [{"kind": "log", "line": "step 1"}]}
)
await fetch("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs/99", {
method: "PATCH",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ events: [{ kind: "log", line: "step 1" }] })
});
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs/99")
req = Net::HTTP::Patch.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { events: [{ kind: "log", line: "step 1" }] }.to_json
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/ai-tasks/{id}/runs/{run_id}/stream
Server-Sent Events tail of new log lines for a run. Polls every 750ms and emits one `event: event` per appended line. When the run reaches a terminal state, emits a final `event: status` carrying `{status, error}` and closes the stream. Keep-alive comments are sent so proxies do not time out the connection.
"event: event\ndata: {\"kind\":\"log\",\"line\":\"Importing row 1\"}\n\nevent: event\ndata: {\"kind\":\"progress\",\"current\":1,\"total\":200,\"label\":\"Row 1/200\"}\n\nevent: status\ndata: {\"status\":\"success\",\"error\":null}\n"
curl -N "https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs/99/stream" \
-H "Authorization: Bearer zspat_..."
const resp = await fetch(
"https://yourshop.zeroshop.io/admin/api/ai-tasks/4/runs/99/stream",
{ headers: { "Authorization": "Bearer zspat_..." } }
);
const reader = resp.body.getReader();
// ... parse SSE frames from reader
We value your privacy
We use cookies for essential site functionality and, with your consent, analytics to understand how our platform is used. No personal data is shared with third parties. See our Privacy Policy for details.