One Server

Docs · Reference

Management REST API

Everything the console does, over HTTP. The 315 endpoints are read from the router (rest.ts) and the operations (operations.ts) and become this page and the OpenAPI document. This list is generated from the server code, so new features appear here as soon as they exist.

Getting started

Base URL

https://end.blackall.ai/api/v1

Authentication

Every request needs an Authorization: Bearer <token> header. The token is a bwk_… personal token made in the console under Account, API and MCP tokens, or the access token an MCP client received through OAuth.

curl https://end.blackall.ai/api/v1/me \
  -H "Authorization: Bearer $ONEEND_TOKEN"

Scopes and roles

Tokens carry the read and write scopes. Endpoints that only read need read; anything that changes something needs write. A token has its owner’s role and no more, so a write token still gets a 403 where its owner’s role is not enough.

PermissionRoles that hold it
audit.readowner or admin
billing.manageowner or admin or billing
data.writeowner or admin or developer
project.adminowner or admin
secrets.readowner or admin or developer
team.manageowner or admin

Language

Error messages and note follow ?lang=en, ?lang=ja or the Accept-Language header, and are Korean otherwise. The rest of a reply is data and does not change with the language.

Limits

Request bodies are accepted up to 256 KiB by default. Routes that take larger bodies, such as migrations and function deploys, list their own limit. This API has no per-token request rate limit. CORS is open to every origin.

OpenAPI 3.1

The same content as an OpenAPI 3.1 document, for code generators and Postman.

Download openapi.json · https://end.blackall.ai/docs/api/openapi.json

Status codes

A failure answers with one of the status codes below and {"error": {"message": "…"}}. The message is written for a person and says what to do next.

{ "error": { "message": "…" } }
Status codesDescription
200Success. The body is the result as JSON.
400The request is not valid or the operation was refused. error.message says why.
401No token, or the token is not valid or has expired.
402
403The token lacks the scope (read/write) or the role, or the organisation requires two-factor authentication.
404The project or the target does not exist, or this token cannot see it.
409Not possible right now, for example while the project database is still being created.
413The body is larger than this route accepts.
422
429
500The server could not read back the result. Try again shortly.
502

A note, when present, is a sentence saying what to do next.

Account and regions

MethodPathDescription
GET/API version
GET/meMy account
GET/regionsRegions
POST/regions/requestsRequest a region
GET/server-sizesServer sizes and prices
GET/imports/sourcesImport sources
POST/importsStart an import
GET/imports/{id}Import progress

GET /api/v1/

API version Returns the name and version of this API. Useful to check that a token is accepted.

NameDescription
Token scopeNone
RolesAny member of the organisation
ResponseObject: name, version
Status codes200 · 400 · 401
curl "https://end.blackall.ai/api/v1/" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/me

My account Tells you whose token this is and what it may do. operator says whether the operator tools are open to it (active) and, when not, this account's reasons (refused: not_listed, token_bound, no_second_factor). Call it first when you are not sure where to start.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolwhoami
ResponseObject: email, organisation, projects, scopes, zone, read_only, next
Status codes200 · 400 · 401 · 403
curl "https://end.blackall.ai/api/v1/me" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/me', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/regions

Regions Returns the regions. You can create a project now only where can_create is true; where it is false the region is being prepared and is listed, but cannot be chosen yet. A region is chosen once when a project is created and cannot be changed later, so check this before create_project.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_regions
ResponseArray of objects: code, name, country, status, is_default, can_create
Status codes200 · 400 · 401 · 403
curl "https://end.blackall.ai/api/v1/regions" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/regions', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/regions/requests

Request a region Records that you need a region that is not open yet (one where can_create is false). A person from our team will contact you. No date is promised. A region that is already open is not something to request: create the project there with create_project.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolrequest_region
Body limit256 KiB
ResponseObject: region, name, recorded
Status codes200 · 400 · 401 · 403 · 413
NameInTypeRequiredDescription
regionbodystringYesA code that list_regions reported with can_create=false.
notebodystringNoWhat you plan to run there, and why it has to be that region. Optional, but it is the first thing the person who contacts you reads.
curl -X POST "https://end.blackall.ai/api/v1/regions/requests" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"region":"kr"}'
const response = await fetch('https://end.blackall.ai/api/v1/regions/requests', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "region": "kr"
  }),
})
const result = await response.json()

GET /api/v1/server-sizes

Server sizes and prices Each server size (memory) with its Google machine, CPUs and monthly price per region (monthly_minor). With no currency, the organisation's billing currency. KRW is in won with VAT included; USD is in cents with tax added at checkout. A size whose included_in is pro is included in the Pro plan. A region whose region_status is planned is coming soon and cannot be chosen. listed false means the sizes are not on sale yet and sizes is empty.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_server_sizes
ResponseObject: note
Status codes200 · 400 · 401 · 403
NameInTypeRequiredDescription
regionquerystringNoOne region's code (code in list_regions) to see only that region. Empty means every region.
currencyquerystring, KRW | USDNoKRW or USD. Empty means the organisation's billing currency.
curl "https://end.blackall.ai/api/v1/server-sizes?region=kr&currency=KRW" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/server-sizes?region=kr&currency=KRW', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/imports/sources

Import sources The same as Import from another server in the console. For each source account connected to this organisation (Supabase, Vercel, PostgreSQL), its projects, and the imports so far. Accounts are connected in the console (connect_url), so a token is never pasted into a chat.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolimport_sources
ResponseObject: organisation, connect_url, sources, imports
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
orgquerystringNoThe organisation id or name. Empty means the first one this token reaches.
curl "https://end.blackall.ai/api/v1/imports/sources?org=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/imports/sources?org=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/imports

Start an import The same as [Start import] in the console. Starts an import split in the console (import), or a new one from import_sources' connection and project, split one new project per group. New projects are made; the source is never changed. A Supabase source needs its session pooler connection string in address, used for this run only and then deleted. Starting a stopped import again passes over finished destinations and moves only what changed. Owners, admins and developers only.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolstart_import
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
orgquerystringNoThe organisation id or name. Empty means the first one this token reaches.
curl -X POST "https://end.blackall.ai/api/v1/imports?org=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{}'
const response = await fetch('https://end.blackall.ai/api/v1/imports?org=value', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({}),
})
const result = await response.json()

GET /api/v1/imports/{id}

Import progress The same as the import progress page in the console. For each destination the state and counts of each step (schema, sign-in users, data, files, scheduled jobs...), what is not moved and why, and what is worth checking.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_import
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
idpathstringYesThe import id.
orgquerystringNoThe organisation id or name. Empty means the first one this token reaches.
curl "https://end.blackall.ai/api/v1/imports/ID?org=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/imports/ID?org=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Projects

MethodPathDescription
GET/projectsProjects
POST/projectsCreate a project
GET/projects/{ref}One project
PATCH/projects/{ref}Rename
POST/projects/{ref}/pausePause a project
POST/projects/{ref}/resumeResume a project
GET/projects/{ref}/archiveDatabase archive status
POST/projects/{ref}/archive/restoreRestore an archived database
POST/projects/{ref}/archive/downloadArchive download address
DELETE/projects/{ref}Delete a project
POST/projects/{ref}/retry-provisioningRetry a project that failed to set up

GET /api/v1/projects

Projects Returns the projects on this account and the API address of each. status is ready (usable), provisioning (the database is being made, a few seconds), failed (retry_provisioning) or paused.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_projects
ResponseArray of objects: ref, name, organisation, organisation_id, region, api_url, paused, archive_at, archive_purge_at, created_at
Status codes200 · 400 · 401 · 403
curl "https://end.blackall.ai/api/v1/projects" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects

Create a project Creates a new project with a database, sign-in and file storage. Leave the region empty for the default region. The region cannot be changed after the project is created. The price is the same in every region. With several organizations, org chooses which one to create it in (only its owners, admins and developers can, within the number of projects its plan allows). The database is ready a few seconds later: when status is provisioning, pass wait (seconds) or use tables and keys once status in list_projects is ready.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolcreate_project
Body limit256 KiB
ResponseObject: ref, name, organisation, region, api_url, status, created_at
Status codes200 · 400 · 401 · 403 · 404 · 413 · 502
NameInTypeRequiredDescription
waitqueryinteger, min 0, max 120NoSeconds to wait for the database to be ready (status: ready). The default 0 answers at once. It usually takes a few seconds.
namebodystringNoA name for people to read.
regionbodystringNoWhere to put it. list_regions tells you the values you can use. Empty means the default region.
orgbodystringNoThe id or name of the organization to create it in (organisation_id or organisation from list_projects). Empty means the first organization you can create projects in.
waitbodyinteger, min 0, max 120NoSeconds to wait for the database to be ready (status: ready). The default 0 answers at once. It usually takes a few seconds.
tierbodystringNoThe size of a server for this project alone: a value list_server_sizes gave (for example 4gb), or default for the plan's own size. The server is asked for as soon as the project exists. Empty starts it on the shared server.
curl -X POST "https://end.blackall.ai/api/v1/projects?wait=0" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","region":"kr","org":"value","wait":0,"tier":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects?wait=0', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "region": "kr",
    "org": "value",
    "wait": 0,
    "tier": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}

One project Returns the one entry of the project list with this ref.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_projects
ResponseArray of objects: ref, name, organisation, organisation_id, region, api_url, paused, archive_at, archive_purge_at, created_at
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$Yes
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PATCH /api/v1/projects/{ref}

Rename Changes the project name, the organisation name, or both. The identifier (ref) and the addresses do not change. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrename_project
Body limit256 KiB
ResponseObject: ref, name, organisation
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringNoThe new project name.
organisationbodystringNoThe new organisation name.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","organisation":"My team"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "organisation": "My team"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/pause

Pause a project Stops everything the project does: the site, API and functions return 503, realtime connections are refused, and scheduled jobs and new deployments stop. Data and files stay, and resume_project brings it straight back. Paused for over 90 days, the database moves to archive storage (get_project_archive). Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolpause_project
ResponseObject: ref, paused, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/pause" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/pause', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/resume

Resume a project Starts a paused project again, including one the Free plan paused after 3 unused days (a new deployment also starts it). Scheduled jobs do not catch up on missed runs; they run from the next scheduled time.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolresume_project
ResponseObject: ref, paused
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/resume" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/resume', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/archive

Database archive status A database that has been unusable for 90 days (a failed payment, the Free plan, a pause) is moved to archive storage. Returns where this project stands: state (none, scheduled, archiving, archived, restoring, purged), reason, archive_at (when it moves), purge_at (when the archive and the files are deleted, a year after the move), restore_blocked_by.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_project_archive
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/archive" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/archive', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/archive/restore

Restore an archived database Makes the database in archive storage again, exactly as it was archived. Usually a few minutes; a paused project starts again with the restore. Refused while a payment is outstanding or on the Free plan (restore_blocked_by). If the archive was already deleted, creates a new, empty database. Owners and admins only.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrestore_project_archive
ResponseObject: ref, restore, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/archive/restore" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/archive/restore', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/archive/download

Archive download address Creates a signed address the archived database (pg_dump format; restore it with pg_restore) can be downloaded from for an hour. Needs a reason, which is kept in the access records. Owners and admins only.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_archive_download_url
Body limit256 KiB
ResponseObject: ref, url, expires_at, bytes, format
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
reasonbodystring, min length 4, max length 300YesWhy it is downloaded. Kept in the access records.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/archive/download" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"reason":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/archive/download', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "reason": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}

Delete a project Permanently deletes the project's tables, users, files, functions, scheduled jobs, environment variables and domains. This cannot be undone. Use it only when the user clearly asks, and put the project name exactly in confirm. The last remaining project cannot be deleted. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldelete_project
Body limit256 KiB
ResponseObject: ref, deleted
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
confirmbodystringYesThe project name. It must be exactly the name from list_projects.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/retry-provisioning

Retry a project that failed to set up Sets up again a project whose database could not be created. Use it when another tool refuses with "could not create". Do not create a new project: this continues setting up the same one, and a new one only doubles the bill.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolretry_provisioning
ResponseObject: ref, retried, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/retry-provisioning" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/retry-provisioning', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Keys and signing

MethodPathDescription
GET/projects/{ref}/keysGet API keys
GET/projects/{ref}/api-keysNamed API keys
POST/projects/{ref}/api-keysCreate an API key
DELETE/projects/{ref}/api-keys/{id}Revoke an API key
GET/projects/{ref}/signingToken signing
PUT/projects/{ref}/signingChange token signing
POST/projects/{ref}/signing/rotateRotate the key
DELETE/projects/{ref}/signing/keys/{kid}Retire an old key

GET /api/v1/projects/{ref}/keys

Get API keys Returns the project address and its two keys. The publishable key is for browsers. The secret key bypasses every access rule, so use it only on a server. Only a token with write access can call this. When you need values for frontend code, call it with only: "publishable": you get the address and the publishable key only, so the secret key never enters the conversation or the code (a read-only token can do this).

NameDescription
Token scopewrite
Rolessecrets.read owner or admin or developer
MCP toolget_project_keys
ResponseObject: ref, api_url, publishable_key, secret_key, key_version
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
onlyquerystring, publishableNopublishable returns only the address and the publishable key. Empty returns both keys.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/keys?only=publishable" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/keys?only=publishable', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/api-keys

Named API keys The name, kind, prefix, last use and revoked state of each bw_publishable_ and bw_secret_ key made for a purpose. The value of a secret key is never returned. The value of a publishable key is.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_api_keys
ResponseArray of objects: id, name, kind, prefix, publishable_key, created_at, created_by, last_used_at, revoked_at
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/api-keys" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/api-keys', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/api-keys

Create an API key Creates one named key. publishable is for browsers (follows access rules); secret is for servers (bypasses access rules). The value of a secret key is in this response only and cannot be seen again. Only owners and admins can create keys, up to 20 per project.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_api_key
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
kindbodystring, publishable | secretYespublishable for browsers, secret for servers.
namebodystring, min length 1, max length 60YesWhat the key is for. For example: payment server
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/api-keys" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"kind":"publishable","name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/api-keys', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "kind": "publishable",
    "name": "my-name"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/api-keys/{id}

Revoke an API key Revokes one named key. Requests with that key are refused within 30 seconds. Other keys are not affected. This cannot be undone.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrevoke_api_key
ResponseObject: revoked, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_api_keys.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/api-keys/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/api-keys/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/signing

Token signing Shows whether sign-in tokens are signed with HS256 or ES256, and the old keys still accepted for now (keys in their grace period).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_jwt_signing
ResponseObject: ref, alg, keys, grace_hours
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/signing

Change token signing With ES256, new sign-ins are signed with an asymmetric key and other servers can check tokens with the public keys (JWKS). Tokens already issued keep working until they expire. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_jwt_signing
Body limit256 KiB
ResponseObject: ref, alg, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
algbodystring, HS256 | ES256YesHS256 or ES256.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"alg":"HS256"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "alg": "HS256"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/signing/rotate

Rotate the key Creates a new key for signing the publishable and secret keys. The old key is still accepted for grace_hours (0, 1, 24, 168); 0 (or empty) makes it invalid at once (when it leaked). With 0 every app using it stops within 30 seconds, so put the project name (name from list_projects) exactly in confirm. Every key put in an app has to be replaced, so use this only when the user clearly asks. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrotate_signing_key
Body limit256 KiB
ResponseObject: ref, key_version, grace_hours, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
grace_hoursbodyinteger, 0 | 1 | 24 | 168NoHow many hours the old key is still accepted: 0, 1, 24 or 168.
confirmbodystringNoOnly when grace_hours is 0 or empty: the project name. It must be exactly the same as name in list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing/rotate" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"grace_hours":0,"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing/rotate', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "grace_hours": 0,
    "confirm": "my-project"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/signing/keys/{kid}

Retire an old key Ends an old key in its grace period now. kid is one in the keys of get_jwt_signing whose state is rotating. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolretire_signing_key
ResponseObject: ref, kid, retired, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
kidpathstringYesThe kid from get_jwt_signing.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing/keys/KEY_ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/signing/keys/KEY_ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Database

MethodPathDescription
GET/projects/{ref}/advisorsSecurity and performance check
GET/projects/{ref}/tables/{table}/csvExport a table as CSV
POST/projects/{ref}/tables/{table}/csvImport CSV into a table
GET/projects/{ref}/tablesTables
GET/projects/{ref}/policiesAccess rules
GET/projects/{ref}/rows/{table}Read a table
GET/projects/{ref}/migrationsMigration history
POST/projects/{ref}/migrationsApply migrations
DELETE/projects/{ref}/migrations/{version}Remove a migration record
GET/projects/{ref}/schemaThe schema as SQL
GET/projects/{ref}/types/typescriptGenerate TypeScript types
POST/projects/{ref}/sql/queryQuery SQL (read-only)
POST/projects/{ref}/sqlRun SQL (changes)
GET/projects/{ref}/databaseDatabase connection details
POST/projects/{ref}/database/passwordNew database password
GET/projects/{ref}/mysqlList MySQL databases
POST/projects/{ref}/mysqlCreate a MySQL database
PATCH/projects/{ref}/mysql/{name}Connect a MySQL database to an app
DELETE/projects/{ref}/mysql/{name}Delete a MySQL database
GET/projects/{ref}/mysql/{name}/connectionMySQL connection
GET/projects/{ref}/mysql/{name}/backupsList MySQL backups
POST/projects/{ref}/mysql/{name}/backupsBack up MySQL now
POST/projects/{ref}/mysql/{name}/restoreRestore MySQL from a backup
GET/projects/{ref}/extensionsDatabase extensions
PUT/projects/{ref}/extensions/{name}Turn on a database extension
DELETE/projects/{ref}/extensions/{name}Turn off a database extension
GET/projects/{ref}/database/moveMove to a dedicated database: status
POST/projects/{ref}/database/moveMove to a dedicated database
GET/projects/{ref}/database/dedicated-diskDedicated disk
POST/projects/{ref}/database/dedicated-diskMove to a dedicated disk
POST/projects/{ref}/database/dedicated-disk/shrinkMake the disk smaller
GET/projects/{ref}/realtimeTables with realtime on
PUT/projects/{ref}/realtime/{table}Turn realtime on or off for a table

GET /api/v1/projects/{ref}/advisors

Security and performance check Looks for security and performance problems in the project's database structure: tables with RLS off, rules that let anyone write, views that bypass access rules, security definer functions with no fixed search_path, views that expose the users table, foreign keys with no index, tables with no primary key, duplicate indexes, and rules that call uid() for every row. Where there is SQL that fixes an item (remedy), it is included. No data is read. Call this after creating tables or changing rules.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_advisors
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/advisors" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/advisors', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/tables/{table}/csv

Export a table as CSV Returns a table's rows as CSV (the first line holds the column names), as the console table editor's export does. Up to 1MB comes back; past that it is cut at a whole row and truncated is true. Then split the range with filters and fetch it in several calls. An empty field is NULL and "" is an empty string.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolexport_table_csv
ResponseObject: ref, table, rows, bytes, truncated, csv
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tablepathstringYesThe table name (list_tables).
sortquerystringNoOrder by this column. Empty for the table's own order.
descquerybooleanNoWith true, largest first.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/tables/notes/csv?sort=value&desc=true" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/tables/notes/csv?sort=value&desc=true', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/tables/{table}/csv

Import CSV into a table Puts CSV rows into a table, as the console table editor's import does. The first line names the columns, and each goes into the column of the same name (columns changes that). If any row cannot go in, nothing goes in, and the line numbers and reasons come back. Up to 2MB and 10,000 rows. An unquoted empty field is NULL or the column's default. Split anything larger into several calls.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolimport_table_csv
Body limit4096 KiB
ResponseObject: ref, table, inserted, columns
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tablepathstringYesThe table name (list_tables).
csvbodystringYesThe CSV text. The first line holds the column names.
columnsbodyarray<string>NoFor each CSV column, the table column it goes into, or "" to skip it. As many as the first line has. Leave it out to match by name.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/tables/notes/csv" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"csv":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/tables/notes/csv', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "csv": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/tables

Tables Returns the project's tables with their columns, an approximate row count and the state of their access rules. If rules are on and there are none, the publishable key sees nothing.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_tables
ResponseArray of objects: name, approximate_rows, row_level_security, policies, columns
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/tables" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/tables', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/policies

Access rules Returns which action on which table is open to whom.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_policies
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/policies" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/policies', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/rows/{table}

Read a table Reads the first rows of a table as they are. For checking; when you need conditions, use run_sql.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolread_rows
ResponseObject: columns, rows
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tablepathstringYesThe table name.
limitqueryinteger, min 1, max 200NoDefault 50 rows.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rows/notes?limit=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rows/notes?limit=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/migrations

Migration history Returns the version, name and time applied of each migration applied with apply_migrations, oldest first. The record is kept in the project's oneend_migrations table.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_migrations
ResponseObject: ref, ledger, migrations
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/migrations" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/migrations', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/migrations

Apply migrations Runs the migrations not yet applied, in version order, each in a transaction of its own, and records them. Versions already applied are skipped. If one fails, only that one is rolled back and applying stops; the ones before it stay applied. Use this instead of run_sql when changing the schema, and you can apply the same changes to other projects exactly as they are. Files from supabase/migrations can be sent as they are: Supabase names such as public., auth.uid(), storage. and to authenticated are run in this project's names and reported in translated (Supabase name translation). The ledger keeps each file as written.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolapply_migrations
Body limit8192 KiB
ResponseObject: ref, applied, recorded_only, already_applied, drifted
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
migrationsbodyarray<object>, max items 500YesThe migrations, each with version, name and sql.
include_allbodybooleanNoAlso applies versions earlier than the last applied one. False by default.
record_onlybodybooleanNoRecords them as applied without running them. False by default.
dry_runbodybooleanNoOnly tells you what would be applied.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/migrations" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"migrations":[{"version":"20260101000000","name":"my-name","sql":"select 1"}]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/migrations', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "migrations": [
      {
        "version": "20260101000000",
        "name": "my-name",
        "sql": "select 1"
      }
    ]
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/migrations/{version}

Remove a migration record Marks one migration as not applied. Only the record is removed; what the migration made is left as it is.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_migration_record
ResponseObject: ref, version, removed, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
versionpathstring, pattern ^[0-9]{1,32}$YesThe version from list_migrations.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/migrations/20260101000000" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/migrations/20260101000000', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/schema

The schema as SQL Returns the project's schema (types, sequences, tables and constraints, foreign keys, indexes, functions, views, triggers, RLS and rules) as SQL that builds it again. Row data and privileges are not included.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP tooldump_schema
ResponseObject: ref, sql, objects
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/schema" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/schema', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/types/typescript

Generate TypeScript types Makes the Database type for supabase-js's createClient<Database>() from the project's tables, views, functions and enums. Call it again after changing the schema.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolgenerate_typescript_types
ResponseObject: ref, schema, types
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
schemaquerystringNopublic by default. A schema the project made (name from list_schemas) works too.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/types/typescript?schema=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/types/typescript?schema=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/sql/query

Query SQL (read-only) Runs one query (SELECT and the like) on the project's PostgreSQL database in a read-only transaction and returns the rows. A statement that changes data or structure is refused; use run_sql for that. A read-only token can call it. It does not open Vault secrets (decrypted_secret in vault.decrypted_secrets is empty). SQL follows the PostgreSQL 16 documentation (https://www.postgresql.org/docs/16/sql-commands.html).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolquery_sql
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
sqlbodystringYesOne query to run.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/sql/query" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"sql":"select 1"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/sql/query', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "sql": "select 1"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/sql

Run SQL (changes) Runs SQL that changes data or structure (CREATE, INSERT, UPDATE, DELETE and so on) on the project's PostgreSQL database and returns the resulting rows. For reading only, use query_sql. SQL follows the PostgreSQL 16 documentation (https://www.postgresql.org/docs/16/sql-commands.html). A new table gets access rules turned on automatically, so unless you then create rules, the publishable key sees nothing. The role names to use in rules are <ref>_anon, <ref>_authenticated and <ref>_service_role. SQL written for Supabase (public., auth.uid(), storage., to authenticated) is run in this project's names, and translated says what was replaced (Supabase name translation).

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolrun_sql
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
sqlbodystringYesOne SQL statement to run.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/sql" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"sql":"select 1"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/sql', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "sql": "select 1"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/database

Database connection details Shows the addresses for connecting directly to this project's PostgreSQL: direct (5432, session: migration tools and database clients) and pooler (6543, transaction: serverless). The password is not stored, so it appears as [YOUR-PASSWORD].

NameDescription
Token scoperead
Rolessecrets.read owner or admin or developer
MCP toolget_database_connection
ResponseObject: ref, available, host, database, user, password_set, direct, pooler, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/database/password

New database password Creates a new password for direct connections and returns it once. Anything connecting with the old password is refused from its next connection: servers, jobs and tools connected with it now break when they reconnect, so put the project name (name from list_projects) exactly in confirm.This user bypasses row level security, so keep it on servers only. With keep_for_deploys: true the password is also kept sealed, and production servers, middleware and functions get DATABASE_URL, POSTGRES_URL and DIRECT_URL at runtime (never builds). Deployed code can then read every row, like the secret key, so turn it on only when the user wants it. A reset without it stops the handover. Use this only when the user asks. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolreset_database_password
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
keep_for_deploysbodybooleanNotrue: hand it to production deploys as DATABASE_URL (kept sealed). Absent or false: not kept, and any handover stops.
confirmbodystringYesThe project name. It must be exactly the same as name in list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/password" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/password', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/mysql

List MySQL databases Returns this project's MySQL databases (MariaDB 11.4), which app each goes to (app; null is the whole project), their status and size, and how many the plan allows (max). available false means this server has no MySQL yet.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_mysql_databases
ResponseObject: ref, available, max, apps, databases
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/mysql

Create a MySQL database Creates a database and its own login for an app that uses MySQL, such as WordPress. Usually ready within a minute. With app, only that app's deployments get it; without, every app of the project does (while it is the project's only one). That app's servers start with MYSQL_URL, MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE and WORDPRESS_DB_HOST, WORDPRESS_DB_USER, WORDPRESS_DB_PASSWORD, WORDPRESS_DB_NAME (never in a build). Backed up every night.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_mysql_database
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystring, pattern ^[a-z][a-z0-9_]{0,31}$YesStarts with a lowercase letter; lowercase letters, digits and underscores (_), up to 32. For example: wordpress
appbodystringNoThe app that gets this database. Empty for the whole project
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/mysql/{name}

Connect a MySQL database to an app Changes which app gets a MySQL database. app null makes it the whole project's. One app has at most one database. Running servers get the change when they restart or are deployed again.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolbind_mysql_database
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe MySQL database name
appbodystringYesAn app name, or null for the whole project
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"app":"owner/repo"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "app": "owner/repo"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/mysql/{name}

Delete a MySQL database Deletes a MySQL database with its login and every backup. This cannot be undone: use it only when the user clearly asked, and put the database name exactly in confirm. The app that got it no longer gets MYSQL_* variables from its next start. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldelete_mysql_database
Body limit256 KiB
ResponseObject: ref, name, deleted, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe MySQL database name
confirmbodystringYesThe MySQL database name. It must be exactly the same as name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/mysql/{name}/connection

MySQL connection Returns a MySQL database's address, port, database name, login, password and the variables its app gets (variables). The address is reachable only from apps on this server. The password is shown to a write token only.

NameDescription
Token scoperead
Rolessecrets.read owner or admin or developer
MCP toolget_mysql_connection
ResponseObject: ref, name, host, port, database, user, password, url, variables
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe MySQL database name
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/connection" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/connection', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/mysql/{name}/backups

List MySQL backups Returns a MySQL database's backups (nightly, made by hand, and taken just before a restore) and its restores.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_mysql_backups
ResponseObject: ref, name, backups, restores
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe MySQL database name
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/backups" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/backups', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/mysql/{name}/backups

Back up MySQL now Backs up a MySQL database now. Make one before updating plugins or changing a lot at once. Kept for 7 days. The nightly backup happens on its own as well.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_mysql_backup
ResponseObject: ref, name, id, status, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe MySQL database name
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/backups" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/backups', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/mysql/{name}/restore

Restore MySQL from a backup Puts a MySQL database back to when a backup was taken. Every change since is lost, so use it only when the user clearly asked, and put the database name exactly in confirm. The state just before is backed up first, so undo_backup_id restores it again. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrestore_mysql_backup
Body limit256 KiB
ResponseObject: ref, name, restore_id, status, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe MySQL database name
backup_idbodystringYesA backup id from list_mysql_backups
confirmbodystringYesThe MySQL database name. It must be exactly the same as name.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/restore" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"backup_id":"BACKUP_ID","confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/mysql/my-name/restore', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "backup_id": "BACKUP_ID",
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/extensions

Database extensions The PostgreSQL extensions you can turn on (vector from pgvector, postgis, citext, pgcrypto and others), and for each whether this server has it (available), whether it is on (enabled), and its version. Extensions are installed in the extensions schema and used in SQL just as vector(1536) or geometry.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_extensions
ResponseObject: ref, extensions
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/extensions" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/extensions', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/extensions/{name}

Turn on a database extension Turns on one extension (create extension ... with schema extensions). vector for embedding search, postgis for maps and distances. Extensions not on the list are refused. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolenable_extension
ResponseObject: ref, extension
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe extension name: vector, postgis, citext, hstore, uuid-ossp, pgcrypto, ltree, fuzzystrmatch, btree_gin, btree_gist, intarray, tablefunc
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/extensions/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/extensions/my-name', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/extensions/{name}

Turn off a database extension Turns off one extension. Refused while columns, indexes or functions that use it remain. If another project on the same database uses it, it stays installed and only this project's record is removed (note). pg_trgm and unaccent are built in and cannot be turned off. Put the extension name exactly in confirm. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldisable_extension
Body limit256 KiB
ResponseObject: ref, extension
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe extension name, as in list_extensions.
confirmbodystringYesThe extension name. It must be exactly the same as name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/extensions/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/extensions/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/database/move

Move to a dedicated database: status Whether this project is on a dedicated database (dedicated), whether its plan includes one (plan_allows), and whether a move was requested, is under way or failed and stopped (stopped, last_error). When size_bytes is above auto_move_max_bytes the project is not moved automatically; request it with move_to_dedicated_database.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_database_move
ResponseObject: ref
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/move" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/move', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/database/move

Move to a dedicated database Requests the move of a project on the shared database to a database of its own, as the console's "Move now" does. It moves when its turn comes; writes pause during the move and reads carry on. The project as it was just before is kept as a backup, and if the move fails the project stays where it was (a stopped move is tried again when requested again). Needed for Vault and for large databases. Included from the Pro plan. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolmove_to_dedicated_database
ResponseObject: ref, note
Status codes200 · 400 · 401 · 402 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/move" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/move', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/database/dedicated-disk

Dedicated disk The disk and Postgres of this project's own, used once its database has grown: its size (size_gb), what is used, the monthly cost, 30 days of history, a request in progress and the suggestion to make it smaller (shrink). While the project is on the shared disk, the size that moves it (threshold_gb) and the database's size now.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_dedicated_disk
ResponseObject: ref
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/dedicated-disk" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/dedicated-disk', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/database/dedicated-disk

Move to a dedicated disk Requests the move of this project's database to a disk of its own (max(1.2 x the database, 20 GB)), as the console's "Move to a dedicated disk" does. The disk is billed on its provisioned size at KRW 1,200 (75 cents) per GB a month, and while it moves writes fail and reads work (expected_downtime_seconds in get_dedicated_disk). Enterprise may ask at any time; other paid plans once past the threshold. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrequest_dedicated_disk
ResponseObject: ref, note
Status codes200 · 400 · 401 · 402 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/dedicated-disk" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/dedicated-disk', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/database/dedicated-disk/shrink

Make the disk smaller Makes the dedicated disk smaller: a new, smaller disk (1.2 x the database, at least 20 GB) is made, the data moves onto it and the old disk is deleted. Only a disk under 25% used for 7 days can be made smaller (shrink.suggested in get_dedicated_disk). While it moves writes fail and reads work. It is never made smaller on its own. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolshrink_dedicated_disk
ResponseObject: ref, note
Status codes200 · 400 · 401 · 402 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/dedicated-disk/shrink" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/database/dedicated-disk/shrink', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/realtime

Tables with realtime on The tables whose changes are sent in real time. Subscribing to a table not listed here receives nothing.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_realtime_tables
ResponseObject: ref, tables
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/realtime" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/realtime', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/realtime/{table}

Turn realtime on or off for a table Turns change notifications for one table on or off. Notifications also go only for rows that pass the access rules.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_realtime
Body limit256 KiB
ResponseObject: ref, tables
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tablepathstringYesThe table name.
enabledbodybooleanYestrue to turn it on, false to turn it off.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/realtime/notes" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/realtime/notes', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

Auth and users

MethodPathDescription
GET/projects/{ref}/usersThe service's users
POST/projects/{ref}/users/{id}/linksMake a sign-in link
PATCH/projects/{ref}/users/{id}Change a user
POST/projects/{ref}/users/importImport users
POST/projects/{ref}/usersAdd a user
GET/projects/{ref}/auth/providersSocial sign-in settings
PUT/projects/{ref}/auth/providers/{provider}Turn on social sign-in
PATCH/projects/{ref}/auth/providers/{provider}Turn social sign-in off and on
DELETE/projects/{ref}/auth/providers/{provider}Remove social sign-in
GET/projects/{ref}/auth/redirect-urlsAddresses to return to after sign-in
POST/projects/{ref}/auth/redirect-urlsAdd an address to return to
DELETE/projects/{ref}/auth/redirect-urlsRemove an address to return to
GET/projects/{ref}/auth/smsSMS sign-in settings
PUT/projects/{ref}/auth/smsSet up SMS sign-in
DELETE/projects/{ref}/auth/smsRemove SMS sign-in
GET/projects/{ref}/auth/smtpSMTP settings
PUT/projects/{ref}/auth/smtpSet SMTP settings
DELETE/projects/{ref}/auth/smtpRemove SMTP settings
POST/projects/{ref}/auth/smtp/testSend an SMTP test email
GET/projects/{ref}/auth/templatesEmail templates
GET/projects/{ref}/auth/templates/{type}Email templates
PUT/projects/{ref}/auth/templates/{type}Save an email template
DELETE/projects/{ref}/auth/templates/{type}Restore an email template
POST/projects/{ref}/auth/templates/{type}/previewPreview an email template
GET/projects/{ref}/auth/ssoSAML SSO
DELETE/projects/{ref}/auth/sso/{id}Remove SAML SSO
PUT/projects/{ref}/auth/anonymousTurn anonymous sign-in on or off
DELETE/projects/{ref}/users/{id}Remove a user

GET /api/v1/projects/{ref}/users

The service's users The users who signed up to the service that uses this project. They are separate from platform accounts. Up to 200 come at a time, and total gives the full count. When has_more is true there are more, so call again with a higher offset. Using the length of users as the head count is wrong.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_users
ResponseObject: users, total, limit, offset, has_more
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
limitqueryinteger, min 1, max 200NoHow many users to get at a time. Default 50, at most 200.
offsetqueryinteger, min 0NoHow many users to skip. Default 0.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users?limit=1&offset=0" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users?limit=1&offset=0', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/users/{id}

Change a user Changes one of this project's users: the email, the password, whether the email is confirmed, a ban and its end, and the metadata. Only what you give changes. A new password or a ban signs the person out everywhere. A password needs at least 10 characters, and one known from a breach is refused. Metadata is merged into what is there. Use it only when the user clearly asks.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolupdate_user
Body limit256 KiB
ResponseObject: ref, user
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id (UUID) from list_users.
emailbodystringNoThe new email address.
passwordbodystringNoThe new password (at least 10 characters).
email_confirmbodybooleanNoWith true, marks the email address as confirmed.
ban_durationbodystringNoHow long to ban, such as 24h, 30m or 876000h (in effect forever). none lifts a ban.
user_metadatabodyobjectNoValues to merge into the metadata the user may change.
app_metadatabodyobjectNoValues to merge into the metadata only a server changes (a role, a plan and so on).
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"email":"[email protected]","password":"a-long-passphrase","email_confirm":true,"ban_duration":"value","user_metadata":{},"app_metadata":{}}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users/ID', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "email": "[email protected]",
    "password": "a-long-passphrase",
    "email_confirm": true,
    "ban_duration": "value",
    "user_metadata": {},
    "app_metadata": {}
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/users/import

Import users Imports up to 500 users at once from another service (for example a Supabase auth.users export). Ids and bcrypt password hashes are kept, so nobody has to choose a new password. The whole list is checked first: a wrong row is named by number and nothing is imported. An account that already exists is skipped, so repeating a call is safe. dry_run=true only checks.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolimport_users
Body limit256 KiB
ResponseObject: ref, dry_run, found, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
usersbodyarray<object>YesThe accounts: { email, id?, encrypted_password?, email_confirmed?, user_metadata?, app_metadata? }.
dry_runbodybooleanNoCheck the list and import nothing.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users/import" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"users":[{"email":"[email protected]","id":"ID","encrypted_password":"value","email_confirmed":"value","user_metadata":"value","app_metadata":"value"}]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users/import', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "users": [
      {
        "email": "[email protected]",
        "id": "ID",
        "encrypted_password": "value",
        "email_confirmed": "value",
        "user_metadata": "value",
        "app_metadata": "value"
      }
    ]
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/users

Add a user Creates one user in this project's service. The password must be at least 10 characters.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooladd_user
Body limit256 KiB
ResponseObject: added
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
emailbodystringYesThe user's email address.
passwordbodystringYesAt least 10 characters.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"email":"[email protected]","password":"a-long-passphrase"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "email": "[email protected]",
    "password": "a-long-passphrase"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/auth/providers

Social sign-in settings Shows which accounts people can use to sign in to this project's service. Every provider the platform supports is returned, so ones not set up yet show configured: false. Providers you registered yourself (custom:<name>) follow, with a custom entry (addresses, scopes, answer field mapping). callback_url is the address to paste as it is into the provider's console.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_auth_providers
ResponseObject: ref, anonymous_sign_in, providers
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/auth/providers/{provider}

Turn on social sign-in Saves and turns on one provider's client ID and secret. The secret is sealed and cannot be read back; only its last four characters can be checked. First create an app in the provider's console and register the callback_url from list_auth_providers as its redirect address. To change only the ID of a provider already saved, leave client_secret as an empty string. As on Supabase, a provider of custom:<name> registers an OAuth 2.0 or OpenID Connect provider the platform does not list (for example custom:kakao-xts). It then needs issuer (for OpenID Connect this alone fills in the addresses) or authorize_url and token_url (and userinfo_url), and may take scopes and claims (which field of the answer is id, email, email_verified, name, avatar and phone, as dotted paths). Your app signs in with signInWithOAuth({ provider: "custom:<name>" }), and right after sign-in the session carries provider_token, once.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_auth_provider
Body limit256 KiB
ResponseObject: provider, enabled, callback_url
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providerpathstring, pattern ^(google|kakao|naver|apple|github|gitlab|microsoft|facebook|line|discord|x|bitbucket|figma|linkedin_oidc|notion|twitch|slack_oidc|spotify|zoom|azure|keycloak|workos|twitter|slack|custom:[a-z0-9][a-z0-9_-]{0,39})$YesThe sign-in provider: google, kakao, naver, apple, github, gitlab, microsoft, facebook, line, discord, x, bitbucket, figma, linkedin_oidc, notion, twitch, slack_oidc, spotify, zoom, azure, keycloak, workos, twitter, slack, or custom:<name> for one you register yourself (lowercase letters, digits, - and _, up to 40).
client_idbodystringNoThe client ID from the provider's console.
client_secretbodystringNoAn empty string keeps the secret already saved. Required for a new provider.
enabledbodybooleanNoOn by default.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers/google" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"client_id":"CLIENT_ID","client_secret":"CLIENT_SECRET","enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers/google', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "client_id": "CLIENT_ID",
    "client_secret": "CLIENT_SECRET",
    "enabled": true
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/auth/providers/{provider}

Turn social sign-in off and on Turns a provider off or on and keeps its saved credentials. Use it while finding out why a provider misbehaves; turning it back on needs no new secret.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_auth_provider_enabled
Body limit256 KiB
ResponseObject: provider, enabled
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providerpathstring, pattern ^(google|kakao|naver|apple|github|gitlab|microsoft|facebook|line|discord|x|bitbucket|figma|linkedin_oidc|notion|twitch|slack_oidc|spotify|zoom|azure|keycloak|workos|twitter|slack|custom:[a-z0-9][a-z0-9_-]{0,39})$YesThe sign-in provider: google, kakao, naver, apple, github, gitlab, microsoft, facebook, line, discord, x, bitbucket, figma, linkedin_oidc, notion, twitch, slack_oidc, spotify, zoom, azure, keycloak, workos, twitter, slack, or custom:<name> for one you register yourself (lowercase letters, digits, - and _, up to 40).
enabledbodybooleanYestrue to turn it on, false to turn it off.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers/google" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers/google', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/auth/providers/{provider}

Remove social sign-in Removes a provider's settings along with its credentials. To turn it off for a while, use set_auth_provider_enabled. After removing it this way, you have to get the secret again.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_auth_provider
ResponseObject: provider, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providerpathstring, pattern ^(google|kakao|naver|apple|github|gitlab|microsoft|facebook|line|discord|x|bitbucket|figma|linkedin_oidc|notion|twitch|slack_oidc|spotify|zoom|azure|keycloak|workos|twitter|slack|custom:[a-z0-9][a-z0-9_-]{0,39})$YesThe sign-in provider: google, kakao, naver, apple, github, gitlab, microsoft, facebook, line, discord, x, bitbucket, figma, linkedin_oidc, notion, twitch, slack_oidc, spotify, zoom, azure, keycloak, workos, twitter, slack, or custom:<name> for one you register yourself (lowercase letters, digits, - and _, up to 40).
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers/google" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/providers/google', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/auth/redirect-urls

Addresses to return to after sign-in The addresses a person can be sent back to after signing in. Nobody is sent to an address not listed here. If sign-in works but goes nowhere, look here first.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_redirect_urls
ResponseObject: ref, urls
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/redirect-urls" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/redirect-urls', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/auth/redirect-urls

Add an address to return to Adds one address to the allow list. Scheme, host and port must all match and the path is not checked, so adding https://app.example.com/callback stores https://app.example.com. A mobile app’s own scheme is accepted too (co.example.app://callback, stored as co.example.app://callback): reverse-DNS, so it has a dot. The response holds the list as actually stored.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooladd_redirect_url
Body limit256 KiB
ResponseObject: ref, urls
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
urlbodystringYesThe address, for example https://app.example.com.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/redirect-urls" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"url":"https://example.com/hook"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/redirect-urls', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "url": "https://example.com/hook"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/auth/redirect-urls

Remove an address to return to Takes one address off the allow list. Give it exactly as list_redirect_urls returned it.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_redirect_url
Body limit256 KiB
ResponseObject: ref, urls
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
urlbodystringYesThe address, as list_redirect_urls returned it.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/redirect-urls" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"url":"https://example.com/hook"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/redirect-urls', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "url": "https://example.com/hook"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/auth/sms

SMS sign-in settings Shows the SMS account used for phone sign-in (signInWithOtp({ phone })): the provider (platform, solapi, nhn, aligo, twilio), the sender number, the country codes it may send to, the code length and lifetime, and the hourly limit. Credentials show only their last four characters. platform_available has to be true to choose the platform account (₩100 a message, Korea only).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_sms_settings
ResponseObject: ref, configured, provider, enabled, sender, allowed_countries, otp_length, otp_ttl_seconds, hourly_limit, template, key_hint, secret_hint, account_hint, credentials_readable, platform_available, providers, updated_at
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sms" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sms', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/auth/sms

Set up SMS sign-in Saves the SMS account and turns it on. Your own provider needs key, secret and account (they differ by provider: Solapi takes API Key and API Secret, NHN Cloud App Key and Secret Key, Aligo API Key and User ID as account, Twilio and Twilio Verify Account SID as account and Auth Token as secret, MessageBird Access Key as key, Textlocal API Key as key, Vonage API Key as key and API Secret as secret) and a sender registered with it (MessageBird, Textlocal and Vonage also take an alphanumeric sender ID of up to 11 characters; Twilio Verify takes its Verify Service SID, starting VA). Phone confirmations, test numbers and the rest are SMS_* keys of update_auth_config. Credentials left empty keep the stored ones. allowed_countries is only 82 (Korea) by default; adding countries you do not serve exposes you to fraud that makes you text premium-rate numbers abroad.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_sms_settings
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providerbodystring, platform | solapi | nhn | aligo | twilio | messagebird | textlocal | vonage | twilio_verifyYesplatform (the platform account, paid plans only), solapi, nhn, aligo, twilio, messagebird, textlocal, vonage or twilio_verify.
keybodystringNoSolapi API Key, NHN Cloud App Key or Aligo API Key. Empty keeps the stored one.
secretbodystringNoSolapi API Secret, NHN Cloud Secret Key or Twilio Auth Token. Empty keeps the stored one.
accountbodystringNoAligo User ID or Twilio Account SID. Empty keeps the stored one.
senderbodystringNoThe sender number, e.g. 01012345678. For Twilio, a +1 number or a Messaging Service SID starting with MG.
allowed_countriesbodyarray<string>NoCountry calling codes it may send to, e.g. ["82"].
otp_lengthbodyinteger, min 6, max 10NoDigits in a code, 6 to 10. Default 6.
otp_ttl_secondsbodyinteger, min 60, max 3600NoHow long a code is valid, 60 to 3600 seconds. Default 300.
hourly_limitbodyinteger, min 1, max 100000NoMost messages the whole project may send in an hour. Default 200.
templatebodystringNoThe message, containing {{code}}. null for the default wording.
enabledbodybooleanNoOn by default.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sms" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"provider":"platform"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sms', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "provider": "platform"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/auth/sms

Remove SMS sign-in Removes the SMS account settings along with the credentials. Phone sign-in is then refused with phone_provider_disabled. To turn it off for a while, call set_sms_settings with enabled: false.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_sms_settings
ResponseObject: ref, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sms" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sms', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/auth/smtp

SMTP settings Shows the SMTP server and sender this project's auth emails (sign-up confirmation, password reset, sign-in link, email change, invitation) go out through: the same fields as Supabase's SMTP Settings. The password is never shown; password_hint has its last four characters (when it has at least 8). A last_error means that server refused and the email went out from the platform's address instead. configured false means the platform's address is used.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_smtp_settings
ResponseObject: ref, configured, enabled, sender_email, sender_name, host, port, username, password_hint, password_readable, minimum_interval_seconds, rate_limit_per_hour, last_sent_at, last_error, last_error_at, updated_at
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/auth/smtp

Set SMTP settings Sends auth emails through this project's SMTP server, with the sender name and address you choose. The first time it needs sender_email, host, username and password; after that, only what changes (an empty password keeps the stored one). The server has to be on a public address and use TLS (from the start on 465, STARTTLS otherwise). If it refuses, the email goes out from the platform's address and the reason is kept in last_error. Check it with test_smtp_settings after saving.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_smtp_settings
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
sender_emailbodystringNoSender email, e.g. [email protected].
sender_namebodystringNoSender name, e.g. My Shop.
hostbodystringNoThe SMTP host, e.g. smtp.sendgrid.net.
portbodyinteger, 25 | 465 | 587 | 2465 | 2525NoPort: 25, 465, 587, 2465 or 2525. Default 587.
usernamebodystringNoThe SMTP username.
passwordbodystringNoThe SMTP password. Empty keeps the stored one.
minimum_interval_secondsbodyinteger, min 0, max 3600NoMinimum seconds between two emails to the same address. Default 60.
rate_limit_per_hourbodyinteger, min 1, max 10000NoEmails the whole project may send in an hour. Default 100. Past it, requests are refused with over_email_send_rate_limit (429).
enabledbodybooleanNofalse keeps the settings and sends from the platform's address.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"sender_email":"value","sender_name":"value","host":"value","port":25,"username":"value","password":"a-long-passphrase","minimum_interval_seconds":0,"rate_limit_per_hour":1,"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "sender_email": "value",
    "sender_name": "value",
    "host": "value",
    "port": 25,
    "username": "value",
    "password": "a-long-passphrase",
    "minimum_interval_seconds": 0,
    "rate_limit_per_hour": 1,
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/auth/smtp

Remove SMTP settings Removes the SMTP settings along with the password. Auth emails then go out from the platform's address. To pause them, call set_smtp_settings with enabled: false.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_smtp_settings
ResponseObject: ref, removed, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/auth/smtp/test

Send an SMTP test email Sends one test email through the saved SMTP server. It does not fall back to the platform, so a failure gives the server's own reason. Without to, it goes to the caller's address.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooltest_smtp_settings
Body limit256 KiB
ResponseObject: ref, sent, to, sender_email
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tobodystringNoThe address to send to. Leave it out for your own.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp/test" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"to":"https://example.com"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/smtp/test', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "to": "https://example.com"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/auth/templates

Email templates The subject and HTML body of the six auth emails (confirmation, invite, magic_link, recovery, email_change, reauthentication). A type with custom false uses the default wording, in Korean, English or Japanese to match the request. Give type to see just one. The variables are the same as Supabase: {{ .ConfirmationURL }}, {{ .Token }}, {{ .TokenHash }}, {{ .SiteURL }}, {{ .Email }}, {{ .NewEmail }}, {{ .RedirectTo }}, {{ .Data.<key> }}.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_email_templates
ResponseObject: ref, variables, templates
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/auth/templates/{type}

Email templates The subject and HTML body of the six auth emails (confirmation, invite, magic_link, recovery, email_change, reauthentication). A type with custom false uses the default wording, in Korean, English or Japanese to match the request. Give type to see just one. The variables are the same as Supabase: {{ .ConfirmationURL }}, {{ .Token }}, {{ .TokenHash }}, {{ .SiteURL }}, {{ .Email }}, {{ .NewEmail }}, {{ .RedirectTo }}, {{ .Data.<key> }}.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_email_templates
ResponseObject: ref, variables, templates
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
typepathstring, confirmation | invite | magic_link | recovery | email_change | reauthentication | password_changed | email_changed | mfa_enrolled | mfa_removed | identity_linked | identity_unlinkedYesconfirmation, invite, magic_link, recovery, email_change or reauthentication.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/auth/templates/{type}

Save an email template Sets the subject and HTML body of one type. Templates copied from Supabase work as they are. Values are HTML-escaped, and the subject has to be one line. Other syntax such as {{ if }} is refused. reauthentication is stored but not sent yet (reauthenticate is not supported). Check it with preview_email_template before saving.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_email_template
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
typepathstring, confirmation | invite | magic_link | recovery | email_change | reauthentication | password_changed | email_changed | mfa_enrolled | mfa_removed | identity_linked | identity_unlinkedYesconfirmation, invite, magic_link, recovery, email_change or reauthentication.
subjectbodystring, max length 200YesThe subject line, up to 200 characters.
bodybodystring, max length 50000YesThe HTML body, e.g. <a href="{{ .ConfirmationURL }}">Confirm</a>
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"subject":"value","body":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "subject": "value",
    "body": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/auth/templates/{type}

Restore an email template Removes the template of one type, which goes back to the default wording (Korean, English and Japanese).

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_email_template
ResponseObject: ref, type, removed, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
typepathstring, confirmation | invite | magic_link | recovery | email_change | reauthentication | password_changed | email_changed | mfa_enrolled | mfa_removed | identity_linked | identity_unlinkedYesconfirmation, invite, magic_link, recovery, email_change or reauthentication.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/auth/templates/{type}/preview

Preview an email template Returns how the email looks with sample values (subject, HTML and text). With subject and body it shows those without saving; without them, the saved template or the default wording.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolpreview_email_template
Body limit256 KiB
ResponseObject: ref, type, custom, subject, html, text
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
typepathstring, confirmation | invite | magic_link | recovery | email_change | reauthentication | password_changed | email_changed | mfa_enrolled | mfa_removed | identity_linked | identity_unlinkedYesconfirmation, invite, magic_link, recovery, email_change or reauthentication.
subjectbodystringNoA subject to try without saving.
bodybodystringNoA body to try without saving.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation/preview" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"subject":"value","body":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/templates/confirmation/preview', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "subject": "value",
    "body": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/auth/sso

SAML SSO Shows the SAML IdPs registered for this project with their email domains, and the two addresses to enter in the IdP (acs_url, entity_id). The app signs in with supabase.auth.signInWithSSO({ domain }).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_sso_providers
ResponseObject: ref, acs_url, entity_id, metadata_url, new_providers_allowed, providers
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sso" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sso', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/auth/sso/{id}

Remove SAML SSO Removes one IdP. Accounts made through it remain, but can no longer sign in through SSO.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_sso_provider
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_sso_providers.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sso/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/sso/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/auth/anonymous

Turn anonymous sign-in on or off Turns on or off anonymous sign-in, which gives a session straight away without an email. The current state is anonymous_sign_in in list_auth_providers.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_anonymous_sign_in
Body limit256 KiB
ResponseObject: ref, anonymous_sign_in
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanYestrue to turn it on, false to turn it off.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/anonymous" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/auth/anonymous', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/users/{id}

Remove a user Removes one of the project's users. This cannot be undone. id is the value list_users returned. Use it only when the user clearly asks, and put that user's email (or id) exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_user
Body limit256 KiB
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id (UUID) from list_users.
confirmbodystringYesThe email or id of the user to remove. It must match email or id in list_users.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/users/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

Storage

POST /api/v1/projects/{ref}/buckets/{bucket}/empty

Empty a bucket Deletes every file in a bucket and keeps the bucket. It cannot be undone, so use it only when the user clearly asks, and put the bucket name exactly in confirm. removed is the number of files deleted.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolempty_bucket
Body limit256 KiB
ResponseObject: ref, bucket, removed
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
confirmbodystringYesThe bucket name. It must be exactly the same as bucket.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/empty" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/empty', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/buckets/{bucket}/objects

Files Lists one folder of a bucket: its subfolders and files with name, size and type. Choose the folder with prefix (for example avatars/2024/) and find names with search.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_objects
ResponseObject: ref, bucket, prefix, total, objects
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
prefixquerystringNoThe folder to list. Empty for the top.
searchquerystringNoOnly names containing this.
limitqueryinteger, min 1, max 1000No100 by default.
offsetqueryinteger, min 0NoHow many to skip.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/objects?prefix=value&search=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/objects?prefix=value&search=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/buckets/{bucket}/objects

Upload a file Uploads one file to a bucket (up to 2MB). Text goes as it is; for binary set encoding to base64 and put base64 in content. A file with the same name is refused, unless upsert is true, which replaces it. The bucket's size and type limits apply. Upload bigger files through the storage API (/storage/v1).

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolupload_object
Body limit4096 KiB
ResponseObject: ref, bucket, name, size, mime, replaced
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
contentbodystringYesThe file content (base64 when encoding is base64).
namebodystringYesThe path inside the bucket. For example: docs/readme.txt
encodingbodystring, utf-8 | base64Noutf-8 by default.
content_typebodystringNoFor example image/png. Empty means text or application/octet-stream.
upsertbodybooleanNoWith true, a file with the same name is replaced.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/objects" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"content":"value","name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/objects', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "content": "value",
    "name": "my-name"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/buckets/{bucket}/object

Download a file Returns the content of one file (up to 2MB): text as utf-8, binary as base64. For bigger files make an address with create_signed_url.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP tooldownload_object
ResponseObject: ref, bucket, name, size, mime, encoding, content
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
namequerystringNoThe path inside the bucket.
encodingquerystring, utf-8 | base64NoEmpty chooses by the content.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/object?name=my-name&encoding=utf-8" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/object?name=my-name&encoding=utf-8', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/buckets/{bucket}/object

Delete a file Removes one file from a bucket. This cannot be undone.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_object
ResponseObject: ref, bucket, name, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
namequerystringNoThe path inside the bucket.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/object?name=my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/object?name=my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/buckets/{bucket}/upload-urls

Make an upload URL Makes a URL to upload one file larger than 2MB, such as an image or a video. The file itself is not sent through this tool. PUT the file bytes as the body to the returned url, with its type (image/png, video/mp4 and so on) as content-type, as in the curl of example. The URL works for that one name for 2 hours. A file larger than max_bytes, or of a type not in allowed_mime_types, is refused; for a file larger than max_bytes use the resumable (TUS) upload in blackwall://storage. For a public bucket, public_url is its public address once uploaded.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_upload_url
Body limit256 KiB
ResponseObject: ref, bucket, name, method, url, expires_at, max_bytes, allowed_mime_types, public_url, example
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesA bucket name from list_buckets.
namebodystringYesThe path inside the bucket, such as videos/intro.mp4.
upsertbodybooleanNoWhen true, replaces a file with the same name. By default that is refused.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/upload-urls" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/upload-urls', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/buckets/{bucket}/signed-urls

Create a signed URL Makes an address anyone can download one file of a private bucket from for a set time (1 hour by default, 7 days at most). Whoever has the address downloads without signing in, so make only what you need.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_signed_url
Body limit256 KiB
ResponseObject: ref, bucket, name, url, expires_at
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
namebodystringYesThe path inside the bucket.
expires_inbodyinteger, min 1, max 604800NoSeconds it stays open. 3600 by default.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/signed-urls" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value/signed-urls', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/buckets/{bucket}

Change bucket settings Changes whether a bucket is public, the largest one file may be, and the types it takes. Only what you give changes. Making it public lets anyone download its files, so do it only when the user clearly asks. Files already there stay as they are.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolupdate_bucket
Body limit256 KiB
ResponseObject: ref, bucket, public, file_size_limit, allowed_mime_types, files, bytes
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
publicbodybooleanNotrue for public, false for private.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"public":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "public": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/buckets/{bucket}

Delete a bucket Deletes an empty bucket. One that still holds files is refused, so empty it first with empty_bucket. It cannot be undone. Use it only when the user clearly asks, and put the bucket name exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_bucket
Body limit256 KiB
ResponseObject: ref, bucket, deleted
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketpathstringYesThe bucket name, from list_buckets.
confirmbodystringYesThe bucket name. It must be exactly the same as bucket.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets/value', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/buckets

Buckets Returns the storage buckets with the number of files and the size of each.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_buckets
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/buckets

Create a bucket Anyone can read a public bucket. Only the uploader can read a private bucket. file_size_limit and allowed_mime_types set the largest file and the types it takes from the start (later, with update_bucket).

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_bucket
Body limit256 KiB
ResponseObject: bucket, public, file_size_limit, allowed_mime_types
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idbodystring, pattern ^[a-z0-9][a-z0-9._-]{1,62}$YesThe bucket name: 2 to 63 characters of lowercase letters, digits, ". _ -".
publicbodybooleanNoPrivate by default.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"id":"ID"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/buckets', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "id": "ID"
  }),
})
const result = await response.json()

Deployments and repositories

MethodPathDescription
POST/projects/{ref}/deployments/sources/upload-urlsMake an upload URL for a deployment archive
POST/projects/{ref}/deployments/sourcesUpload files to deploy
POST/projects/{ref}/deploymentsCreate a deployment
POST/projects/{ref}/deployments/{id}/redeployRedeploy
POST/projects/{ref}/deployments/{id}/promotePromote a preview to production
POST/projects/{ref}/deployments/{id}/rollbackRoll back production
GET/projects/{ref}/deploy-hooksDeploy hooks
POST/projects/{ref}/deploy-hooksCreate a deploy hook
DELETE/projects/{ref}/deploy-hooks/{id}Remove a deploy hook
GET/projects/{ref}/deployments/deletedRecently deleted deployments
DELETE/projects/{ref}/deployments/{id}Delete a deployment
POST/projects/{ref}/deployments/{id}/restoreRestore a deployment
GET/projects/{ref}/deploymentsDeployments
GET/projects/{ref}/deployments/{id}One deployment and its build log
GET/projects/{ref}/branch-previewsBranch previews
PUT/projects/{ref}/branch-previewsChange branch previews
GET/projects/{ref}/preview-protectionPreview protection
PUT/projects/{ref}/preview-protectionChange preview protection
GET/projects/{ref}/appsConnected repositories and build settings
DELETE/projects/{ref}/apps/{name}Delete an app
PATCH/projects/{ref}/apps/{name}Change build settings
GET/projects/{ref}/envEnvironment variables
POST/projects/{ref}/envSave environment variables
DELETE/projects/{ref}/env/{target}/{key}Remove an environment variable
GET/projects/{ref}/gitGit connection
PUT/projects/{ref}/git/tokens/{provider}Save a repository token
DELETE/projects/{ref}/git/tokens/{provider}Remove a repository token
POST/projects/{ref}/git/reposAdd a repository
DELETE/projects/{ref}/git/github-appDisconnect a GitHub account
DELETE/projects/{ref}/git/github-app/{installation}Disconnect a GitHub account
GET/projects/{ref}/git/importableFind repositories to import
POST/projects/{ref}/git/importImport a repository
PUT/projects/{ref}/git/checks/{repo}/{repo}Run checks on One Server
DELETE/projects/{ref}/git/repos/{name}Disconnect a repository
GET/projects/{ref}/volumesVolumes
POST/projects/{ref}/volumesCreate a volume
PATCH/projects/{ref}/volumes/{id}Grow a volume
DELETE/projects/{ref}/volumes/{id}Delete a volume
POST/projects/{ref}/volumes/{id}/backupsBack up a volume
POST/projects/{ref}/volumes/{id}/restoreRestore a volume
GET/projects/{ref}/server-memoryServer memory
PUT/projects/{ref}/server-memoryChange server memory
GET/projects/{ref}/gpuGPU jobs and servers
POST/projects/{ref}/gpu/jobsCreate a GPU job
GET/projects/{ref}/gpu/jobs/{id}A GPU job
POST/projects/{ref}/gpu/jobs/{id}/cancelStop a GPU job
POST/projects/{ref}/gpu/serversCreate a GPU server
POST/projects/{ref}/gpu/servers/{name}/stopStop a GPU server
DELETE/projects/{ref}/gpu/servers/{name}Delete a GPU server
POST/projects/{ref}/gpu/volumesCreate GPU storage
DELETE/projects/{ref}/gpu/volumes/{name}Delete GPU storage
GET/projects/{ref}/concurrent-buildsConcurrent builds
PUT/projects/{ref}/concurrent-buildsChange concurrent builds

POST /api/v1/projects/{ref}/deployments/sources/upload-urls

Make an upload URL for a deployment archive For deploying a site too large for deploy_files (over 3MB) without a repository: makes a URL to upload one archive (.zip, .tar, .tar.gz) to. The archive itself is not sent through this tool. PUT the archive as the body to the returned url (as in the curl of example, with no token); the answer carries source, and deploy_source with that source deploys it. The URL works for this project for 1 hour and takes up to max_bytes.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_source_upload_url
ResponseObject: ref, method, url, expires_at, max_bytes, formats, example, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/sources/upload-urls" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/sources/upload-urls', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/deployments/sources

Upload files to deploy Send the files to deploy, without a repository, as one archive (.zip, .tar, .tar.gz) as the body. It is kept under the digest of its contents; the deployment is a second call, POST /deployments with {"source": "<digest>"}, so the same bytes can be deployed to a preview and then to production.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
Body limit51200 KiB
ResponseObject: ref, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$Yes
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/sources" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/octet-stream" \
  --data-binary @file
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/sources', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/octet-stream' },
  body: bytes,
})
const result = await response.json()

POST /api/v1/projects/{ref}/deployments

Create a deployment Three kinds, by what the body holds. source is the digest of an archive sent to POST /deployments/sources; files (or files_base64) maps paths to contents; with neither, app, branch and commit build a commit from the repository. target set to production deploys to production, otherwise to a preview.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldeploy_source
Body limit4096 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
sourcebodystring, pattern ^[0-9a-f]{64}$NoThe source returned by the upload (a 64 character SHA-256).
filesbodystringNo
files_base64bodystringNo
appbodystringNoThe app to deploy (a name from list_apps). Leave it out when there is one app.
branchbodystringNoThe name of the preview, which becomes its address. preview by default.
commitbodystringNo
waitbodyinteger, min 0, max 120NoSeconds to wait for the build to finish (ready or error). The default 0 answers at once. If it is not done in time the answer has still_running: true and retry_after_seconds. MCP clients usually give up on a call after about 60 seconds, so around 45 is safe.
block_on_advisorsbodybooleanNoWith true, a production deploy is refused while get_advisors has any critical security problem (advisors.critical). The default false deploys and reports the count and the top 3 in advisors. Previews are never held back.
prebuiltbodybooleanNoWith true, nothing is built and the files are served as they are.
targetbodystring, preview | productionNopreview by default.
messagebodystringNoA note to show in the deployment list (optional).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"source":"export default async () => new Response(\"ok\")","files":"value","files_base64":"value","app":"owner/repo","branch":"main","commit":"0123456789abcdef0123456789abcdef01234567","wait":0,"block_on_advisors":true,"prebuilt":true,"target":"preview","message":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "source": "export default async () => new Response(\"ok\")",
    "files": "value",
    "files_base64": "value",
    "app": "owner/repo",
    "branch": "main",
    "commit": "0123456789abcdef0123456789abcdef01234567",
    "wait": 0,
    "block_on_advisors": true,
    "prebuilt": true,
    "target": "preview",
    "message": "value"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/deployments/{id}/redeploy

Redeploy Builds the same commit as one deployment again, to the same environment. Use it to rerun a build that failed for a reason unrelated to the commit (an environment variable added later, a package registry that was briefly down). Only owners and admins can deploy to production. The answer has the new deployment's addresses (url, preview_url or production_url) and the security check (advisors).

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolredeploy
ResponseObject: ref, id, status, environment, preview_protection, advisors
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe deployment name, starting with dpl-.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/redeploy" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/redeploy', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/deployments/{id}/promote

Promote a preview to production Moves a finished preview deployment to production. The same commit is built again with the production environment variables, and when it finishes the production address points to the result. The preview build is not reused so that preview keys never reach production. Given a finished production deployment, the production address points to it now without a rebuild, and a hold a rollback put on production (production_held) is lifted; give the one serving now to lift the hold alone. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer, project.admin owner or admin
MCP toolpromote_deployment
ResponseObject: ref, id, status, environment, preview_protection, advisors
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe dpl- name of the deployment to promote: usually a preview, or a production deployment to lift a hold.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/promote" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/promote', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/deployments/{id}/rollback

Roll back production Points the production address straight back to a production deployment that served before. Nothing is rebuilt, and the switch takes a few seconds. Custom domains follow. The answer has that deployment's url and production_url. Production is then held (production_held): new production builds are built but not served until you promote one with promote_deployment. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrollback_deployment
ResponseObject: ref, id, status, production_held
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe dpl- name of the production deployment to go back to.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/rollback" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/rollback', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/deploy-hooks

Deploy hooks Shows the deploy hooks and their addresses. A POST to an address builds the latest commit of its branch, so the address itself is a secret. A read-only token sees only the last four characters of each address (secret_hidden); a token with write access sees all of it.

NameDescription
Token scoperead
Rolessecrets.read owner or admin or developer
MCP toollist_deploy_hooks
ResponseObject: ref, hooks
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deploy-hooks" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deploy-hooks', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/deploy-hooks

Create a deploy hook Creates an address that triggers a build without a push. Have a CMS call it when a post is published, or a scheduled job. Only owners and admins can create a hook for the production branch.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_deploy_hook
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
appbodystringYesThe name from list_apps.
branchbodystringYesThe branch whose latest commit is built.
namebodystringNoA name to recognise the hook by.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deploy-hooks" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"app":"owner/repo","branch":"main"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deploy-hooks', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "app": "owner/repo",
    "branch": "main"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/deploy-hooks/{id}

Remove a deploy hook Removes a deploy hook. Whatever called its address no longer causes a deployment.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_deploy_hook
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_deploy_hooks.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deploy-hooks/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deploy-hooks/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/deployments/deleted

Recently deleted deployments Returns the deployments deleted in the last 3 days (by hand or because their retention period ended) and until when each can be restored (restorable_until). Meanwhile their own address answers 410.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_deleted_deployments
ResponseObject: ref, deployments
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/deleted" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/deleted', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/deployments/{id}

Delete a deployment Deletes one deployment. A deployment being served or still building cannot be deleted. It can be restored with restore_deployment for 3 days, then its files and build log are removed. Production deployments take a project admin, the rest a developer or above.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_deployment
ResponseObject: ref, id, deleted, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstring, pattern ^dpl-[0-9a-f]{16}$YesThe deployment id (dpl-…). list_deployments or list_deleted_deployments shows it.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/deployments/{id}/restore

Restore a deployment Restores a deployment deleted in the last 30 days. Its own address answers again, and it can be rolled back or promoted to. Production deployments take a project admin, the rest a developer or above.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolrestore_deployment
ResponseObject: ref, id, restored, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstring, pattern ^dpl-[0-9a-f]{16}$YesThe deployment id (dpl-…). list_deployments or list_deleted_deployments shows it.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/restore" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID/restore', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/deployments

Deployments Shows this project's recent deployments, newest first: status (queued, building, ready, error), environment, addresses, branch, commit and the reason it failed. Deleted deployments are left out (list_deleted_deployments shows them). When next_before is set, pass it as before for the next page. url is the deployment's own dpl- address, production_url the production address, preview_url its branch's preview address. preview_protection says who can open a preview address (members: organization members only, the default / password / off: anyone); null for production, whose address is always public, though with protection on a dpl- address is protected even for a production build.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_deployments
ResponseObject: ref, preview_protection, deployments
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
limitqueryinteger, min 1, max 100NoDefault 20.
beforequerystring, pattern ^dpl-[0-9a-f]{16}$NoStart after this deployment (older ones). next_before from the previous page.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments?limit=1&before=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments?limit=1&before=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/deployments/{id}

One deployment and its build log Shows the status of one deployment, its addresses and the end of its build log. When a build failed, the reason is usually in the last few lines of the log. Environment variable values the customer set are masked as ••••••. url is the deployment's own dpl- address; production_url or preview_url is the production or branch address. preview_protection says who can open a preview address (members: organization members only, the default / password / off: anyone); null for production, whose address is always public. With wait (seconds) it waits for the build to finish.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_deployment
ResponseObject: preview_protection, build_log, build_log_lines
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe deployment name, starting with dpl-.
tailqueryinteger, min 1, max 5000NoHow many lines from the end of the build log. Default 200.
waitqueryinteger, min 0, max 120NoSeconds to wait for the build to finish (ready or error). The default 0 answers at once. If it is not done in time the answer has still_running: true and retry_after_seconds. MCP clients usually give up on a call after about 60 seconds, so around 45 is safe.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID?tail=1&wait=0" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployments/ID?tail=1&wait=0', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/branch-previews

Branch previews Shows whether a push to a branch other than production builds a preview in this project's organization (branch_previews). false means only what reaches the production branch (usually main) is built.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_branch_previews
ResponseObject: ref, organization, branch_previews
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branch-previews" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branch-previews', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/branch-previews

Change branch previews Turns preview builds for branch pushes on or off for the whole organization. false builds only what reaches the production branch (usually main); a skipped push stays in the deployment history as skipped. Redeploys, uploads and deploy hooks still build. Applies to every project in the organization. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolset_branch_previews
Body limit256 KiB
ResponseObject: ref, organization, branch_previews, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanYestrue builds a preview for every branch push.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branch-previews" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branch-previews', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/preview-protection

Preview protection Shows who can open deployments that are not production (branch previews, per-deployment addresses): off, members (organisation members only), password. Also returns which addresses are protected (scope), addresses let in without signing in (trusted_ips), paths that pass unchecked (options_paths), bypass keys (bypass_secrets, only their last four characters) and share links (share_links).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_preview_protection
ResponseObject: ref, mode, has_password
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/preview-protection" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/preview-protection', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/preview-protection

Change preview protection Changes who can open previews. members means organisation members signed in to the console, password means one shared password (a password of at least 8 characters is needed the first time), off means anyone. The production address and connected domains are always public. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_preview_protection
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
modebodystring, off | members | passwordYesoff, members or password.
passwordbodystringNoWhen mode is password. Empty keeps the password already set.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/preview-protection" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"mode":"off"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/preview-protection', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "mode": "off"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/apps

Connected repositories and build settings For each repository that deploys to this project, shows the production branch, the folder to build (root_directory), the framework preset (framework; null means detect automatically) and the build command, output directory and install command overrides (build_command, output_directory, install_command; null means the preset default). detected_framework is what the latest build used, and frameworks lists the presets you can choose. A repository appears on its first push.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_apps
ResponseObject: ref, apps, frameworks
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/apps" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/apps', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/apps/{name}

Delete an app Deletes one app of the project (an app whose folder a monorepo removed). Its addresses stop answering at once, its builds in progress are cancelled, and its deployments are deleted with no restore, their files gone within minutes. A push no longer builds it even if the folder is still in the repository. It cannot be undone. The project's own site, an app with domains and an app with disks are refused, with what to do first. Use it only when the user clearly asks, and put the app name exactly in confirm. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldelete_app
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe name from list_apps.
confirmbodystringYesThe app name. It must be exactly the same as name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/apps/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/apps/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/apps/{name}

Change build settings Changes one repository's production branch, folder to build, framework preset, build command, output directory or install command. For a monorepo, put the app folder (for example apps/web) in root_directory. Values you leave out stay as they are; null puts one back to detect automatically (the preset default). An empty command skips that step. If the repository's blackwall.json sets the same item, its value comes first. Takes effect from the next deployment, except port (the port the server listens on), which restarts the server within a minute. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolupdate_app
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe name from list_apps.
production_branchbodystringNoThe branch that deploys to production.
root_directorybodystringNoA path relative to the repository. "." for the whole repository.
run_asbodystring, platform | imageNoWho the server runs as. platform (the default) is an ordinary user (10001); image is the user the image names, root included, inside a gVisor sandbox. Use image only for Dockerfile apps that must start as root, like the official WordPress image. The server restarts within a minute, without a rebuild.
portbodystring, min 1, max 65535NoThe port the server listens on inside its container. null for the image's EXPOSE, or 3000. A change restarts the server within a minute, with no new deployment.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/apps/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"production_branch":"main","root_directory":".","run_as":"platform","port":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/apps/my-name', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "production_branch": "main",
    "root_directory": ".",
    "run_as": "platform",
    "port": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/env

Environment variables The environment variables a deployed app gets when it is built and run. The same name can exist separately for production and preview. A sensitive value is not returned; only its last four characters come as value_hint. readable false means the value cannot be opened and is left out of builds, so save it again. automatic lists the automatic variables that are set without being saved: SUPABASE_URL, NEXT_PUBLIC_SUPABASE_URL, SUPABASE_ANON_KEY, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY and SUPABASE_SERVICE_ROLE_KEY, the names an app moved from Supabase already reads. available_in says where each is available: the public ones in build and runtime (build, runtime), SUPABASE_SERVICE_ROLE_KEY in runtime only (runtime: servers, middleware, functions), never in builds. A variable saved under the same name comes first, and a saved value goes to builds too (overridden_in).

NameDescription
Token scoperead
Rolessecrets.read owner or admin or developer
MCP toollist_env_vars
ResponseObject: ref, automatic, variables, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/env" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/env', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/env

Save environment variables Saves one or more environment variables. Give them one by one in variables, or the whole content of a .env file in dotenv. A name that exists is overwritten. If any one is invalid, nothing is saved, except that a dotenv line with a name the platform sets is skipped and listed in skipped. PORT, HOST, HOSTNAME, HOME and names starting with BLACKWALL_ are used by the platform and cannot be set. Builds get CI=1 unless you set CI yourself (CI=false, for example). Changed values are used from the next deployment. Without sensitive, a name that looks like a secret (API_KEY, STRIPE_SECRET, DATABASE_URL and the like) is saved as sensitive and cannot be read back (sensitive_keys). Only owners and admins can change production values.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_env_vars
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
variablesbodyarray<object>NoA list of { key, value }.
dotenvbodystringNoThe content of a .env file. Comments and blank lines are skipped.
targetsbodyarray<string>, production | preview | development, min items 1Yespreview means the deployments of every branch that is not the production branch.
sensitivebodybooleanNotrue: the value cannot be read again after it is saved. Not given: only a variable whose name contains SECRET, TOKEN, PASSWORD, PRIVATE, DSN, DATABASE_URL or KEY_, or ends in _KEY, is saved as sensitive. false: every value stays readable.
appbodystringNoThe app name (name from list_apps) to save them for that app only, over a project variable of the same name. Empty means every app of the project.
branchbodystring, max length 200NoA git branch name, to save them for that branch's previews only. targets must be preview alone. With app, they go to that app on that branch only. Empty means every preview.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/env" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"targets":["production"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/env', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "targets": [
      "production"
    ]
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/env/{target}/{key}

Remove an environment variable Removes one environment variable from either production or preview. If the value was sensitive, it cannot be recovered.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_env_var
ResponseObject: key, target, app, branch, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
targetpathstring, production | preview | developmentYesproduction or preview.
keypathstringYesThe variable name.
appquerystringNoThe app name, for a variable saved for one app only. Empty means the project's variable.
branchquerystring, max length 200NoThe git branch, for a variable saved for one branch's previews only (branch in list_env_vars).
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/env/production/API_URL?app=owner%2Frepo&branch=main" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/env/production/API_URL?app=owner%2Frepo&branch=main', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/git

Git connection The same as "Git connection" in the console. Shows the repositories this project deploys (connected: whether a push deploys it), the GitHub accounts linked (github_accounts) how private repositories are fetched (the last four characters of a GitHub or GitLab token, a GitHub App installation), and for each GitHub repository whether it runs its checks on One Server (checks_on_server: available, enabled, state, permission_url, workflows_using). If a private repository build fails at the fetch step, look here first.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_git_access
ResponseObject: ref, access, repositories
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/git/tokens/{provider}

Save a repository token Saves an access token used to fetch private repositories (read access is enough). It is sealed and never shown again. For a self-hosted GitLab, put https://gitlab.example.com in gitlab_base. Used from the next build. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_repo_token
Body limit256 KiB
ResponseObject: ref, provider, saved, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providerpathstring, github | gitlab | bitbucketYesgithub or gitlab.
tokenbodystringYesThe access token. Read access is enough.
gitlab_basebodystringNoThe address of a self-hosted GitLab. Empty for gitlab.com.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/tokens/github" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"token":"ghp_xxxxxxxxxxxxxxxxxxxx"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/tokens/github', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "token": "ghp_xxxxxxxxxxxxxxxxxxxx"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/git/tokens/{provider}

Remove a repository token Removes the saved token. A connected GitHub App installation is kept. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolremove_repo_token
ResponseObject: ref, provider, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providerpathstring, github | gitlab | bitbucketYesgithub or gitlab.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/tokens/github" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/tokens/github', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/git/repos

Add a repository Attaches a repository to this project before its first push. Pushes that come through the GitHub App then come to this project, and create_deploy_hook can build without a push. repo is owner/name for GitHub and group/name for GitLab. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooladd_repository
Body limit256 KiB
ResponseObject: ref, access, repositories
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providerbodystring, github | gitlab | bitbucketYesgithub or gitlab.
repobodystringYesowner/name for GitHub, group/name for GitLab.
branchbodystringNoThe production branch. Empty means main.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/repos" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"provider":"github","repo":"owner/repo"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/repos', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "provider": "github",
    "repo": "owner/repo"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/git/github-app

Disconnect a GitHub account Disconnects a GitHub account (a GitHub App installation) from this project: the one named by installation, or every one when it is empty. Saved tokens and the App installation on GitHub are kept. Connecting needs a person to confirm it on GitHub, so it can only be done in the console. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolunlink_github_app
ResponseObject: ref, unlinked
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/github-app" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/github-app', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/git/github-app/{installation}

Disconnect a GitHub account Disconnects a GitHub account (a GitHub App installation) from this project: the one named by installation, or every one when it is empty. Saved tokens and the App installation on GitHub are kept. Connecting needs a person to confirm it on GitHub, so it can only be done in the console. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolunlink_github_app
ResponseObject: ref, unlinked
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
installationpathintegerYesgithub_accounts[].installation_id from list_git_access. Empty means all.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/github-app/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/github-app/value', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/git/importable

Find repositories to import The same as the import list under Git connection in the console. Shows up to 50 repositories the GitHub App can see on a linked GitHub account, most recently pushed first. With query it searches all of them, also past 300. If you just added a repository on GitHub, pass refresh: true. With several accounts, choose one with installation (github_accounts in list_git_access). Only owners and admins can see it.

NameDescription
Token scoperead
Rolesproject.admin owner or admin
MCP toollist_importable_repositories
ResponseObject: ref, github_account, installation_id, other_accounts, repositories, shown, matched, total, searched, add_repositories_url, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
queryquerystringNoPart of a repository name. Empty lists the most recently pushed.
installationqueryintegerNoWhich GitHub account's installation. Empty means the default account.
refreshquerybooleanNoAsk GitHub again, past its one-minute cache.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/importable?query=value&installation=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/importable?query=value&installation=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/git/import

Import a repository The same as [Connect and deploy] in the console. Connects a repository the GitHub App can see to this project and starts the first deployment from the newest commit of its production branch. An empty branch means the repository's default branch. Build settings (framework and the rest) and environment variables (a .env text in dotenv) are saved before the first build. Importing a disconnected repository again connects it again with its settings. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolimport_repository
Body limit256 KiB
ResponseObject: ref, app, repo, branch, github_account, deployment, status, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
repobodystringNoowner/name, as list_importable_repositories gives it.
branchbodystringNoThe production branch. Empty means the repository's default branch.
root_directorybodystringNoThe app folder of a monorepo (for example apps/web). Empty means the top of the repository. Another folder of a repository already connected adds a new app.
installationbodyintegerNoWhich GitHub account's installation fetches it. Empty means the account that owns the repository.
dotenvbodystringNoThe contents of a .env file. Saved for production and previews.
sensitivebodybooleanNoThe dotenv values cannot be viewed again once saved.
appbodystringNoThe app name ("App name" in the console). Empty means the folder name, or the repository name. An app of that name has its settings changed.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/import" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"repo":"owner/repo","branch":"main","root_directory":".","installation":1,"dotenv":"API_URL=https://example.com","sensitive":true,"app":"owner/repo"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/import', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "repo": "owner/repo",
    "branch": "main",
    "root_directory": ".",
    "installation": 1,
    "dotenv": "API_URL=https://example.com",
    "sensitive": true,
    "app": "owner/repo"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/git/checks/{repo}/{repo}

Run checks on One Server Turns on or off running a connected GitHub repository's GitHub Actions checks on One Server's machines, as [Run checks on One Server] under Git connection in the console does. On saves the repository variable POOL_RUNNER: a job that says runs-on: ${{ vars.POOL_RUNNER || 'ubuntu-latest' }} runs on One Server. Off deletes it, and jobs go back to GitHub's machines. When state is needs_permission, approve the GitHub App's new permissions at permission_url and it turns on by itself. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_checks_on_server
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
repopathstringYesrepositories[].repo (owner/name) from list_git_access.
repopathstringYesrepositories[].repo (owner/name) from list_git_access.
enabledbodybooleanYestrue turns it on, false turns it off.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/checks/owner%2Frepo/owner%2Frepo" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/checks/owner%2Frepo/owner%2Frepo', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/git/repos/{name}

Disconnect a repository The same as [Disconnect] on a repository under Git connection in the console. Pushes to this repository no longer deploy. The deployment serving now, the history, build settings and environment variables stay, and import_repository with the same repository connects it again. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldisconnect_repository
ResponseObject: ref, name, repo, connected, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesrepositories[].name from list_git_access.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/repos/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/git/repos/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/volumes

Volumes Returns the app volumes (disk that stays when a server restarts or a new deployment goes out) and their backups, how much the plan allows in total (max_gb) and how much is used (used_gb), and the port each app's server listens on (apps[].listens_on). Volumes are mounted on production servers only.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_volumes
ResponseObject: ref, max_gb, used_gb, volumes
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/volumes

Create a volume Adds a volume to an app's production servers, mounted read-write at mount_path in the container; its size is a hard cap. For WordPress use /var/www/html. System paths such as /etc, /usr, /proc and /tmp are refused. Ready within a minute; the servers restart with it. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_volume
Body limit256 KiB
ResponseObject: ref, volume, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
appbodystringYesThe name from list_apps.
mount_pathbodystringYesAn absolute path inside the container, for example /var/www/html.
size_gbbodyinteger, min 1, max 500NoSize in GB, 1 to 500. 1 by default.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"app":"owner/repo","mount_path":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "app": "owner/repo",
    "mount_path": "value"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/volumes/{id}

Grow a volume Makes a volume larger without stopping the server. A volume cannot shrink. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolresize_volume
Body limit256 KiB
ResponseObject: ref, volume, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe volume id from list_volumes.
size_gbbodyinteger, min 1, max 500YesSize in GB, 1 to 500.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"size_gb":1}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "size_gb": 1
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/volumes/{id}

Delete a volume Deletes a volume, every file on it and its backups. Production servers restart without it. This cannot be undone, so use it only when the user clearly asks, and put the volume path (mount_path) exactly in confirm. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldelete_volume
Body limit256 KiB
ResponseObject: ref, volume, deleted, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe volume id from list_volumes.
confirmbodystringYesThe volume path. Must equal mount_path from list_volumes exactly.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/volumes/{id}/backups

Back up a volume Packs the volume's files now and keeps them for 30 days. Finishes within a few minutes and appears in list_volumes under backups. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_volume_backup
ResponseObject: ref, volume_id, backup, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe volume id from list_volumes.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID/backups" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID/backups', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/volumes/{id}/restore

Restore a volume Replaces the volume's current files with a backup's. Files changed after the backup are lost, but the current files are backed up first and kept for 7 days, so that backup undoes it. Use it only when the user asks. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrestore_volume_backup
Body limit256 KiB
ResponseObject: ref, volume, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe volume id from list_volumes.
backup_idbodystringYesA backups[].id from list_volumes whose status is ready.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID/restore" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"backup_id":"BACKUP_ID"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/volumes/ID/restore', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "backup_id": "BACKUP_ID"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/server-memory

Server memory The memory (MB) and CPUs this project's server deployments run with, the most its plan allows, the price per GB-hour and what a server held all month costs. Servers at production and branch addresses always run, requests or not (minimum one instance), so that is what one of them costs a month.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_server_memory
ResponseObject: ref, server_memory_mb, cpus, max_server_memory_mb, sizes
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server-memory" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server-memory', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/server-memory

Change server memory Chooses the memory this project's server deployments (Node servers, workers) run with: 512, 1024, 2048 or 4096 MB. Above 1024 MB they get two CPUs. Metered as runtime_gb_hour. Servers restart with the new size within a minute. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_server_memory
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
memory_mbbodyinteger, 512 | 1024 | 2048 | 4096YesMemory in MB: 512, 1024, 2048 or 4096.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server-memory" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"memory_mb":512}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server-memory', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "memory_mb": 512
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/gpu

GPU jobs and servers This project's GPU jobs (the latest 50), GPU servers and GPU storage, the hourly price of each card and the price of a GB-month of storage, the regions and data centers to choose from, and how many GPUs the organisation may run at once and runs now. The free plan has no GPUs.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_gpu
ResponseObject: ref, prices, max_gpus, in_use, jobs, servers, volumes, volume_price, max_volume_gb, volume_gb, regions, datacenters
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/gpu/jobs

Create a GPU job Rents a GPU and runs a command in a public container image to its end (training, a batch). The GPU is let go when the command ends or max_minutes passes, and the seconds used are billed at the card's hourly price. Results last only if the job uploads them somewhere, such as the project's storage. The image needs curl, wget or python3 to report its start and end. Pulling the image is GPU time too: it is billed and counts toward max_minutes.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_gpu_job
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
gpubodystring, rtx4090 | l40s | a100 | h100Yesrtx4090, l40s, a100 or h100
imagebodystringYesA public image, e.g. pytorch/pytorch:2.4.0-cuda12.1-cudnn9-runtime
commandbodystringYesRun with sh
namebodystringNoA name
envbodyobjectNoEnvironment variables, kept sealed
max_minutesbodyinteger, min 1, max 10080No60 by default
regionbodystring, auto | asia | us | euNoWhere to rent: auto (any data center with a free card, the default), asia (Japan, India), us or eu. With a volume, the GPU is rented in its data center.
volumebodystringNoThe GPU storage to mount at /workspace, by name (made with create_gpu_volume)
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/jobs" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"gpu":"rtx4090","image":"value","command":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/jobs', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "gpu": "rtx4090",
    "image": "value",
    "command": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/gpu/jobs/{id}

A GPU job One GPU job's status, the time and money it used, its exit code and the end of its output (up to 64KB).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_gpu_job
ResponseObject: id, name, gpu, image, command, max_minutes, status, created_at, started_at, ended_at, seconds, cost_minor, exit_code, error, region, volume
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe job id
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/jobs/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/jobs/ID', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/gpu/jobs/{id}/cancel

Stop a GPU job Stops a GPU job. One not started yet never starts; a running one lets its GPU go. The time until the GPU is let go is billed.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcancel_gpu_job
ResponseObject: id, name, gpu, image, command, max_minutes, status, created_at, started_at, ended_at, seconds, cost_minor, exit_code, error, region, volume
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe job id
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/jobs/ID/cancel" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/jobs/ID/cancel', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/gpu/servers

Create a GPU server Creates a GPU server that hosts a model from a public image. Called with the secret key at https://<ref>.<zone>/gpu/v1/servers/<name>/, it starts when stopped (answering 503 meanwhile) and passes the request on. It stops after idle_minutes without a request (0 keeps it up). Billed by the second from when it starts turning on until it is off, image download included. The server receives the x-oneend-gpu-token header, the same value as ONEEND_GPU_TOKEN.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_gpu_server
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringYesLowercase letters, digits and hyphens
gpubodystring, rtx4090 | l40s | a100 | h100Yesrtx4090, l40s, a100 or h100
imagebodystringYesA public image, e.g. vllm/vllm-openai:latest
commandbodystringNoEmpty runs the image's own
envbodyobjectNoEnvironment variables, kept sealed
portbodyinteger, min 1, max 65535No8000 by default
idle_minutesbodyinteger, min 0, max 1440No15 by default, 0 keeps it up
regionbodystring, auto | asia | us | euNoWhere to rent: auto (any data center with a free card, the default), asia (Japan, India), us or eu. With a volume, the GPU is rented in its data center.
volumebodystringNoThe GPU storage to mount at /workspace, by name (made with create_gpu_volume)
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/servers" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","gpu":"rtx4090","image":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/servers', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "gpu": "rtx4090",
    "image": "value"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/gpu/servers/{name}/stop

Stop a GPU server Stops a GPU server now. The time until it is off is billed. The next request starts it again.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolstop_gpu_server
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe server name
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/servers/my-name/stop" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/servers/my-name/stop', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/gpu/servers/{name}

Delete a GPU server Deletes a GPU server, stopping it first when it runs. This cannot be undone, so put the GPU server name exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_gpu_server
Body limit256 KiB
ResponseObject: ref, name, deleted
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe server name
confirmbodystringYesThe GPU server name. It must be exactly the same as name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/servers/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/servers/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/gpu/volumes

Create GPU storage Creates GPU storage in one data center to keep models and data. Name it as volume on a job or server and it is mounted at /workspace, so nothing has to be downloaded again. Billed by the GB-month for as long as it exists, used or not. Jobs and servers that use it rent their GPU in its data center only. Not on the free plan.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_gpu_volume
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringYesLowercase letters, digits and hyphens
size_gbbodyinteger, min 10, max 4000No50 GB by default
datacenterbodystringYesA data center, e.g. AP-JP-1 (Japan); list_gpu lists them
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/volumes" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","datacenter":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/volumes', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "datacenter": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/gpu/volumes/{name}

Delete GPU storage Deletes GPU storage and every file on it. Not while a job or server uses it. This cannot be undone. Storage is billed until it is deleted.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_gpu_volume
ResponseObject: ref, name, deleted, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe storage name
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/volumes/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/gpu/volumes/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/concurrent-builds

Concurrent builds How many of this project's builds may run at once. null means as many as the builders allow.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_concurrent_builds
ResponseObject: ref, concurrent_builds, max
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/concurrent-builds" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/concurrent-builds', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/concurrent-builds

Change concurrent builds Sets how many of this project's builds may run at once, 1 to 20, or null for no limit. Builds over the limit wait in the queue and start when one ahead of them finishes; a waiting build is not billed for build minutes. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_concurrent_builds
Body limit256 KiB
ResponseObject: ref, concurrent_builds, max, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
concurrent_buildsbodystring, min 1, max 20YesA whole number from 1 to 20, or null for no limit.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/concurrent-builds" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"concurrent_builds":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/concurrent-builds', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "concurrent_builds": "value"
  }),
})
const result = await response.json()

Functions

MethodPathDescription
GET/projects/{ref}/functionsFunctions
GET/projects/{ref}/functions/{name}Read a function's code
PUT/projects/{ref}/functions/{name}Deploy a function
PATCH/projects/{ref}/functions/{name}Function time limits
DELETE/projects/{ref}/functions/{name}Remove a function
PUT/projects/{ref}/function-egressFunctions' outbound requests
GET/projects/{ref}/function-secretsFunction secrets
POST/projects/{ref}/function-secretsSave function secrets
POST/projects/{ref}/function-secrets/copyCopy function secrets
DELETE/projects/{ref}/function-secrets/{name}Delete a function secret

GET /api/v1/projects/{ref}/functions

Functions Shows the deployed functions with the address to call each one and whether it is public. egress says whether outbound requests are on, and outbound_requests_per_minute is the limit a minute on outside requests that functions, pg_net and HTTP jobs share (calls to the project's own API are not counted).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_functions
ResponseObject: ref, functions
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/functions/{name}

Read a function's code Shows the code and version of a deployed function. Read the current code before changing it. When deploy_function answered status "building", build.status (building, deployed or failed) and build.error tell how it ended.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_function
ResponseObject: ref, name, status, public, version, url, updated_at, bundled, bytes
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe function name.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/functions/{name}

Deploy a function Deploys one function. A function with the same name is replaced as a whole, and the next call runs the new code. The code is one TypeScript file that exports, with export default, (request, { sql }) => response. request has method, headers, claims (the claims of the key it was called with: role anon, authenticated or service_role; null for a public function called with no key), text() and json(); sql(text, values) runs one SQL statement with this project's service_role privileges and returns an array of rows. The response is an object (sent as JSON), a string, or { status, headers, body }. To use npm packages, send dependencies ({"zod":"^3.23.8"}) or package_json (a string) with it. They are installed at deploy time and bundled with the code into one ESM file (up to 5MB; install scripts are not run; no native modules). A package set installed once is reused, so from the second deploy on it takes seconds. If it has not finished in 40 seconds, it answers status "building" at once and the deploy carries on on the server. Then call get_function again until build.status is deployed or failed. For several files, send them together as files ({"lib/util.ts": "..."}) and import them by relative path. To send a whole folder, leave source empty and put everything, index.ts included, in files. As on Supabase, the imports of a deno.json (or import_map.json) and npm:, jsr: and URL imports (such as npm:zod@3, jsr:@supabase/supabase-js@2, https://esm.sh/…, https://deno.land/[email protected]/http/server.ts) work. npm:, jsr: and esm.sh are installed as npm packages at deploy time; any other URL is fetched once at deploy time and bundled with the code (the addresses and SHA-256 fetched are in get_function's remote_imports, and must match a deno.lock when there is one). Nothing is fetched while the function runs. As with Supabase's supabase/functions/<name>/ folder, when the code imports ../_shared/cors.ts, put it in files as "../_shared/cors.ts". Only folders beside the function folder can be sent this way. One call may run for 150 seconds (as on Supabase). To change that, use set_function_limits. The caller's Authorization header (when it is a key of this project) reaches the function as it was sent, so createClient(url, anonKey, { global: { headers: { Authorization: req.headers.get('Authorization') } } }) acts as that person. When you change a bundled function, send back the package_json and files that get_function returned, as they are. Leave them out and it becomes a single-file function. Network access is off by default and can be turned on with set_function_egress. The production environment variables arrive in process.env. Only owners and admins can deploy functions. Who can call it: a private function (the default) needs a key of this project: the publishable key (claims.role anon), a signed-in user's token (authenticated) or the secret key (service_role). This is what Supabase's verify_jwt checks, so supabase.functions.invoke and a fetch with the publishable key work as they are. To accept signed-in users only, check request.claims.role in the function. With public set to true, anyone can call it, with no key at all. Use that only for requests that arrive without a key, such as a payment provider's webhook. Calling: POST https://<ref>.<zone>/functions/v1/<name>, with the header Authorization: Bearer <publishable key, user token or secret key>.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldeploy_function
Body limit4096 KiB
ResponseObject: ref, name, status, version, public, url, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413 · 429
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesStarts with a lowercase letter; lowercase letters, digits and hyphens. It becomes the end of the address you call.
sourcebodystringNoThe whole function code (up to 8MB). With files, it is saved as the entry file. Leave it out when files holds the entry.
publicbodybooleanNoLeft out, the current value is kept; a new function is private.
package_jsonbodystringNoThe whole package.json (optional). With it, packages are installed and everything is bundled into one file.
package_lockbodystringNopackage-lock.json (optional). With it, npm ci installs exactly those versions.
dependenciesbodyobjectNoThe npm packages to use and their versions. For example: {"zod": "^3.23.8"}. Merged into the dependencies of package_json.
filesbodyobjectNoOther files to put beside source, relative path to content (up to 200, 8MB in total). A folder beside the function folder is written "../_shared/cors.ts".
entrybodystringNoThe file name source is saved as. index.ts by default.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"source":"export default async () => new Response(\"ok\")","public":true,"package_json":"value","package_lock":"value","dependencies":{},"files":{},"entry":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "source": "export default async () => new Response(\"ok\")",
    "public": true,
    "package_json": "value",
    "package_lock": "value",
    "dependencies": {},
    "files": {},
    "entry": "value"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/functions/{name}

Function time limits Sets, without deploying again, how long one call of a function may take (timeout_seconds) and how long one SQL statement of it may take (sql_timeout_seconds), in seconds. The defaults are 150 seconds (as on Supabase's edge functions) and 8 seconds; this server's maximum is get_function's timeout_cap_seconds. null puts one back to its default; leaving it out keeps the current value. Takes effect from the next call. A call past its limit is cut off with 504.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_function_limits
Body limit256 KiB
ResponseObject: ref, name
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413 · 429
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe function name.
publicbodybooleanNo
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"public":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "public": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/functions/{name}

Remove a function Removes a function. Its address returns 404, and scheduled jobs that call it fail. It cannot be undone, so use it only when the user clearly asks, and put the function name exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_function
Body limit256 KiB
ResponseObject: ref, name, removed
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413 · 429
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe function name.
confirmbodystringYesThe function name. It must be exactly the same as name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/functions/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/function-egress

Functions' outbound requests Allows or blocks functions from calling public HTTPS addresses with fetch(). The current state is egress in list_functions. Takes effect within 30 seconds. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_function_egress
Body limit256 KiB
ResponseObject: ref, egress, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanYestrue to allow outbound requests, false to block them.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-egress" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-egress', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/function-secrets

Function secrets The names of the secrets only functions see, and when each changed. No tool can read a value back. readable false means the value no longer opens and is left out of functions, so save it again. Only owners and admins can see them.

NameDescription
Token scoperead
Rolesproject.admin owner or admin
MCP toollist_function_secrets
ResponseObject: ref, secrets
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/function-secrets

Save function secrets Saves one or more secrets only functions see (a webhook signing key, a mail service's API key). They never reach a site build, and they win over an environment variable of the same name. Functions read them from process.env from their next call. A name already there is overwritten; if any one is wrong, nothing is saved. A saved value cannot be read back: the answer holds names only. Names starting with SUPABASE_ or BLACKWALL, and NODE_ENV, HOME, PATH, NODE_OPTIONS and TZ, are refused. Up to 100 per project and 32KB per value. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_function_secrets
Body limit256 KiB
ResponseObject: ref, saved, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
secretsbodyobjectNoNames and values, for example {"STRIPE_WEBHOOK_SECRET": "whsec_…"}. The values are not shown again.
dotenvbodystringNoThe whole of a .env file. Comments and blank lines are skipped.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"secrets":{},"dotenv":"API_URL=https://example.com"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "secrets": {},
    "dotenv": "API_URL=https://example.com"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/function-secrets/copy

Copy function secrets Copies function secrets by name from another of your projects (from) into this one (ref). The values move inside the server and the answer holds names only, so the same key need not be pasted again. A name this project already has is skipped (skipped) unless overwrite is true. Names from does not have are listed in missing and the rest are copied. Up to 50 at a time. You must be an owner or admin of both projects (they may be in different organisations).

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcopy_function_secrets
Body limit256 KiB
ResponseObject: ref, from, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
frombodystringYesThe ref of the project to copy from. list_projects gives it.
namesbodyarray<string>, min items 1, max items 50YesThe secret names to copy: name in list_function_secrets of the from project. For example ["ANTHROPIC_API_KEY"].
overwritebodybooleanNoWhether to replace a name this project already has. Defaults to false (skipped).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets/copy" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"from":"value","names":["my-name"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets/copy', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "from": "value",
    "names": [
      "my-name"
    ]
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/function-secrets/{name}

Delete a function secret Deletes one function secret. The value cannot be brought back, and functions run without it from their next call. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldelete_function_secret
ResponseObject: ref, name, deleted
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesname in list_function_secrets.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/function-secrets/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Domains and edge

MethodPathDescription
GET/projects/{ref}/domainsDomains
POST/projects/{ref}/domainsConnect a domain
DELETE/projects/{ref}/domains/{hostname}Disconnect a domain
PUT/projects/{ref}/domains/{hostname}/redirectRedirect a domain
GET/projects/{ref}/edgeFirewall and cache
PUT/projects/{ref}/edgeChange the firewall
POST/projects/{ref}/edge/purgePurge the cache
PUT/projects/{ref}/domains/{hostname}/certificateUpload a certificate
DELETE/projects/{ref}/domains/{hostname}/certificateRemove an uploaded certificate

GET /api/v1/projects/{ref}/domains

Domains Shows the domains connected to this project with what each is for (site/api), the branch it follows, where it redirects, its verification state (with the TXT record to add if not verified yet) and its certificate state.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_domains
ResponseObject: ref, domains
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/domains

Connect a domain Connects a domain. Add the returned verification_record (TXT) to DNS and it is verified within a few minutes. With target=api the address answers as the project API (data, sign-in, storage). With branch, it follows that branch's preview. Wildcards such as *.example.com work. With pair_www=true the www address is connected too and one redirects to the other.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooladd_domain
Body limit256 KiB
ResponseObject: ref, domains
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
hostnamebodystringYesFor example: example.com, api.example.com, *.example.com
targetbodystring, site | apiNosite for the deployed site, api for the project API.
branchbodystringNoSite only. Empty means production.
pair_wwwbodybooleanNoAlso connect the www address and redirect one to the other.
appbodystringNoThe app name (name from list_apps). Empty means the project's site (the app with project_site true).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"hostname":"app.example.com"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "hostname": "app.example.com"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/domains/{hostname}

Disconnect a domain Disconnects a domain. The address stops answering within a few seconds. Use it only when the user clearly asks, and put the domain exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_domain
Body limit256 KiB
ResponseObject: ref, domains
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
hostnamepathstringYesThe connected domain.
confirmbodystringYesThe domain. It must be the same as hostname (case does not matter).
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/domains/{hostname}/redirect

Redirect a domain Makes this domain redirect to another domain of the same project, or, with to empty, serve the site again. With a path in to (example.com/vision), the visited path is appended to it and the query string goes along: vision.example.com/x?y=1 → example.com/vision/x?y=1, and the home page goes to example.com/vision. The domain in to must be connected to this project and not itself a redirect.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_domain_redirect
Body limit256 KiB
ResponseObject: ref, domains
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
hostnamepathstringYesThe domain to redirect.
tobodystringNoA domain, or a domain and a path (for example example.com/vision). Empty turns the redirect off.
statusbodyinteger, 308 | 307 | 301 | 302NoThe status code. 308 (permanent, keeps the method) by default. Left out, the current one stays.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com/redirect" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"to":"https://example.com","status":308}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com/redirect', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "to": "https://example.com",
    "status": 308
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/edge

Firewall and cache Shows this project's IP deny and allow lists, the request limit per minute for one address, what they apply to, and when the cache was last purged.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_edge
ResponseObject: ref, deny, allow, rate_per_minute, scope, cache_purged_at
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/edge

Change the firewall Changes the IP deny list, the allow list (empty allows everyone), the request limit per minute (10 to 100000, null for the platform default) and what they apply to (all/site/api). Only the fields you give change. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_edge
Body limit256 KiB
ResponseObject: ref, deny, allow, rate_per_minute, scope, cache_purged_at
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
denybodyarray<string>NoIP addresses or CIDR ranges to refuse.
allowbodyarray<string>NoIP addresses or CIDR ranges to allow. Empty allows everyone.
rate_per_minutebodynumberNoRequests per minute from one address, 10 to 100000. null for the platform default.
scopebodystring, all | site | apiNoWhat the rules apply to: all, site or api.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"deny":["value"],"allow":["value"],"rate_per_minute":1,"scope":"all"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "deny": [
      "value"
    ],
    "allow": [
      "value"
    ],
    "rate_per_minute": 1,
    "scope": "all"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/edge/purge

Purge the cache Discards every response stored at the edge because a server deployment allowed sharing with s-maxage. From the next request, the deployment answers again.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolpurge_edge_cache
ResponseObject: ref, deny, allow, rate_per_minute, scope, cache_purged_at
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge/purge" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge/purge', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/domains/{hostname}/certificate

Upload a certificate Uses your own TLS certificate for a connected domain. certificate is the PEM chain (the server certificate first), key is the PEM private key. The domain name must be in the certificate. Takes effect within 1 minute. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolupload_certificate
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
hostnamepathstringYesThe connected domain.
certificatebodystringYesThe PEM certificate chain, the server certificate first.
keybodystringYesThe PEM private key.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com/certificate" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"certificate":"-----BEGIN CERTIFICATE-----\n…","key":"API_URL"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com/certificate', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "certificate": "-----BEGIN CERTIFICATE-----\n…",
    "key": "API_URL"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/domains/{hostname}/certificate

Remove an uploaded certificate Removes the uploaded certificate and goes back to the automatically issued one. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolremove_certificate
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
hostnamepathstringYesThe connected domain.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com/certificate" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/domains/app.example.com/certificate', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Network

MethodPathDescription
GET/projects/{ref}/networkNetwork addresses
GET/projects/{ref}/network/ipsNetwork addresses
POST/projects/{ref}/network/static-outbound-ipStatic outbound IP
DELETE/projects/{ref}/network/static-outbound-ipStatic outbound IP

GET /api/v1/projects/{ref}/network

Network addresses The addresses the project receives requests on (inbound: a custom domain's A record) and, for a project with the static outbound IP turned on, the static addresses it sends from (outbound.addresses). For a project without it the outbound address is shared and not static, so it is not returned. Do not put it on an allow list.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_network
ResponseObject: ref, region, inbound, outbound, static_outbound_ip, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/network/ips

Network addresses The addresses the project receives requests on (inbound: a custom domain's A record) and, for a project with the static outbound IP turned on, the static addresses it sends from (outbound.addresses). For a project without it the outbound address is shared and not static, so it is not returned. Do not put it on an allow list.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_network
ResponseObject: ref, region, inbound, outbound, static_outbound_ip, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network/ips" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network/ips', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/network/static-outbound-ip

Static outbound IP Turns the static outbound IP, a paid add-on, on or off. When on, server deployments, functions' fetch() and database webhooks leave from a static address, and a monthly price is billed for the time it is on. Pro plan and above; only owners, admins and billing members can do it. It costs money, so use it only when the user clearly asks. The price and the current state are in get_network. In a region whose address does not exist yet the outcome is provisioning: the address is being made (usually under an hour), and until it is ready nothing is billed and no address is returned.

NameDescription
Token scopewrite
Rolesbilling.manage owner or admin or billing
MCP toolset_static_outbound_ip
ResponseObject: ref, active, status, outcome, addresses, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network/static-outbound-ip" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network/static-outbound-ip', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/network/static-outbound-ip

Static outbound IP Turns the static outbound IP, a paid add-on, on or off. When on, server deployments, functions' fetch() and database webhooks leave from a static address, and a monthly price is billed for the time it is on. Pro plan and above; only owners, admins and billing members can do it. It costs money, so use it only when the user clearly asks. The price and the current state are in get_network. In a region whose address does not exist yet the outcome is provisioning: the address is being made (usually under an hour), and until it is ready nothing is billed and no address is returned.

NameDescription
Token scopewrite
Rolesbilling.manage owner or admin or billing
MCP toolset_static_outbound_ip
ResponseObject: ref, active, status, outcome, addresses, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network/static-outbound-ip" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network/static-outbound-ip', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Scheduled jobs

MethodPathDescription
GET/projects/{ref}/jobsScheduled jobs
PUT/projects/{ref}/jobs/{job}Create or change a scheduled job
PATCH/projects/{ref}/jobs/{job}Turn a scheduled job on or off
POST/projects/{ref}/jobs/{job}/runRun a scheduled job now
GET/projects/{ref}/jobs/{job}/runsScheduled job runs
DELETE/projects/{ref}/jobs/{job}Remove a scheduled job

GET /api/v1/projects/{ref}/jobs

Scheduled jobs Shows the scheduled jobs (cron) with their schedule, next run and last result. Jobs made with cron.schedule in the project's SQL have source sql. limit is the plan's number of jobs. All times are UTC.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_jobs
ResponseObject: ref, timezone, limit, used, jobs
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/jobs/{job}

Create or change a scheduled job Calls a function or runs one SQL statement on a schedule. A job with the same name is changed. schedule is five-field cron (minute hour day month weekday) in UTC. Korea is UTC+9, so midnight in Korea is "0 15 * * *". With kind function, target is the name of a deployed function (called with a POST whose body is {}); with sql, target is SQL run as the service role. External URLs cannot be called. Create a function, or use net.http_post from SQL. SQL has to finish within timeout_seconds (120 by default); when the SQL raises its own statement_timeout it is given that long. Either way, at most the plan's maximum (Free 300 seconds, Pro and Enterprise 3600). Jobs made with cron.schedule in SQL are listed too, with source sql; change them in SQL (cron.alter_job, cron.unschedule) or with set_job_enabled and remove_job. Each plan has a number of jobs per project (limit in list_jobs).

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolsave_job
Body limit256 KiB
ResponseObject: ref, timezone, job
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
jobpathstringYesThe job name. Saving a name that exists changes that job.
schedulebodystringYesFor example: "*/5 * * * *" (every 5 minutes), "0 15 * * *" (midnight in Korea).
kindbodystring, function | sqlYesfunction to call a deployed function, sql to run SQL.
targetbodystringYesThe function name, or the SQL to run.
enabledbodybooleanNoWhether the job runs. On by default.
timeout_secondsbodynumberNoHow long a SQL job may run, in seconds. 120 by default; at most the plan's maximum (Free 300, Pro and Enterprise 3600).
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"schedule":"0 3 * * *","kind":"function","target":"hello"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "schedule": "0 3 * * *",
    "kind": "function",
    "target": "hello"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/jobs/{job}

Turn a scheduled job on or off Turns a scheduled job off or back on. When turned back on it does not catch up on missed runs; it runs from the next scheduled time. job is the id or the name.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_job_enabled
Body limit256 KiB
ResponseObject: ref, id, enabled
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
jobpathstringYesThe job id or name.
enabledbodybooleanYestrue to turn it on, false to turn it off.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/jobs/{job}/run

Run a scheduled job now Runs the job once within a minute, without waiting for its schedule. See the result with list_job_runs.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolrun_job
ResponseObject: ref, id, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
jobpathstringYesThe job id or name.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly/run" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly/run', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/jobs/{job}/runs

Scheduled job runs Shows a job's recent runs and their results, newest first, with the error when one failed.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_job_runs
ResponseObject: ref, id, runs
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
jobpathstringYesThe job id or name.
limitquerynumberNoHow many runs to show.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly/runs?limit=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly/runs?limit=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/jobs/{job}

Remove a scheduled job Removes a scheduled job and its run history.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_job
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
jobpathstringYesThe job id or name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/jobs/nightly', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Database webhooks

MethodPathDescription
GET/projects/{ref}/webhooksDatabase webhooks
POST/projects/{ref}/webhooksCreate a database webhook
PATCH/projects/{ref}/webhooks/{webhook}Turn a database webhook on or off
DELETE/projects/{ref}/webhooks/{webhook}Remove a database webhook
GET/projects/{ref}/webhooks/{webhook}/deliveriesDatabase webhook deliveries

GET /api/v1/projects/{ref}/webhooks

Database webhooks Shows the webhooks that report table changes and their recent deliveries. attached false means the table was dropped and the webhook stopped. The signing key (signing_secret) is shown once, when the webhook is created (create_db_webhook); the list carries only signing_secret_set and its last four characters (signing_secret_last4).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_db_webhooks
ResponseObject: ref, webhooks
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/webhooks

Create a database webhook Sends a POST when rows are added to, changed in or deleted from a table. Only for committed changes, within a few seconds. With kind url, target is a public https address (no private or internal addresses, redirects are not followed); with function, it is the name of a function in this project. Body: { type: "INSERT"|"UPDATE"|"DELETE", table, schema: "public", record, old_record, hook, delivery, occurred_at }. Verify it with the header x-blackwall-signature: t=<seconds>,v1=<HMAC-SHA256(signing_secret, "<t>.<body>") in hex>. signing_secret is in this answer only and cannot be read again, so save it on the receiving side right away. On failure (a response other than 2xx, or more than 10 seconds), it sends again after 10 seconds, 1 minute, 5 minutes and 30 minutes, and gives up on the fifth try.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_db_webhook
Body limit256 KiB
ResponseObject: ref, webhook, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413 · 500
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringYesA name to recognise the webhook by.
tablebodystringYesA table name from list_tables.
eventsbodyarray<string>, insert | update | deleteYesWhich changes to send: insert, update, delete.
kindbodystring, url | functionYesurl to POST to an address, function to call a function in this project.
targetbodystringYesThe https address, or the function name.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","table":"notes","events":["insert"],"kind":"url","target":"hello"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "table": "notes",
    "events": [
      "insert"
    ],
    "kind": "url",
    "target": "hello"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/webhooks/{webhook}

Turn a database webhook on or off Turns a webhook off or on. Changes made while it is off are never sent. webhook is the id or the name.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_db_webhook_enabled
Body limit256 KiB
ResponseObject: ref, id, enabled
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
webhookpathstringYesThe webhook id or name.
enabledbodybooleanYestrue to turn it on, false to turn it off.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks/orders" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks/orders', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/webhooks/{webhook}

Remove a database webhook Removes a webhook, its delivery history and any changes not sent yet.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_db_webhook
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
webhookpathstringYesThe webhook id or name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks/orders" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks/orders', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/webhooks/{webhook}/deliveries

Database webhook deliveries Shows a webhook's recent deliveries with the response status and the reason for any failure, newest first. Kept for 7 days.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_db_webhook_deliveries
ResponseObject: ref, id, deliveries
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
webhookpathstringYesThe webhook id or name.
limitquerynumberNoHow many deliveries to show.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks/orders/deliveries?limit=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/webhooks/orders/deliveries?limit=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Backups and restore

MethodPathDescription
GET/projects/{ref}/backupsBackups
POST/projects/{ref}/backupsBack up now
POST/projects/{ref}/backups/{backup_id}/restoreRestore a backup
POST/projects/{ref}/backups/restore-to-timeRestore a backup

GET /api/v1/projects/{ref}/backups

Backups Shows the project backups (automatic every night between 03:00 and 05:00 in the local time of the project region, made by hand, and made just before a restore) and the restore history. retention_days is how long they are kept.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_backups
ResponseObject: ref, retention_days, point_in_time, backups, restores
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/backups

Back up now Backs up the tables, users and files now. Make one before a big change (a migration, a large delete). Kept for 7 days.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_backup
ResponseObject: ref, id, status, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/backups/{backup_id}/restore

Restore a backup Returns the project's tables, users and files to when the backup was made. Give at (an ISO time) instead of backup_id to return to that moment, to the second (Pro and above, within list_backups' point_in_time range); files are then returned to what the rows at that moment point to (including ones deleted in the last 14 days) only on servers that use a file bucket, and otherwise left as they are now. Every change after that point is lost, so use this only when the user clearly asks. The state just before the restore is backed up first, so you can restore again with undo_backup_id. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrestore_backup
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
backup_idpathstringYesThe id from list_backups.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups/BACKUP_ID/restore" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups/BACKUP_ID/restore', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/backups/restore-to-time

Restore a backup Returns the project's tables, users and files to when the backup was made. Give at (an ISO time) instead of backup_id to return to that moment, to the second (Pro and above, within list_backups' point_in_time range); files are then returned to what the rows at that moment point to (including ones deleted in the last 14 days) only on servers that use a file bucket, and otherwise left as they are now. Every change after that point is lost, so use this only when the user clearly asks. The state just before the restore is backed up first, so you can restore again with undo_backup_id. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrestore_backup
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
atbodystringNoFor example: 2026-10-01T14:59:00+09:00
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups/restore-to-time" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"at":"2026-10-01T14:59:00+09:00"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/backups/restore-to-time', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "at": "2026-10-01T14:59:00+09:00"
  }),
})
const result = await response.json()

Logs and observability

MethodPathDescription
GET/projects/{ref}/logsLogs
GET/projects/{ref}/log-drainsLog drains
POST/projects/{ref}/log-drainsAdd a log drain
PATCH/projects/{ref}/log-drains/{id}Turn a log drain off or on
DELETE/projects/{ref}/log-drains/{id}Remove a log drain
GET/projects/{ref}/metricsMetrics
GET/projects/{ref}/auditAudit log

GET /api/v1/projects/{ref}/logs

Logs Shows what happened in the project, newest first: requests to the site, API, sign-in and functions (method, path, status, time taken) and the lines the deployed servers and functions printed. If the app returns 500, look here first with level set to error. For the next page, pass the response's next_before as before. Query strings are not stored.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_logs
ResponseObject: ref, lines, next_before, retention_days
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
sourcequerystring, site | server | api | auth | functions | buildNoWhere the lines come from: site, server, api, auth, functions, build.
levelquerystring, warn | errorNowarn for warnings and errors, error for errors only.
qquerystringNo
beforequerystringNoOnly lines older than this time. The previous response's next_before.
deploymentquerystringNo
limitqueryinteger, min 1, max 500NoDefault 100.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/logs?source=site&level=warn" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/logs?source=site&level=warn', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/log-drains

Log drains Where logs are sent (https or datadog), which sources are sent, and the last delivery and error. The signing key (signing_secret) is shown once, when the drain is created (create_log_drain); the list carries only signing_secret_set and, for owners and admins, its last four characters (signing_secret_last4).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_log_drains
ResponseObject: ref, sources, drains
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/log-drains

Add a log drain Sends this project's logs elsewhere, starting with logs written after it is created. kind=https POSTs signed JSON; kind=datadog needs an API key in secret. An https drain's signing_secret is in this answer only, so save it on the receiving side right away. Up to 5 per project. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_log_drain
Body limit256 KiB
ResponseObject: id, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringYesA name to recognise the drain by.
kindbodystring, https | datadogYeshttps or datadog.
urlbodystringYesA public https address.
secretbodystringNoThe Datadog API key, or the value to put in the Authorization header when sending over https.
sourcesbodyarray<string>, site | server | api | auth | functions | buildNoEmpty means all.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","kind":"https","url":"https://example.com/hook"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "kind": "https",
    "url": "https://example.com/hook"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/log-drains/{id}

Turn a log drain off or on Stops or restarts sending and keeps the settings. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_log_drain_enabled
Body limit256 KiB
ResponseObject: ref, id, enabled
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_log_drains.
enabledbodybooleanYestrue to send, false to stop.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains/ID', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/log-drains/{id}

Remove a log drain Removes a log drain. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolremove_log_drain
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_log_drains.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/log-drains/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/metrics

Metrics Returns requests, failures and latency (p50/p95/p99) by surface and by time, and the database size, connections and cache hit rate.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_metrics
ResponseObject: ref, range, requests, failures, latency_ms, by_surface, points, database
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
rangequerystring, 1h | 24h | 7dNoEmpty means 24h.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/metrics?range=1h" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/metrics?range=1h', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/audit

Audit log tab=activity is who changed what in the organisation; tab=access is who accessed personal data. Newest first, up to 200 at a time; when has_more is true, call again with a higher offset. from and to are YYYY-MM-DD (dates in Korea). Only owners and admins can do this.

NameDescription
Token scoperead
Rolesaudit.read owner or admin
MCP toolget_audit_log
ResponseObject: ref, tab, total, has_more, records
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tabquerystring, activity | accessNoactivity for changes, access for access to personal data.
fromquerystringNoThe first date, YYYY-MM-DD.
toquerystringNoThe last date, YYYY-MM-DD.
actorquerystringNoPart of an email address.
actionquerystringNoActions starting with these words. For example: member.
limitqueryinteger, min 1, max 200NoHow many entries to get, up to 200.
offsetqueryinteger, min 0NoHow many entries to skip.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/audit?tab=activity&from=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/audit?tab=activity&from=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Team and notifications

MethodPathDescription
GET/projects/{ref}/teamTeam members
POST/projects/{ref}/team/invitationsInvite a member
DELETE/projects/{ref}/team/invitations/{id}Cancel an invitation
PATCH/projects/{ref}/team/members/{email}Change a member's role
DELETE/projects/{ref}/team/members/{email}Remove a member
PUT/projects/{ref}/team/mfaRequire two-step verification
GET/projects/{ref}/channelsNotification channels
POST/projects/{ref}/channelsAdd a notification channel
POST/projects/{ref}/channels/{id}/testTest a notification channel
PATCH/projects/{ref}/channels/{id}Turn a notification channel off or on
DELETE/projects/{ref}/channels/{id}Remove a notification channel

GET /api/v1/projects/{ref}/team

Team members The members of the organisation that owns this project, with their roles, whether they use two-step verification, and invitations not accepted yet.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_team
ResponseObject: ref, mfa_required, members, invitations
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/team/invitations

Invite a member Sends an invitation link by email. A person joins by opening the link, so nobody is added straight away. For an address that is already a member, only the role changes. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolinvite_member
Body limit256 KiB
ResponseObject: ref, id, email, role, outcome, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
emailbodystringYesThe email address to invite.
rolebodystring, admin | developer | billing | viewerNoEmpty means developer.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/invitations" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"email":"[email protected]"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/invitations', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "email": "[email protected]"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/team/invitations/{id}

Cancel an invitation Cancels a sent invitation. Its link stops opening at once. id is in the invitations of list_team.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolcancel_invitation
ResponseObject: ref, id, cancelled
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe invitation id from list_team.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/invitations/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/invitations/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/team/members/{email}

Change a member's role Changes the role of someone who is already a member. The owner's role cannot be changed. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolset_member_role
Body limit256 KiB
ResponseObject: ref, email, role
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
emailpathstringYesThe member's email address.
rolebodystring, admin | developer | billing | viewerYesThe new role.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/members/person%40example.com" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"role":"admin"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/members/person%40example.com', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "role": "admin"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/team/members/{email}

Remove a member Removes someone from the organisation. The owner cannot be removed. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolremove_member
ResponseObject: ref, email, removed
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
emailpathstringYesThe member's email address.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/members/person%40example.com" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/members/person%40example.com', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/team/mfa

Require two-step verification Requires two-step verification from every member of the organisation, or stops requiring it. To turn it on, this token's owner must have two-step verification on first. Members without it cannot open this organisation until they turn it on, and are listed in without_mfa in the response. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolset_mfa_required
Body limit256 KiB
ResponseObject: ref, mfa_required
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
requiredbodybooleanYestrue to require it, false to stop requiring it.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/mfa" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"required":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/team/mfa', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "required": true
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/channels

Notification channels The Slack, KakaoWork and webhook channels that receive news about deployments, domains, usage and billing, with the result of the last delivery. Organisation wide. A webhook channel's signing key (signing_secret) is shown once, when the channel is created (create_channel); the list carries only signing_secret_set and its last four characters (signing_secret_last4).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_channels
ResponseObject: ref, notices, channels
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/channels

Add a notification channel Sends notifications to Slack (an Incoming Webhook address), KakaoWork (a bot webhook address), a signed webhook, or email (1 to 10 addresses in emails). An email channel mails each address a confirmation link and sends notifications only to addresses that opened it. Leave notices empty to receive every kind (usage covers limit warnings and traffic spike alerts). The signing_secret of a webhook channel is in this answer only, so save it on the receiving side right away. Check it with test_channel after creating it. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolcreate_channel
Body limit256 KiB
ResponseObject: ref, id, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
kindbodystring, slack | kakaowork | webhook | emailYesslack, kakaowork, webhook or email.
namebodystringYesA name to recognise the channel by.
urlbodystringNoThe webhook address to send to. Not used by an email channel.
emailsbodyarray<string>, max items 10NoFor an email channel, the addresses to send to, e.g. ["[email protected]"].
noticesbodyarray<string>, deployments | domains | usage | billingNoWhich kinds to receive: deployments, domains, usage, billing. Empty means all.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"kind":"slack","name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "kind": "slack",
    "name": "my-name"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/channels/{id}/test

Test a notification channel Sends a test message to that one channel only. For an email channel it goes to confirmed addresses only, and addresses still waiting are sent the confirmation again. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP tooltest_channel
ResponseObject: ref, id, sent, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_channels.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels/ID/test" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels/ID/test', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/channels/{id}

Turn a notification channel off or on Stops or resumes notifications and keeps the settings. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolset_channel_enabled
Body limit256 KiB
ResponseObject: ref, id, enabled
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_channels.
enabledbodybooleanYestrue to send, false to stop.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels/ID', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/channels/{id}

Remove a notification channel Removes a notification channel. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesteam.manage owner or admin
MCP toolremove_channel
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_channels.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/channels/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Billing and usage

MethodPathDescription
GET/projects/{ref}/usageUsage
GET/projects/{ref}/billing/planPlan
GET/projects/{ref}/billing/spendSpend controls
PUT/projects/{ref}/billing/spend-capTurn the spend cap on or off
PUT/projects/{ref}/billing/spend-limitMonthly spend limit
GET/projects/{ref}/billingBill and usage

GET /api/v1/projects/{ref}/usage

Usage Returns the number of tables, rows, users and files, the storage used, this month's metered amounts, and the limits a minute (limits.outbound_requests_per_minute: outside requests that functions, pg_net and HTTP jobs share; calls to the project's own API are not counted).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolusage
ResponseObject: tables, approximate_rows, users, files, storage_bytes, database_bytes
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/usage" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/usage', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/billing/plan

Plan The organisation's current plan, any scheduled change, the plans it can choose and their monthly prices, and the paid add-ons running in the organisation (addons). On the Free plan, free holds its limits (one person, no custom domains, deployments a day, paused after 3 unused days, build minutes and traffic) and what is Pro's. Each add-on is turned on and off with its own tool (set_static_outbound_ip). Changing the plan needs a payment, so it is done only on the console's billing page. Only owners, admins and billing members can see it.

NameDescription
Token scoperead
Rolesbilling.manage owner or admin or billing
MCP toolget_plan
ResponseObject: ref, currency, plan, scheduled, options, billed_by, addons, note
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/plan" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/plan', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/billing/spend

Spend controls The spend cap (on: usage stops at what the plan includes and no overage is charged), the monthly overage limit and its alert webhook, and this month's usage. Only owners, admins and billing members can see it.

NameDescription
Token scoperead
Rolesbilling.manage owner or admin or billing
MCP toolget_spend_controls
ResponseObject: ref, spend_cap, spend_cap_applies, spend_limit_minor, spend_limit_floor_minor, webhook_url, progress
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/spend" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/spend', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/billing/spend-cap

Turn the spend cap on or off Turns the spend cap of a paid plan on or off. On, usage stops at what is included and there is no overage (the console spend limit's "Allowances only"). Off, what goes over is billed, so turning it off takes the project name (name from list_projects) exactly in confirm. Only owners, admins and billing members can do this.

NameDescription
Token scopewrite
Rolesbilling.manage owner or admin or billing
MCP toolset_spend_cap
Body limit256 KiB
ResponseObject: ref, spend_cap
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanYestrue turns it on.
confirmbodystringNoOnly when turning it off (enabled: false): the project name. It must be exactly the same as name in list_projects.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/spend-cap" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/spend-cap', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/billing/spend-limit

Monthly spend limit Sets the limit on a month's overage (in the currency's smallest unit, won) and the webhook address to alert as it is approached. With the spend cap off, this is the console spend limit's "Allow overage up to an amount" (null: "No limit"). spend_limit_minor null removes the limit. Leave webhook_url out to keep the current address. New alert addresses are better added as notification channels (create_channel). Only owners, admins and billing members can do this.

NameDescription
Token scopewrite
Rolesbilling.manage owner or admin or billing
MCP toolset_spend_limit
Body limit256 KiB
ResponseObject: ref, spend_cap, spend_cap_applies, spend_limit_minor, spend_limit_floor_minor, webhook_url, progress
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
spend_limit_minorbodystring, min 0YesThe limit. null for none.
webhook_urlbodystringNoThe https address to alert. An empty string removes it.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/spend-limit" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"spend_limit_minor":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing/spend-limit', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "spend_limit_minor": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/billing

Bill and usage One month's bill (usage, included amount and charge per item) and usage by day. Empty period means this month (UTC). Amounts are in the smallest unit of currency (KRW: won, USD: cents). included is what the plans held this month allow, split by the days on each; list_included is the Pro allowance. amount_minor and total_minor are what is charged; list_amount_minor and list_total_minor are the list price. An organisation with billing_exempt true (the operator's own) is charged 0 and is never stopped for payment; its list price is the Pro rate card. Cards and payment history are only in the console. Only owners, admins and billing members can see it.

NameDescription
Token scoperead
Rolesbilling.manage owner or admin or billing
MCP toolget_billing
ResponseObject: ref, period, plan, period_start, period_end, currency, list_total_minor, billing_exempt, lines, usage, months
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
periodquerystringNoYYYY-MM
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing?period=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/billing?period=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

Other

MethodPathDescription
GET/projects/{ref}/transferOrganizations to transfer to
POST/projects/{ref}/transferTransfer project
GET/projects/{ref}/serverDedicated server
POST/projects/{ref}/serverRent a dedicated server
PATCH/projects/{ref}/serverChange the server size
DELETE/projects/{ref}/serverGive back the dedicated server
PUT/projects/{ref}/server/standbyStandby server
POST/projects/{ref}/server/moveMove the server
PUT/projects/{ref}/server/restore-daysRestore days
GET/projects/{ref}/s3-keysS3 access keys
POST/projects/{ref}/s3-keysCreate an S3 access key
DELETE/projects/{ref}/s3-keys/{id}Revoke an S3 access key
POST/projects/{ref}/storage/policiesCreate a file access rule
POST/projects/{ref}/storage/copy-from-importCopy files from an import
GET/projects/{ref}/deployment-retentionDeployment retention
PUT/projects/{ref}/deployment-retentionChange deployment retention
GET/projects/{ref}/schemasProject schemas
PUT/projects/{ref}/schemas/exposedSet exposed schemas
GET/projects/{ref}/data-apiData API settings
PATCH/projects/{ref}/data-apiChange the Data API settings
POST/projects/{ref}/{param0}/{param1}Add SAML SSO
PUT/projects/{ref}/{param0}/{param1}/{param2}Add SAML SSO
GET/projects/{ref}/config/authShow the whole sign-in configuration
PATCH/projects/{ref}/config/authChange the sign-in configuration
GET/projects/{ref}/config/auth/custom-providersCustom sign-in providers
POST/projects/{ref}/config/auth/custom-providersCreate a custom sign-in provider
PUT/projects/{ref}/config/auth/custom-providers/{identifier}Change a custom sign-in provider
PATCH/projects/{ref}/config/auth/custom-providers/{identifier}Turn a custom sign-in provider off or on
DELETE/projects/{ref}/config/auth/custom-providers/{identifier}Delete a custom sign-in provider
POST/projects/{ref}/config/auth/copy-from-importCopy sign-in settings from an import
GET/projects/{ref}/config/auth/third-party-authThird-party auth
POST/projects/{ref}/config/auth/third-party-authAdd third-party auth
DELETE/projects/{ref}/config/auth/third-party-auth/{id}Remove third-party auth
GET/projects/{ref}/analyticsWeb analytics
GET/projects/{ref}/analytics/breakdownWeb Analytics breakdown
PUT/projects/{ref}/analytics/previewsCount preview visits
GET/projects/{ref}/analytics/exportExport Web Analytics
GET/projects/{ref}/analytics/reportsCustom reports
GET/projects/{ref}/analytics/reports/{id}Show a custom report
POST/projects/{ref}/analytics/reportsSave a custom report
PATCH/projects/{ref}/analytics/reportsSave a custom report
DELETE/projects/{ref}/analytics/reports/{id}Delete a custom report
GET/projects/{ref}/speed-insightsSpeed Insights
PUT/projects/{ref}/insightsTurn analytics on or off
GET/projects/{ref}/remote-cacheRemote cache
DELETE/projects/{ref}/remote-cache/artifactsClear the remote cache
GET/projects/{ref}/flagsFeature flags
GET/projects/{ref}/flags/evaluateEvaluate a feature flag
PUT/projects/{ref}/flags/{key}/environments/{environment}Turn a feature flag on or off
GET/projects/{ref}/edge-configRead edge config
GET/projects/{ref}/edge-config/tokensEdge config read tokens
POST/projects/{ref}/edge-config/tokensCreate an edge config read token
DELETE/projects/{ref}/edge-config/tokens/{id}Revoke a read token
PUT/projects/{ref}/edge-config/tokens/{id}/connections/{ref}Connect a read token to a project
DELETE/projects/{ref}/edge-config/tokens/{id}/connections/{ref}Disconnect a read token
GET/projects/{ref}/edge-config/historyEdge config history
PATCH/projects/{ref}/edge-configChange edge config
GET/projects/{ref}/rolling-releasesRolling releases
PUT/projects/{ref}/rolling-releasesRolling release settings
POST/projects/{ref}/rolling-releases/{action}Advance a rolling release
GET/projects/{ref}/kvKV keys
GET/projects/{ref}/kv/{id}Read a KV value
PUT/projects/{ref}/kv/{id}Save a KV value
DELETE/projects/{ref}/kv/{id}Delete a KV key
GET/projects/{ref}/ai-gatewayAI gateway
PUT/projects/{ref}/ai-gatewayAI gateway settings
GET/projects/{ref}/ai-gateway/logsAI gateway calls
POST/projects/{ref}/ai-gateway/providersAdd an AI provider
PATCH/projects/{ref}/ai-gateway/providers/{id}Turn an AI provider off or on
DELETE/projects/{ref}/ai-gateway/providers/{id}Remove an AI provider
PUT/projects/{ref}/ai-gateway/budgetsAI gateway budgets
GET/projects/{ref}/ai-gateway/keysAI gateway keys
POST/projects/{ref}/ai-gateway/keysCreate an AI gateway key
PUT/projects/{ref}/ai-gateway/keys/{id}/limitsAI gateway key limits
PUT/projects/{ref}/ai-gateway/aliasesAI model aliases
PUT/projects/{ref}/ai-gateway/hooksAI gateway hooks
PATCH/projects/{ref}/ai-gateway/keys/{id}AI gateway key budgets
DELETE/projects/{ref}/ai-gateway/keys/{id}Revoke an AI gateway key
GET/projects/{ref}/ai-gateway/spendAI gateway spend
GET/projects/{ref}/middlewareMiddleware settings
PUT/projects/{ref}/middlewareChange middleware settings
GET/projects/{ref}/branchesDatabase branches
POST/projects/{ref}/branchesCreate a database branch
PUT/projects/{ref}/branchingAutomatic database branches
POST/projects/{ref}/branches/{name}/mergeMerge a database branch
POST/projects/{ref}/branches/{name}/resetReset a database branch
DELETE/projects/{ref}/branches/{name}Delete a database branch
GET/projects/{ref}/queuesQueues
GET/projects/{ref}/queues/metricsQueue metrics
POST/projects/{ref}/queuesCreate a queue
PUT/projects/{ref}/queues/apiOpen queues to the data API
DELETE/projects/{ref}/queues/{name}Delete a queue
POST/projects/{ref}/queues/{name}/purgeEmpty a queue
POST/projects/{ref}/queues/{name}/messagesSend a message to a queue
GET/projects/{ref}/queues/{name}/messagesLook at queue messages
GET/projects/{ref}/vaultVault secrets
POST/projects/{ref}/vaultStore a Vault secret
PATCH/projects/{ref}/vault/{name}Change a Vault secret
DELETE/projects/{ref}/vault/{name}Delete a Vault secret
GET/projects/{ref}/network-restrictionsDatabase network restrictions
PUT/projects/{ref}/network-restrictionsChange database network restrictions

GET /api/v1/projects/{ref}/transfer

Organizations to transfer to The other organizations this project can move to (ones where you are an owner or admin), with id, name and plan. can_transfer says whether you can move it, which needs you to be an owner of its current organization. Put an id in organisation of transfer_project.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_transfer_targets
ResponseObject: ref, name, organisation, organisation_id, targets
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/transfer" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/transfer', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/transfer

Transfer project Moves the project to another organization. You must be an owner of its current organization and an owner or admin of the receiving one, and the receiving plan must have room for another project. From the move on, usage is billed to the receiving organization, so this month's bill splits at that moment. Access given to members of the old organization is removed, and only an owner of the receiving organization can move it back. Addresses, keys, data and deployments stay as they are. Use it only when the user clearly asks, and put the project name exactly in confirm.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooltransfer_project
Body limit256 KiB
ResponseObject: ref, name, organisation, organisation_id, transferred, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
organisationbodystringYesThe receiving organization id, from list_transfer_targets. Its name also works when only one has it.
confirmbodystringYesThe project name. It must be exactly the name from list_projects.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/transfer" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"organisation":"My team","confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/transfer', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "organisation": "My team",
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/server

Dedicated server Shows the project's dedicated server: size (tier), location, status, address, monthly price, standby, automatic failover, restore days, when it may next move, and jobs in progress. server is null when the project uses the shared server; offered is false while dedicated servers are not open.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_server
ResponseObject: ref, offered, server, standby, options, next_move_at, jobs
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/server

Rent a dedicated server Rents a server for this project alone. Show sizes and monthly prices per location with list_server_sizes first, and pass only what the user chose. Usually ready within 10 minutes; the database and app move onto it, then the address switches. From the Pro plan. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrequest_server
Body limit256 KiB
ResponseObject: job, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tierbodystring, pattern ^[0-9]+gb$YesServer size: a tier from list_server_sizes (for example 4gb).
regionbodystring, pattern ^[a-z]{2}-[a-z]+-[0-9]$NoLocation; the project's own when absent. A region from list_server_sizes.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"tier":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "tier": "value"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/server

Change the server size Changes the dedicated server's size. when=now does it now, tonight between 03:00 and 05:00 in the server location's time. Without a standby the server does not answer for 3 to 5 minutes; with one, only the few seconds of the switch. Changing size is free; the monthly price is the new size's (list_server_sizes). Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolresize_server
Body limit256 KiB
ResponseObject: job, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
tierbodystring, pattern ^[0-9]+gb$YesServer size: a tier from list_server_sizes (for example 4gb).
whenbodystring, now | tonightNonow (default) or tonight (03:00 to 05:00).
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"tier":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "tier": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/server

Give back the dedicated server Gives the dedicated server back: a last backup, the database and app moved to the shared server, then the server (and its standby) deleted. Writes pause during the move. Use it only when the user asks, and put the ref exactly in confirm. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrelease_server
Body limit256 KiB
ResponseObject: job, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
confirmbodystringYesThe project ID (ref). Must equal ref.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/server/standby

Standby server Turns a standby server of the same size in the same location on or off. It replicates the database continuously and costs the same as its size a month (list_server_sizes). With auto_failover=true the project switches to it on its own once the primary has not answered for 60 seconds (needs a standby). Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_standby
Body limit256 KiB
ResponseObject: job, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
standbybodybooleanYesWhether to keep a standby server.
auto_failoverbodybooleanNoAutomatic failover. false by default.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server/standby" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"standby":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server/standby', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "standby": true
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/server/move

Move the server Moves the dedicated server to another location. Free, once a month (next_move_at). A server is made there and replicated; writes pause for a few seconds when the address switches. The old server is kept for 7 days. The monthly price is the new location's (list_server_sizes). Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolmove_server
Body limit256 KiB
ResponseObject: job, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
regionbodystring, pattern ^[a-z]{2}-[a-z]+-[0-9]$YesLocation: a region from list_server_sizes (for example kr-seoul-1).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server/move" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"region":"kr"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server/move', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "region": "kr"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/server/restore-days

Restore days Sets how far back the project can be restored to a moment: 7 or 28 days. 28 is only on a dedicated server and adds a monthly charge (get_server options, get_billing). Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_restore_days
Body limit256 KiB
ResponseObject: job, note
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
daysbodyinteger, 7 | 28Yes7 or 28.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server/restore-days" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"days":7}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/server/restore-days', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "days": 7
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/s3-keys

S3 access keys The S3 endpoint (endpoint, region, force_path_style), the TUS address for resumable uploads, and each S3 access key's name, access key ID, last use and revoked state. The secret access key is never returned.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_s3_keys
ResponseObject: keys
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/s3-keys" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/s3-keys', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/s3-keys

Create an S3 access key Creates an access key ID and secret access key that the AWS SDKs, the aws CLI or rclone sign requests to /storage/v1/s3 with. It acts as the service role and bypasses access rules, so keep it on a server. The secret access key is in this response only and cannot be seen again. Only owners and admins can create keys, up to 20 per project.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_s3_key
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystring, min length 1, max length 60YesWhat the key is for. For example: backup server
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/s3-keys" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/s3-keys', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/s3-keys/{id}

Revoke an S3 access key Revokes one S3 access key. Requests signed with it are refused within 30 seconds. Other keys are not affected. This cannot be undone.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrevoke_s3_key
ResponseObject: revoked, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe keys[].id from list_s3_keys.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/s3-keys/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/s3-keys/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/storage/policies

Create a file access rule Adds one access rule to the file table (objects). Takes name, command (select, insert, update, delete or all), roles, using and check. The project login does not own the file table, so rules cannot be made with SQL; use this route.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$Yes
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/storage/policies" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/storage/policies', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({}),
})
const result = await response.json()

POST /api/v1/projects/{ref}/storage/copy-from-import

Copy files from an import Copies a Supabase project's buckets and files into a project here (ref), read with a Supabase account connected in import_sources. A bucket missing here is made with the source's public flag, size limit and types; files go into the same store upload_object writes to, held to the same limits. A file already here with the same size is passed over, so calling again is safe. One call copies up to 200 files (max_files) within 45 seconds; when files remain, call again with the answer's next until done is true. The answer gives each bucket's created, copied, skipped, failed and bytes, and the first few failed files. The Supabase key never appears in the conversation. Bucket access rules are not copied. dry_run shows the counts and changes nothing. Owners, admins and developers only.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcopy_storage_from_import
Body limit256 KiB
ResponseObject: ref, source_project, dry_run, done, next, buckets, failures
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
bucketsbodyarray<string>NoSupabase bucket names to copy. Empty means all of them.
renamebodyobjectNoBucket names to change, {"Supabase bucket": "bucket here"}. Without it a name is lowercased with spaces as - (My Files becomes my-files).
dry_runbodybooleanNotrue shows the buckets that would be made and the files that would be copied, without changing anything.
max_filesbodyinteger, min 1, max 1000NoThe most files to copy in this call (default 200, at most 1000).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/storage/copy-from-import" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"buckets":["value"],"rename":{},"dry_run":true,"max_files":1}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/storage/copy-from-import', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "buckets": [
      "value"
    ],
    "rename": {},
    "dry_run": true,
    "max_files": 1
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/deployment-retention

Deployment retention Returns how many days canceled, failed, preview and production deployments are kept: the days in force (days), the project's own number (project_days, null when it follows the plan) and the plan default (plan_days). Also what is kept whatever the period (always_kept) and how many days a deleted deployment can be restored (restore_days).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_deployment_retention
ResponseObject: ref, canceled, error, preview, production, always_kept, restore_days
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployment-retention" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployment-retention', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/deployment-retention

Change deployment retention Changes how long each kind of deployment is kept. A deployment past its period is deleted and can be restored with restore_deployment for 3 days. Recent and served deployments are kept whatever the period. Only project admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_deployment_retention
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
canceled_daysbodystring, min 1, max 3650NoDays to keep canceled or skipped deployments (1 to 3650). null for the plan default. Left out: kept as it is.
error_daysbodystring, min 1, max 3650NoDays to keep failed deployments (1 to 3650). null for the plan default. Left out: kept as it is.
preview_daysbodystring, min 1, max 3650NoDays to keep preview deployments (1 to 3650). null for the plan default. Left out: kept as it is.
production_daysbodystring, min 1, max 3650NoDays to keep production deployments (1 to 3650). null for the plan default. Left out: kept as it is.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployment-retention" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"canceled_days":"value","error_days":"value","preview_days":"value","production_days":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/deployment-retention', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "canceled_days": "value",
    "error_days": "value",
    "preview_days": "value",
    "production_days": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/schemas

Project schemas The schemas the project made with SQL besides public (create schema blackquant), and whether the data API exposes each (exposed). In the database the name is app_<ref>_<name>, but SQL and clients use the name as written.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_schemas
ResponseObject: ref, always_exposed, schemas, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/schemas" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/schemas', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/schemas/exposed

Set exposed schemas Sets, as a whole list, the schemas the data API (/rest/v1) answers for besides public (Supabase's Exposed schemas). Use an exposed schema with the Accept-Profile / Content-Profile headers, supabase-js db: { schema } or .schema(). Row level security and the anon, authenticated and service_role privileges apply exactly as in public. An empty list exposes only public. Takes effect within 30 seconds. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_exposed_schemas
Body limit256 KiB
ResponseObject: ref, exposed, schemas, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
exposedbodyarray<string>YesThe schema names to expose (name from list_schemas). public is always exposed.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/schemas/exposed" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"exposed":["value"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/schemas/exposed', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "exposed": [
      "value"
    ]
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/data-api

Data API settings Whether the data API (/rest/v1) is on, the most rows one read returns (max_rows, null for no cap), the exposed schemas and the search path. search_path_choices are the schemas the search path can take: public, the schemas the project made with create schema, and extensions.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_data_api_settings
ResponseObject: ref, enabled, max_rows, exposed_schemas, search_path, search_path_choices
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/data-api" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/data-api', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/data-api

Change the Data API settings Turns the data API (/rest/v1) on or off and sets the row cap and the search path. Only what is given changes. A schema on the search path comes after the one asked for, so functions and views find its tables and functions without the schema name. Takes effect within 30 seconds. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_data_api_settings
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanNofalse makes every /rest/v1 request answer 404.
max_rowsbodynumber, min 1, max 1000000NoThe most rows one read returns. null for no cap.
search_pathbodyarray<string>, max items 20NoThe schemas to put on the search path (name from search_path_choices in get_data_api_settings). An empty list puts none.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/data-api" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true,"max_rows":1,"search_path":["value"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/data-api', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true,
    "max_rows": 1,
    "search_path": [
      "value"
    ]
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/{param0}/{param1}

Add SAML SSO Registers a SAML IdP from its metadata (an https URL or the XML). The metadata needs an HTTP-Redirect sign-in address and a signing certificate. signInWithSSO with an email domain in domains goes to this IdP. A domain belongs to one IdP only. A new IdP can be added on the Enterprise plan only (new_providers_allowed in list_sso_providers); IdPs already added keep working on any plan.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooladd_sso_provider
Body limit256 KiB
ResponseObject: id, entity_id, metadata_url, domains, attribute_mapping, created_at, updated_at
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
param0pathstringYes
param1pathstringYes
metadata_urlbodystringNoAn https URL. Give this or metadata_xml.
metadata_xmlbodystringNoThe IdP's metadata XML. Give this or metadata_url.
domainsbodyarray<string>NoEmail domains, e.g. ["example.com"].
attribute_mappingbodyobjectNoGoTrue form: {"keys": {"email": {"name": "mail"}}}.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/value/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"metadata_url":"value","metadata_xml":"value","domains":["value"],"attribute_mapping":{}}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/value/value', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "metadata_url": "value",
    "metadata_xml": "value",
    "domains": [
      "value"
    ],
    "attribute_mapping": {}
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/{param0}/{param1}/{param2}

Add SAML SSO Registers a SAML IdP from its metadata (an https URL or the XML). The metadata needs an HTTP-Redirect sign-in address and a signing certificate. signInWithSSO with an email domain in domains goes to this IdP. A domain belongs to one IdP only. A new IdP can be added on the Enterprise plan only (new_providers_allowed in list_sso_providers); IdPs already added keep working on any plan.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooladd_sso_provider
Body limit256 KiB
ResponseObject: id, entity_id, metadata_url, domains, attribute_mapping, created_at, updated_at
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
param0pathstringYes
param1pathstringYes
param2pathstringYes
metadata_urlbodystringNoAn https URL. Give this or metadata_xml.
metadata_xmlbodystringNoThe IdP's metadata XML. Give this or metadata_url.
domainsbodyarray<string>NoEmail domains, e.g. ["example.com"].
attribute_mappingbodyobjectNoGoTrue form: {"keys": {"email": {"name": "mail"}}}.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/value/value/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"metadata_url":"value","metadata_xml":"value","domains":["value"],"attribute_mapping":{}}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/value/value/value', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "metadata_url": "value",
    "metadata_xml": "value",
    "domains": [
      "value"
    ],
    "attribute_mapping": {}
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/config/auth

Show the whole sign-in configuration Returns this project's whole sign-in configuration under the same keys as Supabase's auth config, in lower case: sign-ups (disable_signup, mailer_autoconfirm, security_manual_linking_enabled, external_anonymous_users_enabled), the email provider and password rules, phone and the SMS provider (sms_*), SAML (saml_*), Web3, and for each social provider external_<provider>_enabled, client_id, secret, url and email_optional, and the custom providers switch. A secret is given as its last four characters, like ••••abcd; handing that back to update_auth_config leaves the stored one alone. callback_url is the address to paste into a provider's console.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_auth_config
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/config/auth

Change the sign-in configuration Changes only the keys given (GoTrue's upper-case names work too), checked by the same rules as the console's Sign In / Providers page: a provider switched on needs what it needs (external_google_client_id, a Keycloak realm url), an Apple secret must be an ES256 JWT with more than 7 days left, and an SMS message must contain {{ .Code }}. If any of it is wrong nothing is saved, and each key's reason comes back. Kakao's consent items are external_kakao_scopes (profile_nickname, account_email, gender and so on, separated by commas), the Naver fields to keep are external_naver_scopes, and Google's and GitHub's additional scopes are external_<provider>_additional_scopes. An empty string removes a secret. Only owners, admins and developers can do this.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolupdate_auth_config
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({}),
})
const result = await response.json()

GET /api/v1/projects/{ref}/config/auth/custom-providers

Custom sign-in providers Shows the OAuth and OIDC providers the project defined itself (custom:<identifier>), the plan’s limit (max_providers) and the switch (enabled). The app signs in with signInWithOAuth({ provider: "custom:<identifier>" }).

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_custom_providers
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/config/auth/custom-providers

Create a custom sign-in provider Creates one OAuth or OIDC provider (custom:<identifier>) and turns it on, in the same place set_auth_provider writes. With provider_type oidc the issuer alone is enough: the endpoints and jwks_uri are read from {issuer}/.well-known/openid-configuration, and each id_token's signature, issuer, audience, expiry and nonce are checked against those keys. With oauth2, give the issuer or authorization_url and token_url; endpoints left empty are {issuer}/oauth/authorize, /oauth/token and /oauth/userinfo. claim_map says which field of the answer is id, email, email_verified, name, avatar and phone (dotted paths). PKCE is on by default. Refused past the plan’s limit.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolcreate_custom_provider
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
pkcebodybooleanNoOn by default; turn it off only if the provider refuses PKCE.
pkce_enabledbodystringNo
identifierbodystringYesLowercase letters, numbers and hyphens. custom: may be given or left off.
namebodystringYesThe display name, under 100 characters.
labelbodystringNo
provider_typebodystring, oidc | oauth2Yesoidc (endpoints discovered, id_token verified) or oauth2 (endpoints given, read through claim_map).
issuerbodystringNoThe provider's base URL.
discovery_urlbodystringNoFor oidc, when the discovery document is not at the standard path.
authorization_urlbodystringNoRequired for oauth2 without an issuer; for oidc, empty is discovered.
authorize_urlbodystringNo
token_urlbodystringNoRequired for oauth2 without an issuer; for oidc, empty is discovered.
userinfo_urlbodystringNoWhere the user is read; for oidc, empty is discovered.
jwks_uribodystringNoFor oidc, to verify ID tokens; empty is discovered.
client_idbodystringYesThe client ID.
client_secretbodystringYesThe client secret.
scopesbodyarray<string>NoFor example ["openid", "email", "profile"].
email_optionalbodybooleanNoAllow users without an email.
claim_mapbodyobjectNoWhich field of the answer is id, email, email_verified, name, avatar and phone, as dotted paths. Left out: the OpenID standard names.
claimsbodystringNo
token_authbodystring, body | basicNoSend client_secret in the body (body, the default) or as HTTP Basic (basic).
enabledbodybooleanNoOn by default.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"identifier":"value","name":"my-name","provider_type":"oidc","client_id":"CLIENT_ID","client_secret":"CLIENT_SECRET"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "identifier": "value",
    "name": "my-name",
    "provider_type": "oidc",
    "client_id": "CLIENT_ID",
    "client_secret": "CLIENT_SECRET"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/config/auth/custom-providers/{identifier}

Change a custom sign-in provider Changes one custom provider. The identifier cannot change. An empty client_secret keeps the stored one.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolupdate_custom_provider
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
identifierpathstringYesThe identifier from list_custom_providers.
pkcebodybooleanNoOn by default; turn it off only if the provider refuses PKCE.
pkce_enabledbodystringNo
identifierbodystringYesThe identifier from list_custom_providers.
namebodystringYesThe display name, under 100 characters.
labelbodystringNo
provider_typebodystring, oidc | oauth2Yesoidc (endpoints discovered, id_token verified) or oauth2 (endpoints given, read through claim_map).
issuerbodystringNoThe provider's base URL.
discovery_urlbodystringNoFor oidc, when the discovery document is not at the standard path.
authorization_urlbodystringNoRequired for oauth2 without an issuer; for oidc, empty is discovered.
authorize_urlbodystringNo
token_urlbodystringNoRequired for oauth2 without an issuer; for oidc, empty is discovered.
userinfo_urlbodystringNoWhere the user is read; for oidc, empty is discovered.
jwks_uribodystringNoFor oidc, to verify ID tokens; empty is discovered.
client_idbodystringYesThe client ID.
client_secretbodystringNoEmpty keeps the stored secret.
scopesbodyarray<string>NoFor example ["openid", "email", "profile"].
email_optionalbodybooleanNoAllow users without an email.
claim_mapbodyobjectNoWhich field of the answer is id, email, email_verified, name, avatar and phone, as dotted paths. Left out: the OpenID standard names.
claimsbodystringNo
token_authbodystring, body | basicNoSend client_secret in the body (body, the default) or as HTTP Basic (basic).
enabledbodybooleanNoTurn it on or off as it is saved.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"identifier":"value","name":"my-name","provider_type":"oidc","client_id":"CLIENT_ID"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers/value', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "identifier": "value",
    "name": "my-name",
    "provider_type": "oidc",
    "client_id": "CLIENT_ID"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/config/auth/custom-providers/{identifier}

Turn a custom sign-in provider off or on Turns one custom provider off or on. Off, existing sessions stay, but new sign-ins fail until it is turned on again.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolset_custom_provider_enabled
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
identifierpathstringYesThe identifier from list_custom_providers.
enabledbodybooleanYestrue to turn it on, false to turn it off.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers/value', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/config/auth/custom-providers/{identifier}

Delete a custom sign-in provider Deletes one custom provider. It cannot be undone, and users who signed in with it can no longer get in.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolremove_custom_provider
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
identifierpathstringYesThe identifier from list_custom_providers.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/custom-providers/value', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/config/auth/copy-from-import

Copy sign-in settings from an import Copies a Supabase project's sign-in setup into a project here (ref), read with a Supabase account connected in import_sources: each social provider switched on there (client ID, secret and the rest, checked as update_auth_config checks them), the redirect allow list (uri_allow_list, as add_redirect_url adds it) and site_url only when this project's is empty. Secrets never appear in the conversation; the answer says per provider whether it was copied and why not. Sign-in fails until the answer's callback_url is registered in each provider's console. dry_run shows what would be copied and changes nothing. Owners, admins and developers only.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcopy_auth_from_import
Body limit256 KiB
ResponseObject: ref, source_project, dry_run, callback_url, providers, site_url, redirect_urls, console_url
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
providersbodyarray<string>NoProviders to copy (google, kakao, apple, x...). Empty means every one switched on at Supabase.
dry_runbodybooleanNotrue shows what would be copied without changing anything.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/copy-from-import" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"providers":["google"],"dry_run":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/copy-from-import', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "providers": [
      "google"
    ],
    "dry_run": true
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/config/auth/third-party-auth

Third-party auth The other identity services whose tokens the Data API takes: Firebase, Clerk, WorkOS, Auth0, Amazon Cognito and OpenID issuers.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_third_party_auth
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/third-party-auth" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/third-party-auth', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/config/auth/third-party-auth

Add third-party auth Takes another identity service's JWTs as sign-ins to this project. value depends on type: firebase the project ID, clerk https://clerk.<domain> or https://<name>.clerk.accounts.dev, workos the issuer URL, auth0 the tenant name (the start of <name>.auth0.com), cognito the user pool ID (with region), oidc the issuer URL. For Auth0 and Cognito, giving audiences is recommended.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP tooladd_third_party_auth
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
typebodystring, firebase | clerk | workos | auth0 | cognito | oidcYesfirebase, clerk, workos, auth0, cognito or oidc.
valuebodystringYesWhat the type takes, as above.
regionbodystringNoCognito's AWS region, e.g. ap-northeast-2.
audiencesbodyarray<string>NoValues a token’s aud must contain.
jwks_urlbodystringNoFor oidc, a JWKS address to use instead of discovery.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/third-party-auth" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"type":"firebase","value":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/third-party-auth', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "type": "firebase",
    "value": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/config/auth/third-party-auth/{id}

Remove third-party auth Removes one third-party auth integration. Requests from users signed in with that service are refused with 401 within 30 seconds.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolremove_third_party_auth
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id from list_third_party_auth.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/third-party-auth/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/config/auth/third-party-auth/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/analytics

Web analytics Returns page views and visitors per day, with the top pages, referrers, countries, devices, browsers and operating systems. Nothing that identifies a person is stored, so these are totals only.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_analytics
ResponseObject: ref, settings
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
daysqueryinteger, 1 | 7 | 30 | 90NoThe period in days. 7 by default.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics?days=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics?days=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/analytics/breakdown

Web Analytics breakdown Returns campaigns (utm_source, utm_medium, utm_campaign), routes, custom events and how many bots were filtered. With event, also that event's values per property. Totals only.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_analytics_breakdown
ResponseObject: ref, days, utm_source, utm_medium, utm_campaign, routes, events, bots_filtered, crawlers
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
daysqueryinteger, 1 | 7 | 30 | 90NoThe period. 7 by default.
eventquerystring, max length 100NoThe event whose properties to break down.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/breakdown?days=1&event=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/breakdown?days=1&event=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/analytics/previews

Count preview visits Sets whether visits to preview deployments and dpl- addresses count in Web Analytics. Off (the default), previews get no script and what they send is dropped. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_analytics_previews
Body limit256 KiB
ResponseObject: ref, include_previews, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
include_previewsbodybooleanYesCount previews too.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/previews" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"include_previews":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/previews', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "include_previews": true
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/analytics/export

Export Web Analytics Returns the period's totals as a CSV string, by day, dimension and value.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolexport_analytics
ResponseObject: ref, days, format, csv
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
daysqueryinteger, 1 | 7 | 30 | 90NoThe period. 7 by default.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/export?days=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/export?days=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/analytics/reports

Custom reports Returns the saved Web Analytics reports and the names of the charts a report can hold.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_analytics_reports
ResponseObject: ref, widgets, reports
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/analytics/reports/{id}

Show a custom report Returns one saved report with its period's totals, breakdowns and speed.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_analytics_report
ResponseObject: ref, report, totals, breakdown, speed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id list_analytics_reports returned.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports/ID', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/analytics/reports

Save a custom report Creates a report, or changes the one id names. Developers and above can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolsave_analytics_report
Body limit256 KiB
ResponseObject: ref, report
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystring, min length 1, max length 80YesThe name.
daysbodyinteger, 1 | 7 | 30 | 90NoThe period. 7 by default.
widgetsbodyarray<string>, views | visitors | path | referrer | country | device | browser | os | utm_source | utm_medium | utm_campaign | route | event | bot | vitals, min items 1, max items 16YesThe charts and tables to show.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","widgets":["views"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "widgets": [
      "views"
    ]
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/analytics/reports

Save a custom report Creates a report, or changes the one id names. Developers and above can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolsave_analytics_report
Body limit256 KiB
ResponseObject: ref, report
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystring, min length 1, max length 80YesThe name.
daysbodyinteger, 1 | 7 | 30 | 90NoThe period. 7 by default.
widgetsbodyarray<string>, views | visitors | path | referrer | country | device | browser | os | utm_source | utm_medium | utm_campaign | route | event | bot | vitals, min items 1, max items 16YesThe charts and tables to show.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","widgets":["views"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "widgets": [
      "views"
    ]
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/analytics/reports/{id}

Delete a custom report Deletes the report only; the analytics stay as they are. Developers and above can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolremove_analytics_report
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id list_analytics_reports returned.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/analytics/reports/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/speed-insights

Speed Insights Returns the 75th percentile and rating of the Core Web Vitals (LCP, INP, CLS, FCP, TTFB) that real visitors measured, and the slowest pages.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_speed_insights
ResponseObject: ref, settings
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
daysqueryinteger, 1 | 7 | 30 | 90NoThe period in days. 7 by default.
devicequerystring, all | desktop | mobileNoThe device. all by default.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/speed-insights?days=1&device=all" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/speed-insights?days=1&device=all', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/insights

Turn analytics on or off Turns Web Analytics and Speed Insights on or off. When on, the site's HTML gets the /_oneend/insights/script.js script. Apps that use Vercel's @vercel/analytics and @vercel/speed-insights are counted in the same place with no code change. No cookies are used and no address is stored. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_insights
Body limit256 KiB
ResponseObject: ref, settings, script, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
analyticsbodybooleanNoWeb Analytics.
speed_insightsbodybooleanNoSpeed Insights.
vitals_sample_percentbodyinteger, min 1, max 100NoThe share of visits whose speed is measured.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/insights" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"analytics":true,"speed_insights":true,"vitals_sample_percent":1}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/insights', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "analytics": true,
    "speed_insights": true,
    "vitals_sample_percent": 1
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/remote-cache

Remote cache Returns the Turborepo remote cache's address, how many outputs it holds and their size, the last 30 days' hits and uploads, and this month's metered storage (remote_cache_gb_hour) with its price. Downloads are counted as egress_gb.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_remote_cache
ResponseObject: ref, api, artifacts, bytes, last_30_days, metered
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/remote-cache" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/remote-cache', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/remote-cache/artifacts

Clear the remote cache Deletes every stored output. The next builds fill it again, and the metered storage drops by what was cleared. Developers and above can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolclear_remote_cache
ResponseObject: ref, cleared
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/remote-cache/artifacts" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/remote-cache/artifacts', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/flags

Feature flags Returns the project's feature flags together with the flag-shaped values kept in its edge config. source says where each is kept and shadows whether it hides an edge config value of the same key. Where both have a key, the flag decides.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_flags
ResponseObject: ref, flags, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/flags" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/flags', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/flags/evaluate

Evaluate a feature flag Returns what the app would get: with a context (e.g. {"user": {"id": "u1"}}) and an environment, the flag's rules decide; with no flag of that key, the edge config value does. source says which decided.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolevaluate_flag
ResponseObject: ref, key, environment, note
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
keyquerystring, max length 256NoThe flag key.
environmentquerystringNoThe environment. production by default.
contextqueryobjectNoThe context to evaluate for (a JSON object).
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/flags/evaluate?key=API_URL&environment=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/flags/evaluate?key=API_URL&environment=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/flags/{key}/environments/{environment}

Turn a feature flag on or off Turns a flag on or off in one environment. Production takes owners and admins; other environments, developers and above.

NameDescription
Token scopewrite
RolesAny member of the organisation
MCP toolset_flag_enabled
Body limit256 KiB
ResponseObject: ref, key, environment, enabled, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
keypathstring, max length 64YesThe flag key.
environmentpathstringYesThe environment.
enabledbodybooleanYesOn.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/flags/API_URL/environments/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/flags/API_URL/environments/value', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/edge-config

Read edge config Returns the project's edge config (values and feature flags that change without a deploy), with its version, size and limits.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_edge_config
ResponseObject: ref, version, bytes, limits, items, read_url
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/edge-config/tokens

Edge config read tokens Returns the read tokens other projects read this project's edge config with, the projects each is connected to (variable name, environments), and other projects' edge configs connected to this one. The tokens themselves are not returned.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_edge_config_tokens
ResponseObject: ref, tokens, connected_here
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/edge-config/tokens

Create an edge config read token Creates a read token and, with connect, connects it to a project in the same organization. From its next deployment, that project gets the connection string (https://<ref>.<zone>/config/v1?token=…) as a variable. The token is shown only this once. Project admins only.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_edge_config_token
Body limit256 KiB
ResponseObject: ref, id, name, token, connection_string, connected, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
connectbodyobjectNoA project in the same organization to connect right away: ref, the variable name (GLOBAL_CONFIG by default) and targets (all three environments by default; production only by that project's owners and admins).
namebodystring, min length 1, max length 60YesThe token's name.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/edge-config/tokens/{id}

Revoke a read token Revokes a read token. It stops reading at once, and connected projects lose the variable with their next deployment.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrevoke_edge_config_token
ResponseObject: ref, id, revoked
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe read token id, from list_edge_config_tokens.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/edge-config/tokens/{id}/connections/{ref}

Connect a read token to a project Connects a read token to another project in the organization as a variable in its deployments. If already connected, changes the variable name and environments. Takes a developer or above of that project (owners and admins for production).

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolconnect_edge_config_token
Body limit256 KiB
ResponseObject: ref, id, connected
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe read token id, from list_edge_config_tokens.
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
variablebodystring, pattern ^[A-Za-z_][A-Za-z0-9_]{0,63}$NoThe variable deployments read. GLOBAL_CONFIG by default.
targetsbodyarray<string>, production | preview | development, min items 1NoEnvironments that get the variable. All three by default; production only by that project's owners and admins.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens/ID/connections/abcdefghijklmnopqrst" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"variable":"value","targets":["production"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens/ID/connections/abcdefghijklmnopqrst', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "variable": "value",
    "targets": [
      "production"
    ]
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/edge-config/tokens/{id}/connections/{ref}

Disconnect a read token Takes a read token out of one project. The variable leaves with that project's next deployment.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldisconnect_edge_config_token
ResponseObject: ref, id, disconnected
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe read token id, from list_edge_config_tokens.
refpathstring, pattern ^[a-z]{20}$YesA project in the same organization to connect.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens/ID/connections/abcdefghijklmnopqrst" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/tokens/ID/connections/abcdefghijklmnopqrst', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/edge-config/history

Edge config history Returns who changed which key, when, from what to what, most recent first.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_edge_config_history
ResponseObject: ref, changes
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
limitqueryinteger, min 1, max 500NoHow many. 50 by default.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/history?limit=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config/history?limit=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/edge-config

Change edge config Sets or deletes keys. Every server has the change within a few seconds, and it is kept in the history. A flag is true or false, or {"enabled": true, "percent": 20}, or {"enabled": true, "allow": ["user-1"]}. Developers and above can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_edge_config
Body limit256 KiB
ResponseObject: version, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
setbodyobjectNoKeys and values to set (values are JSON).
deletebodyarray<string>NoKeys to delete.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"set":{},"delete":["value"]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/edge-config', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "set": {},
    "delete": [
      "value"
    ]
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/rolling-releases

Rolling releases Returns the rolling release settings, the release in progress (the share of visitors the new deployment gets), and recent ones.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_rolling_releases
ResponseObject: ref, config, active, recent
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rolling-releases" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rolling-releases', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/rolling-releases

Rolling release settings Decides whether new production deployments go out in stages. stages is a rising list of shares (for example [10, 50]), stage_minutes the length of each, and with auto_advance false each stage waits for approval. Above abort_error_rate (0.05 is 5%) the release is rolled back automatically. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_rolling_releases
Body limit256 KiB
ResponseObject: ref, config, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanNoOn.
stagesbodyarray<integer>NoThe share at each stage (%).
stage_minutesbodyinteger, min 1, max 1440NoStage length in minutes.
auto_advancebodybooleanNoMove on automatically as time passes.
abort_error_ratebodynumber, min 0.001, max 1NoThe 5xx rate that stops it automatically. null turns it off.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rolling-releases" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true,"stages":["value"],"stage_minutes":1,"auto_advance":true,"abort_error_rate":0.001}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rolling-releases', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true,
    "stages": [
      "value"
    ],
    "stage_minutes": 1,
    "auto_advance": true,
    "abort_error_rate": 0.001
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/rolling-releases/{action}

Advance a rolling release Moves the release in progress to its next stage. From the last stage it goes to everybody. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooladvance_rolling_release
Body limit256 KiB
ResponseObject: ref, release
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
actionpathstringYes
reasonbodystringNo
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rolling-releases/value" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"reason":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/rolling-releases/value', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "reason": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/kv

KV keys Returns the KV store's keys by prefix, 50 at a time, with usage, limits and the REST address. Only roles that may see secrets can do this.

NameDescription
Token scoperead
Rolessecrets.read owner or admin or developer
MCP toollist_kv_keys
ResponseObject: ref, usage, keys, cursor, rest_url
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
prefixquerystringNoA prefix.
cursorquerystringNoThe cursor from the previous answer.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv?prefix=value&cursor=value" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv?prefix=value&cursor=value', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/kv/{id}

Read a KV value Returns one key's value and the seconds it has left (ttl, -1 for no expiry).

NameDescription
Token scoperead
Rolessecrets.read owner or admin or developer
MCP toolget_kv
ResponseObject: ref, key, value, ttl
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYes
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv/ID', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/kv/{id}

Save a KV value Stores a JSON value at a key. ttl is the seconds until it expires. Developers and above can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolset_kv
Body limit256 KiB
ResponseObject: ref, key, saved
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYes
valuebodystringYesThe value (JSON).
ttlbodyinteger, min 1NoSeconds until it expires.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"value":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv/ID', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "value": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/kv/{id}

Delete a KV key Deletes a key. Developers and above can do this.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_kv
ResponseObject: ref, key, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYes
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/kv/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/ai-gateway

AI gateway Returns the AI gateway's OpenAI-compatible address, its settings, its providers (keys as their last four characters only), and the last 30 days of use.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_ai_gateway
ResponseObject: ref, base_url, settings, providers, usage_30d, top_users_30d
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/ai-gateway

AI gateway settings Turns the AI gateway on or off and sets whether signed-in users may call it, the cache in seconds, the requests per minute, the fallback to the platform's provider (billed per token), and the daily requests and tokens one user of the app may use. The user is a signed-in user's id, or, with the secret key, the x-oneend-user header or the body's user. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_ai_gateway
Body limit256 KiB
ResponseObject: ref, settings, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanNoOn.
allow_usersbodybooleanNoAllow signed-in users.
cache_secondsbodyinteger, min 0, max 86400NoCache seconds.
rate_per_minutebodyinteger, min 1NoRequests per minute.
platform_fallbackbodybooleanNoFall back to the platform provider.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true,"allow_users":true,"cache_seconds":0,"rate_per_minute":1,"platform_fallback":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true,
    "allow_users": true,
    "cache_seconds": 0,
    "rate_per_minute": 1,
    "platform_fallback": true
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/ai-gateway/logs

AI gateway calls Returns recent calls with their provider, model, status, tokens, latency and whether they fell back. Conversations are not stored.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_ai_gateway_logs
ResponseObject: ref, logs
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
limitqueryinteger, min 1, max 500NoHow many. 50 by default.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/logs?limit=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/logs?limit=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/ai-gateway/providers

Add an AI provider Adds a provider with its key: OpenAI-compatible, Anthropic (translated to and from its Messages API) or Google Gemini. Only public https addresses are accepted. The key is sealed and never shown again. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooladd_ai_provider
Body limit256 KiB
ResponseObject: note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystring, pattern ^[a-z][a-z0-9-]{0,39}$YesA name; calling name/model calls this provider only.
kindbodystring, openai | anthropic | google | deepseek | openai-compatibleYesThe type.
base_urlbodystringNoThe address. Optional for openai, anthropic, google and deepseek.
keybodystringNoThe API key.
modelsbodyarray<string>NoThe models it takes. Empty for any.
prioritybodyinteger, min 0, max 1000NoPriority, lowest first.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/providers" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","kind":"openai"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/providers', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "kind": "openai"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/ai-gateway/providers/{id}

Turn an AI provider off or on Stops or resumes using a provider and keeps its settings and key. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_ai_provider_enabled
Body limit256 KiB
ResponseObject: ref, id, enabled
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id get_ai_gateway returned.
enabledbodybooleanYesUse it.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/providers/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/providers/ID', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/ai-gateway/providers/{id}

Remove an AI provider Removes a provider and its sealed key. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolremove_ai_provider
ResponseObject: ref, id, removed
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id get_ai_gateway returned.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/providers/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/providers/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/ai-gateway/budgets

AI gateway budgets Sets the project's AI gateway budgets in dollars (day, week and month, UTC). Once one is spent, calls are refused until the next period. A value left out is kept. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_ai_budgets
Body limit256 KiB
ResponseObject: ref, budgets, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
daily_usdbodystring, min 0, max 1000000NoThe daily (UTC) budget in dollars. null for none.
weekly_usdbodystring, min 0, max 1000000NoThe weekly (from Monday, UTC) budget in dollars. null for none.
monthly_usdbodystring, min 0, max 1000000NoThe monthly (UTC) budget in dollars. null for none.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/budgets" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"daily_usd":"value","weekly_usd":"value","monthly_usd":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/budgets', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "daily_usd": "value",
    "weekly_usd": "value",
    "monthly_usd": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/ai-gateway/keys

AI gateway keys Returns the gateway keys with their names, prefixes, budgets, this month's spend and when each was last used. Never the keys themselves.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_ai_gateway_keys
ResponseObject: ref, keys
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/ai-gateway/keys

Create an AI gateway key Creates a gateway key (bwai_…) for one app, or for one customer the project resells AI to, with its own budgets, tag and limits per minute. The key is in this answer once and never again. Keys with a tag (external_ref) do not count toward the 50 keys a project may have. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_ai_gateway_key
Body limit256 KiB
ResponseObject: ref, id, name, key, base_url, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystring, min length 1, max length 60YesThe key's name.
daily_usdbodystring, min 0, max 1000000NoThe daily (UTC) budget in dollars. null for none.
weekly_usdbodystring, min 0, max 1000000NoThe weekly (from Monday, UTC) budget in dollars. null for none.
monthly_usdbodystring, min 0, max 1000000NoThe monthly (UTC) budget in dollars. null for none.
external_refbodystring, max length 200NoWho the key stands for in the project's own books, such as a customer's user id. Sent to the hooks.
labelsbodyobjectNoUp to 20 free labels, such as {"tier": "1"}. Sent to the hooks.
rate_per_minutebodystring, min 1, max 100000NoRequests per minute. null for none.
tokens_per_minutebodystring, min 1, max 100000000NoTokens per minute, prompt and completion. null for none.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/ai-gateway/keys/{id}/limits

AI gateway key limits Changes one gateway key's limits on requests and tokens per minute. Every server counts the same minute. A value left out is kept; null removes the limit. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_ai_gateway_key_limits
Body limit256 KiB
ResponseObject: ref, id, limits, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id list_ai_gateway_keys returned.
rate_per_minutebodystring, min 1, max 100000NoRequests per minute. null for none.
tokens_per_minutebodystring, min 1, max 100000000NoTokens per minute, prompt and completion. null for none.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys/ID/limits" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"rate_per_minute":"value","tokens_per_minute":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys/ID/limits', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "rate_per_minute": "value",
    "tokens_per_minute": "value"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/ai-gateway/aliases

AI model aliases Sets the model names callers use (aliases) and the model each is sent to, each with an optional system prompt put first. Answers carry the alias as their model. The list given replaces the whole list. With aliases_only on, nothing but an alias can be asked for and /models lists only the aliases. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_ai_model_aliases
Body limit256 KiB
ResponseObject: ref, aliases_only, aliases, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
aliasesbodyarray<object>, max items 50YesEvery alias. An empty list removes them all.
aliases_onlybodybooleanNoAllow aliases only. Kept as it is when left out.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/aliases" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"aliases":[{"alias":"value","target":"hello","system_prompt":"value"}]}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/aliases', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "aliases": [
      {
        "alias": "value",
        "target": "hello",
        "system_prompt": "value"
      }
    ]
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/ai-gateway/hooks

AI gateway hooks Sets the https address asked before, and told after, every call made with a gateway key. If ai.before does not answer 2xx within timeout_ms, the call is refused (a 402 is passed on as a low balance). ai.after carries the usage, cost and status, and is sent again for a day until it is taken. The answer holds the signing secret. A null url turns hooks off. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_ai_gateway_hooks
Body limit256 KiB
ResponseObject: ref, url, timeout_ms, signing_secret, events, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
urlbodystring, max length 500NoThe hook address (https). null turns hooks off.
timeout_msbodyinteger, min 200, max 10000NoHow long ai.before may take to answer, in milliseconds. 2000 by default.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/hooks" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"url":"https://example.com/hook","timeout_ms":200}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/hooks', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "url": "https://example.com/hook",
    "timeout_ms": 200
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/ai-gateway/keys/{id}

AI gateway key budgets Changes one gateway key's budgets. A value left out is kept. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_ai_gateway_key_budgets
Body limit256 KiB
ResponseObject: ref, id, budgets
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id list_ai_gateway_keys returned.
daily_usdbodystring, min 0, max 1000000NoThe daily (UTC) budget in dollars. null for none.
weekly_usdbodystring, min 0, max 1000000NoThe weekly (from Monday, UTC) budget in dollars. null for none.
monthly_usdbodystring, min 0, max 1000000NoThe monthly (UTC) budget in dollars. null for none.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"daily_usd":"value","weekly_usd":"value","monthly_usd":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys/ID', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "daily_usd": "value",
    "weekly_usd": "value",
    "monthly_usd": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/ai-gateway/keys/{id}

Revoke an AI gateway key Revokes a gateway key. Every server refuses it within 30 seconds. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolrevoke_ai_gateway_key
ResponseObject: ref, id, revoked
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
idpathstringYesThe id list_ai_gateway_keys returned.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys/ID" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/keys/ID', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/ai-gateway/spend

AI gateway spend Returns today's, this week's and this month's spend against the budgets, and spend per day, model and key, in dollars. Calls to a provider with the project's own key are estimated at list price.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_ai_spend
ResponseObject: ref, spent_usd, budgets, days, models, keys
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
daysqueryinteger, min 1, max 400NoHow many days. 30 by default.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/spend?days=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/ai-gateway/spend?days=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/middleware

Middleware settings Returns whether the site's middleware (middleware.ts) runs and its time limit.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_middleware
ResponseObject: ref, enabled, timeout_ms
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/middleware" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/middleware', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/middleware

Change middleware settings Turns the site's middleware off or on and sets its time limit (100 to 5000ms). Off serves without middleware, with no new deploy. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_middleware
Body limit256 KiB
ResponseObject: ref, enabled, timeout_ms, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
enabledbodybooleanNoRun it.
timeout_msbodyinteger, min 100, max 5000NoTime limit (ms).
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/middleware" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"enabled":true,"timeout_ms":100}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/middleware', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "enabled": true,
    "timeout_ms": 100
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/branches

Database branches This project's database branches: name, the branch's own project ref (use it with every other tool), the git branch it is wired to, status (creating, ready, merging, resetting, failed, deleting), size and when it expires. Also returns the automatic branching settings.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_branches
ResponseObject: ref, automatic, lifetime_days, has_seed, branches
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/branches

Create a database branch Makes a preview database from the project's schema: the project's migrations (the history apply_migrations records) are replayed in order and the seed is run. Rows are copied only with with_data. A branch is a project with a ref of its own; use that ref with run_sql, get_project_keys and every other tool. With git_branch, that branch's preview deployments use this database. Available from the Pro plan.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_branch
Body limit256 KiB
ResponseObject: ref, branch, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringNoThe branch name (lower-case letters, digits and -, up to 30). Made from git_branch when absent.
git_branchbodystringNoThe git branch to wire it to. That branch's preview deployments get this database's address, keys and connection string.
with_seedbodybooleanNofalse to skip the seed.
with_databodybooleanNotrue to copy the project's rows too, instead of replaying the migrations and running the seed. Files and their rows are not copied. Refused when the data is past 512MB. Progress is under copy in list_branches.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","git_branch":"value","with_seed":true,"with_data":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "git_branch": "value",
    "with_seed": true,
    "with_data": true
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/branching

Automatic database branches Whether a database branch is made automatically for every git branch deployed as a preview, the seed SQL every branch gets, and how many days an unused branch is kept (1 to 90, extended by every deployment). Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_branching
Body limit256 KiB
ResponseObject: ref, automatic, lifetime_days, has_seed
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
automaticbodybooleanNotrue to make one for every preview branch.
seed_sqlbodystringNoSQL run in every new branch after the migrations (up to 1MB). An empty string removes it.
lifetime_daysbodyinteger, min 1, max 90NoHow many days a branch is kept after its last deployment.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branching" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"automatic":true,"seed_sql":"value","lifetime_days":1}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branching', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "automatic": true,
    "seed_sql": "value",
    "lifetime_days": 1
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/branches/{name}/merge

Merge a database branch Applies the migrations only the branch has to the project (production), in version order, each in a transaction of its own; stops at the first failure, rolling back only that one. Rows do not move. Change the schema in a branch with apply_migrations, not run_sql, for it to be merged.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolmerge_branch
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 422
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe branch name (name in list_branches).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches/my-name/merge" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches/my-name/merge', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/branches/{name}/reset

Reset a database branch Drops the branch's database and makes it again from the project's migrations and seed. All the branch's rows and users are lost and no backup is kept. Put the branch name exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolreset_branch
Body limit256 KiB
ResponseObject: ref, branch, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe branch name.
confirmbodystringYesThe branch name. It must be exactly the same as name.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches/my-name/reset" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches/my-name/reset', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/branches/{name}

Delete a database branch Deletes the branch with its database and keys. It cannot be undone, so put the branch name exactly in confirm. Deleting the git branch or closing its pull request deletes it automatically.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_branch
Body limit256 KiB
ResponseObject: ref, branch, deleted, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe branch name.
confirmbodystringYesThe branch name. It must be exactly the same as name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/branches/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/queues

Queues The queues (the same SQL functions as pgmq: pgmq.send, pgmq.read, pgmq.pop, pgmq.archive, pgmq.delete), with how many messages each holds, how many can be read now, how many are archived and the age of the oldest. enabled says whether pgmq is installed, exposed whether the data API (pgmq_public) can call it.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toollist_queues
ResponseObject: ref
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/queues/metrics

Queue metrics From the queue samples taken every five minutes: each queue's current depth, ready messages, oldest wait and archived count, and across all queues the depth, messages sent and messages processed over time. 24 hours by default, up to 168.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_queue_metrics
ResponseObject: ref, hours, queues, points
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
hoursqueryinteger, min 1, max 168NoHow many hours. 24 by default.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/metrics?hours=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/metrics?hours=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/queues

Create a queue Makes one queue (pgmq.create), installing pgmq the first time. Messages are kept in the table pgmq.q_<name>; in SQL, put one in with pgmq.send(name, message) and read with pgmq.read(name, seconds, count). Only in a database of its own.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolcreate_queue
Body limit256 KiB
ResponseObject: ref
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringYesThe queue name (lower-case letters, digits and _, up to 47).
unloggedbodybooleanNotrue for a faster table that writes no WAL (messages are lost if the server stops abnormally).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name"
  }),
})
const result = await response.json()

PUT /api/v1/projects/{ref}/queues/api

Open queues to the data API true makes the pgmq_public schema (send, send_batch, read, pop, archive, delete) so the data API can call it as supabase.schema("pgmq_public").rpc("send", ...). It runs as the caller, so for the publishable key or signed-in users to use it, grant privileges and write access rules on the queue tables. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolset_queues_api
Body limit256 KiB
ResponseObject: ref
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
exposedbodybooleanYestrue to open, false to close.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/api" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"exposed":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/api', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "exposed": true
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/queues/{name}

Delete a queue Deletes a queue with its messages and archive (pgmq.drop_queue). It cannot be undone, so put the queue name exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP tooldelete_queue
Body limit256 KiB
ResponseObject: ref, queue, dropped
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe queue name.
confirmbodystringYesThe queue name. It must be exactly the same as name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/queues/{name}/purge

Empty a queue Deletes all of a queue's messages (pgmq.purge_queue). The archive is kept. It cannot be undone, so put the queue name exactly in confirm.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolpurge_queue
Body limit256 KiB
ResponseObject: ref, queue, purged
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe queue name.
confirmbodystringYesThe queue name. It must be exactly the same as name.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name/purge" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"confirm":"my-project"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name/purge', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "confirm": "my-project"
  }),
})
const result = await response.json()

POST /api/v1/projects/{ref}/queues/{name}/messages

Send a message to a queue Puts one JSON message on a queue (pgmq.send). It cannot be read for delay seconds.

NameDescription
Token scopewrite
Rolesdata.write owner or admin or developer
MCP toolsend_queue_message
Body limit256 KiB
ResponseObject: ref, queue, msg_id
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe queue name.
messagebodystringYesThe message. Any JSON value.
delaybodyinteger, min 0NoSeconds before it can be read.
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name/messages" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"message":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name/messages', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "message": "value"
  }),
})
const result = await response.json()

GET /api/v1/projects/{ref}/queues/{name}/messages

Look at queue messages Shows the messages at the front of a queue without taking them. Neither the read count nor the visibility time changes, so it never takes a message a worker was about to get.

NameDescription
Token scoperead
Rolesdata.write owner or admin or developer
MCP toolpeek_queue
ResponseObject: ref, queue, messages
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe queue name.
limitqueryinteger, min 1, max 100NoHow many messages to show (20 by default).
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name/messages?limit=1" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/queues/my-name/messages?limit=1', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/vault

Vault secrets The names and descriptions of the secrets in Vault. Values are never returned. The project's code reads one with the secret key: select decrypted_secret from vault.decrypted_secrets where name = ...

NameDescription
Token scoperead
Rolessecrets.read owner or admin or developer
MCP toollist_vault_secrets
ResponseObject: ref, enabled, secrets
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

POST /api/v1/projects/{ref}/vault

Store a Vault secret Encrypts one secret and stores it in vault.secrets in the project's database (vault.create_secret). The platform holds the key and it is never stored in the database, so dumps and backups hold only ciphertext. Turns Vault on the first time. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolcreate_vault_secret
Body limit256 KiB
ResponseObject: ref, secret, note
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namebodystringYesThe secret name (starts with a letter or _, up to 128).
valuebodystringYesThe value to store (up to 64KB). It is not shown again.
descriptionbodystringNoA description (up to 500 characters).
curl -X POST "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"my-name","value":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault', {
  method: 'POST',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "name": "my-name",
    "value": "value"
  }),
})
const result = await response.json()

PATCH /api/v1/projects/{ref}/vault/{name}

Change a Vault secret Changes a secret's value or description (vault.update_secret). Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolupdate_vault_secret
Body limit256 KiB
ResponseObject: ref, secret
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe secret name.
valuebodystringNoThe new value.
descriptionbodystringNoThe new description.
curl -X PATCH "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"value":"value","description":"value"}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault/my-name', {
  method: 'PATCH',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "value": "value",
    "description": "value"
  }),
})
const result = await response.json()

DELETE /api/v1/projects/{ref}/vault/{name}

Delete a Vault secret Deletes one secret. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP tooldelete_vault_secret
ResponseObject: ref, name, deleted
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
namepathstringYesThe secret name.
curl -X DELETE "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault/my-name" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/vault/my-name', {
  method: 'DELETE',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

GET /api/v1/projects/{ref}/network-restrictions

Database network restrictions The addresses that may connect to the database directly (5432 and 6543) and whether TLS is required. restricted false means every address may connect. your_address_allowed says whether the address this request came from is allowed.

NameDescription
Token scoperead
RolesAny member of the organisation
MCP toolget_network_restrictions
ResponseObject: ref
Status codes200 · 400 · 401 · 403 · 404 · 409
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
curl "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network-restrictions" \
  -H "Authorization: Bearer $ONEEND_TOKEN"
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network-restrictions', {
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}` },
})
const result = await response.json()

PUT /api/v1/projects/{ref}/network-restrictions

Change database network restrictions Changes the addresses allowed to connect directly (a list of CIDRs, up to 100) and whether TLS is required. allow_all allows every address again. Refused without confirm when the address this request came from is not on the list. The console, the API and run_sql do not go through the pooler, so a restriction can always be undone. Only owners and admins can do this.

NameDescription
Token scopewrite
Rolesproject.admin owner or admin
MCP toolupdate_network_restrictions
Body limit256 KiB
ResponseResult JSON (the shape depends on the operation)
Status codes200 · 400 · 401 · 403 · 404 · 409 · 413
NameInTypeRequiredDescription
refpathstring, pattern ^[a-z]{20}$YesThe project identifier. Find it with list_projects.
allowed_cidrsbodyarray<string>NoAddresses or CIDRs to allow (for example 203.0.113.0/24). An empty array lifts the restriction.
allow_allbodybooleanNotrue to lift the restriction and allow every address.
enforce_sslbodybooleanNotrue to accept only TLS connections (sslmode=require). The default is true.
confirmbodybooleanNoSaves even when the address this request came from is not on the list.
curl -X PUT "https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network-restrictions" \
  -H "Authorization: Bearer $ONEEND_TOKEN" \
  -H "content-type: application/json" \
  -d '{"allowed_cidrs":["value"],"allow_all":true,"enforce_ssl":true,"confirm":true}'
const response = await fetch('https://end.blackall.ai/api/v1/projects/abcdefghijklmnopqrst/network-restrictions', {
  method: 'PUT',
  headers: { authorization: `Bearer ${process.env.ONEEND_TOKEN}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    "allowed_cidrs": [
      "value"
    ],
    "allow_all": true,
    "enforce_ssl": true,
    "confirm": true
  }),
})
const result = await response.json()