Home›Developer docs›Management API

SashiDo Management API

Updated Nov 14, 202621 min read

Reference for the SashiDo Management API. Authenticate with a personal access token, then manage apps, builds, deployments, engines, scaling and environment variables, read the audit log, and query your app's data through the Parse proxy. Parameters, example requests and responses, status codes and errors.

The Management API is the API the SashiDo dashboard and the MCP server use. With a personal access token you can call it from your scripts, CI pipelines and tools.

Quick start

1. Create a token in the dashboard: Account › Access Tokens › Create token. Copy it. It is shown once.

export SASHIDO_TOKEN=sashido_pat_...

2. Check it. GET /tokens/current says what the token may do:

curl https://api.sashido.io/tokens/current \
  -H "Authorization: Bearer $SASHIDO_TOKEN"

3. Find your app's objectId. Every app endpoint takes it in the path. GET /users/info lists your apps:

curl https://api.sashido.io/users/info \
  -H "Authorization: Bearer $SASHIDO_TOKEN"
{
  "data": {
    "user": { "objectId": "Us3rIdAbCd", "username": "you@example.com", "createdAt": "2021-03-04T10:00:00.000Z" },
    "products": {
      "apps": [
        { "objectId": "AbC123xYz0", "name": "Shop", "region": "1-eu", "parseServerVersion": "release-6.5.11-ubi8-node18", "…": "…" }
      ]
    }
  }
}

4. Call an app endpoint, e.g. its recent deployments:

export APP=AbC123xYz0
curl "https://api.sashido.io/apps/$APP/deployments?limit=3" \
  -H "Authorization: Bearer $SASHIDO_TOKEN"

Basics

Base URL https://api.sashido.io (no version prefix)
Authentication Authorization: Bearer sashido_pat_... on every request
Request bodies JSON with Content-Type: application/json, up to 300 KB
Rate limit 600 requests per minute per token; above it 429 with Retry-After

Responses. Endpoints that return data answer 200 with the result in data: { "data": … }. Endpoints that only act (deploy, scale, rename…) answer 204 No Content with an empty body. The exceptions are noted at each endpoint.

Where the IDs come from:

Parameter Where to get it
{appObjectId} objectId of the app in GET /users/info. Not the app's Application ID.
{buildId} objectId of the build in GET /apps/{appObjectId}/builds
a build's or deployment's name name in the builds or deployments list
imageTag GET /products/parse-server-versions
an engine's name and billingPolicy GET /products/parse-server-engines
region the app's region: 1-eu (Europe) or 2-us (North America)

Regions. Build, deploy, engine and scale endpoints take an optional ?region=. Without it they act on all the app's regions.

Tracing your calls

Two optional headers are saved with each change in your Activity, to tell your scripts apart later. They are labels only and change nothing about access.

Header Max length Example
X-SashiDo-Client 64 github-actions (default for tokens: api)
X-Request-Id 64 an ID from your own logs

Errors

Check the status code first; the body depends on where the error comes from.

Access and SashiDo errors are JSON with a numeric code:

{ "code": 302, "message": "The access token isn't allowed to access this app." }
Status code When What to do
401 300 The token is invalid, expired or revoked, or the header isn't Bearer Create a new token
403 301 The token lacks a scope; the message names it Use a token with that scope
403 302 The token is limited to other apps Use a token for this app
403 303 The endpoint isn't available with a token Use the dashboard
429 304 More than 600 requests in a minute Wait Retry-After seconds
404 101 No app with this objectId that you can access Check the objectId
400 200 e.g. "Build not found." Check the ID
400/403/409/502 305 The Parse proxy refused or couldn't complete the request See the message
400 310 An App Settings or custom domain request is invalid: an unknown setting, a wrong type, a setting the app's Parse Server version lacks Fix what the message names
400 311 The platform refused the change, e.g. an email sender on a domain you don't own See the message
403 312 You can see the app but not change its settings Ask the owner

401 and 403 access errors also carry the standard WWW-Authenticate: Bearer error="invalid_token" (or "insufficient_scope") header.

Invalid parameters (a missing or mistyped field) answer 400 with the fields at fault:

{ "body.name": { "message": "'name' is required" } }

Refused changes answer 400 with the reason as plain text (Content-Type: text/html), e.g.:

only app owner can change engines
Parse Server version release-9.9.9 isn't available.

Errors from your app's Parse Server, through the proxy, are Parse's own: { "code": 101, "error": "Object not found." }.

A client that handles all of them:

async function sashido(path, options = {}) {
  const res = await fetch(`https://api.sashido.io${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${process.env.SASHIDO_TOKEN}`,
      'Content-Type': 'application/json',
      'X-SashiDo-Client': 'my-script',
      ...options.headers,
    },
  });
  if (res.status === 204) return null;
  const text = await res.text();
  let body;
  try { body = JSON.parse(text); } catch { body = text; }
  if (res.status === 429) {
    const wait = Number(res.headers.get('Retry-After') || 60);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return sashido(path, options);
  }
  if (!res.ok) {
    const message = typeof body === 'string' ? body : body.message || body.error || JSON.stringify(body);
    throw new Error(`${res.status}: ${message}`);
  }
  return body;
}

const { data } = await sashido('/users/info');

Account

GET /tokens/current

The token you call with. Works with any scope.

Example response

{
  "data": {
    "id": "TkN1dAbCd0",
    "name": "GitHub Actions deploy",
    "scopes": ["apps:read", "deploy"],
    "appIds": ["AbC123xYz0"],
    "expiresAt": "2027-02-11T09:30:00.000Z",
    "user": { "objectId": "Us3rIdAbCd", "username": "you@example.com" }
  }
}

appIds is null for a token for all apps; expiresAt is null for one that doesn't expire.

GET /users/info

Scope: account:read. You and the apps the token can access, with their runtime. No keys, no billing data.

Example response

{
  "data": {
    "user": { "objectId": "Us3rIdAbCd", "username": "you@example.com", "createdAt": "2021-03-04T10:00:00.000Z" },
    "products": {
      "apps": [
        {
          "objectId": "AbC123xYz0",
          "name": "Shop",
          "appId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
          "serviceName": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5",
          "slug": "shop",
          "region": "1-eu",
          "status": "active",
          "server_status": { "state": "finished" },
          "parseServerVersion": "release-6.5.11-ubi8-node18",
          "settings": {
            "engine": { "1-eu": { "name": "Production 1X", "billingPolicy": "hourly" } },
            "engine_scaling_config": { "min": 1, "max": 20, "buffer": 4 },
            "highly_available": false
          },
          "deployment": {
            "objectId": "DpL0yAbCd0",
            "name": "parse-server-deployment-109",
            "number": 109,
            "phase": "Active",
            "region": "1-eu",
            "duration": 74,
            "replicas": { "auto": false, "min": 1, "max": 1 },
            "createdAt": "2026-11-12T14:02:11.000Z",
            "updatedAt": "2026-11-12T14:03:25.000Z"
          },
          "createdAt": "2021-03-04T10:05:00.000Z",
          "updatedAt": "2026-11-12T14:03:25.000Z"
        }
      ]
    }
  }
}
  • appId is the app's Application ID, used by the Parse SDKs. The API's paths use objectId.
  • deployment is the app's latest deployment; replicas are its instances.
  • settings.engine_scaling_config holds the limits for scaling.

GET /activities

Scope: account:read. Your account's Activity (audit log), newest first: every change, who made it and how it went. Collaborators see only their own events.

Query Type Default
page number 1 Starts at 1
pageSize number 50 Events per page

GET /activities/{type} returns only one type, e.g. /activities/deploy.

Example request

curl "https://api.sashido.io/activities?page=1&pageSize=20" \
  -H "Authorization: Bearer $SASHIDO_TOKEN"

Example response

{
  "data": [
    {
      "objectId": "AcT1vAbCd0",
      "type": "set_env",
      "createdAt": "2026-11-13T08:41:07.000Z",
      "ip": "203.0.113.24",
      "browser": "curl/8.4.0",
      "actor": { "kind": "access_token", "tokenId": "TkN1dAbCd0", "tokenName": "GitHub Actions deploy" },
      "via": { "client": "github-actions", "requestId": "4f0c9a52-6f1e-4a43-9a3b-2a7c1d9e8b10" },
      "result": { "status": "ok" },
      "source": "server",
      "app": { "objectId": "AbC123xYz0", "name": "Shop" },
      "data": {
        "message": "Environment variables changed",
        "params": { "env": [{ "name": "STRIPE_KEY", "value": "(not recorded)" }] }
      }
    }
  ]
}
  • actor.kind: session (you in the dashboard) or access_token.
  • via: the client, and through MCP also mcpClient (e.g. Claude Code) and mcpTool.
  • result.status: ok, refused or failed, with code and message when it didn't succeed.
  • data.params holds what the change was called with. Values under names that look secret (value, password, token, key…) are never stored; they show as "(not recorded)".

Apps

POST /apps

Scope: apps:write, with a token for all apps. Creates an app. Billable.

Body Type Required
name string yes
region string yes 1-eu (Europe) or 2-us (North America)
parseServerVersion string no An imageTag from the versions. Set it: the default may be an older version.
engine object no { "name": "Production 1X", "billingPolicy": "hourly" } from the engines

Example request

curl -X POST https://api.sashido.io/apps \
  -H "Authorization: Bearer $SASHIDO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Shop staging", "region": "1-eu", "parseServerVersion": "release-6.5.11-ubi8-node18"}'

Example response

200, the new app without a data wrapper, with the same fields as in GET /users/info:

{ "objectId": "NeW4pPAbC0", "name": "Shop staging", "region": "1-eu", "parseServerVersion": "release-6.5.11-ubi8-node18", "…": "…" }

An unavailable version answers 400 with the text Parse Server version <imageTag> isn't available.

PUT /apps/{appObjectId}/name

Scope: apps:write. Renames the app. Body: { "name": "Shop" }. Answers 204.

GET /products/parse-server-versions

Any scope. The Parse Server versions apps can run.

Example response

{
  "data": [
    { "imageTag": "release-3.6.0-node18", "version": "3.6.0" },
    { "imageTag": "release-6.5.11-ubi8-node18", "version": "6.5.11" }
  ]
}

One version can carry "latest": true: the one new apps get when POST /apps names none.

PUT /apps/{appObjectId}/version

Scope: apps:write. Switches the app to another Parse Server version and redeploys it. Check what your cloud code needs first: the MCP server's plan_parse_server_upgrade lists it.

Body Type Required
imageTag string yes From the versions

Example response

{ "data": { "imageTag": "release-6.5.11-ubi8-node18", "version": "6.5.11" } }

When the app's settings must change first (e.g. a social login that the new version configures differently), it answers 400 with the reasons as text.

Builds

A build packages the app's cloud code from its GitHub repository into a new image. A deployment then runs it.

GET /apps/{appObjectId}/builds

Scope: apps:read. The app's builds, newest first.

Query Type Default
limit number 30
populateGithubData boolean true Adds the commit's author and message to trigger

Example response

{
  "data": [
    {
      "objectId": "BuILdAbCd0",
      "name": "parse-server-buildconfig-42",
      "number": 42,
      "phase": "Complete",
      "region": "1-eu",
      "duration": 96,
      "trigger": {
        "source": "GitHub",
        "commit": "9fceb02d0ae598e95dc970b74767f19372d61af8",
        "message": "Validate notes in beforeSave",
        "author": { "name": "Ada Lovelace", "email": "ada@example.com" }
      },
      "createdAt": "2026-11-12T13:58:40.000Z",
      "updatedAt": "2026-11-12T14:00:16.000Z"
    }
  ]
}

phase: New, Pending, Running, Complete, Failed, Error or Cancelled.

A build someone started from the dashboard, the API or MCP also has startedBy, as in the deployment history.

GET /apps/{appObjectId}/builds/{buildId}/log

Scope: apps:read. The end of a build's log, where a failure shows.

Query Type Default
lines number 300 The last lines; up to 1000

Example response

{
  "data": {
    "buildId": "BuILdAbCd0",
    "number": 42,
    "phase": "Failed",
    "finished": true,
    "text": "npm ERR! code ETARGET\nnpm ERR! notarget No matching version found for stripe@^99.0.0.\n",
    "truncated": true
  }
}

truncated says earlier lines were left out. An unknown buildId answers 400, code 200, Build not found.

POST /apps/{appObjectId}/build

Scope: deploy. Starts a build of the cloud code in the app's repository. Answers 204; the build appears in the list.

POST /apps/{appObjectId}/build/cancel

Scope: deploy. Body: { "name": "parse-server-buildconfig-43" }, the build's name. Answers 204.

Deployments

GET /apps/{appObjectId}/deployments

Scope: apps:read. The app's deployments, newest first.

Query Type Default
limit number 10 Up to 50

Example response

{
  "data": [
    {
      "objectId": "DpL0yAbCd1",
      "name": "parse-server-deployment-110",
      "number": 110,
      "phase": "Running",
      "region": "1-eu",
      "duration": 0,
      "replicas": { "auto": false, "min": 1, "max": 1 },
      "createdAt": "2026-11-13T09:12:01.000Z",
      "updatedAt": "2026-11-13T09:12:05.000Z"
    },
    {
      "objectId": "DpL0yAbCd0",
      "name": "parse-server-deployment-109",
      "number": 109,
      "phase": "Active",
      "region": "1-eu",
      "duration": 74,
      "replicas": { "auto": false, "min": 1, "max": 1 },
      "createdAt": "2026-11-12T14:02:11.000Z",
      "updatedAt": "2026-11-12T14:03:25.000Z"
    }
  ]
}

Active is the deployment serving traffic. New, Pending and Running are rolling out. Earlier ones end as Complete, Failed, Cancelled or Error.

POST /apps/{appObjectId}/deploy

Scope: deploy. Deploys the app's latest build. Answers 204; the new deployment appears in the list, first as Running, then Active.

POST /apps/{appObjectId}/deploy/cancel

Scope: deploy. Body: { "name": "parse-server-deployment-110" }, the deployment's name. Answers 204.

GET /apps/{appObjectId}/deployments/history

Scope: apps:read. The deployments, newest first, each with what started it (cause) and who (startedBy). Work SashiDo does on its own infrastructure isn't listed.

Query Type Default
limit number 100 Up to 100

Example response

{
  "data": [
    {
      "objectId": "DpL0yAbCd2",
      "name": "parse-server-deployment-111",
      "number": 111,
      "phase": "Active",
      "region": "1-eu",
      "createdAt": "2026-11-14T08:40:12.000Z",
      "updatedAt": "2026-11-14T08:41:30.000Z",
      "cause": {
        "kind": "settings",
        "changes": [
          { "name": "PARSE_SERVER_PUSH_ENABLE_REST", "change": "changed", "from": "off", "to": "on" },
          { "name": "PARSE_SERVER_LIVE_QUERY_OPTIONS", "change": "changed", "from": "none", "to": "Message, Todo" }
        ]
      },
      "startedBy": {
        "client": "mcp/0.2.0",
        "user": { "name": "Ada Lovelace", "email": "ada@example.com" },
        "tokenName": "Claude Code",
        "mcpClient": "claude-code",
        "mcpTool": "update_app_settings",
        "ip": "203.0.113.7"
      }
    },
    {
      "objectId": "DpL0yAbCd1",
      "name": "parse-server-deployment-110",
      "number": 110,
      "phase": "Complete",
      "region": "1-eu",
      "createdAt": "2026-11-13T09:12:01.000Z",
      "updatedAt": "2026-11-13T09:13:15.000Z",
      "cause": {
        "kind": "build",
        "build": { "number": 42, "source": "GitHub", "commit": "9fceb02d0ae598e95dc970b74767f19372d61af8", "message": "Validate notes in beforeSave" }
      },
      "startedBy": { "client": "github", "user": { "name": "Ada Lovelace", "email": "ada@example.com" } }
    }
  ]
}
  • cause.kind: settings, env (environment variables), domain, push_certificate, version (Parse Server version), engine, high_availability, manual (Start Deploy) or build.
  • changes names what changed: added, removed or changed. Before and after (from, to) are shown only for settings that hold nothing secret, such as on/off options, numbers and the Live Queries classes; a variable's or a key's value never is. Without env:read, a token sees that variables changed, not their names.
  • startedBy.client: dashboard, api, mcp/<version> (with the AI client and its tool) or github (a push to the app's repository, with the commit's author).
  • startedBy.ip is the address the change came from, shown only to the person who made it: a collaborator sees who, not where.
  • Deployments from before November 14, 2026 have no cause or startedBy.

GET /apps/rollouts/active

Scope: apps:read. What your apps are building or deploying now, and what finished in the last two minutes: the dashboard's "your app is updating" indicator. With a token limited to some apps, only those.

{
  "data": [
    { "appObjectId": "AbC123xYz", "appName": "my-app", "kind": "deploy", "reason": "settings", "region": "1-eu", "startedAt": "2026-11-14T08:40:12.000Z" }
  ],
  "recent": [
    { "appObjectId": "QrS456uVw", "appName": "staging", "kind": "build", "reason": "git", "region": "1-eu", "startedAt": "2026-11-14T08:37:02.000Z", "state": "done", "endedAt": "2026-11-14T08:39:40.000Z" }
  ]
}

kind is build or deploy; reason is what started it, as in the history's cause.kind, or git for a push to the app's repository. state is done or failed.

Engines, scaling and high availability

These change your bill, and only the app's owner can make them. A collaborator gets 400 with only app owner can change engines.

GET /products/parse-server-engines

Scope: apps:read. The engines your plan offers.

Example response

{
  "data": [
    {
      "name": "Production 1X",
      "billingPolicy": "hourly",
      "default": false,
      "allowedInTrial": false,
      "supportsAutoScaling": true,
      "supportsHighAvailability": true,
      "params": { "cpu": 4, "ram": 1073741824 }
    }
  ]
}

ram is in bytes; cpu is the engine's relative CPU share.

PUT /apps/{appObjectId}/engine

Scope: apps:write. Billable. Body: { "name": "Production 1X", "billingPolicy": "hourly" }. Optional ?region=. Answers 204.

PUT /apps/{appObjectId}/scale

Scope: apps:write. Billable. Sets the number of instances.

Body Type Required
auto boolean yes Auto-scaling (engines with supportsAutoScaling)
min number yes Instances when auto is off; the minimum with it on
max number yes The maximum with auto on; at least min
cpuTarget number no The CPU % at which auto-scaling adds instances

min and max are brought within your limits (settings.engine_scaling_config in /users/info). Optional ?region=. Answers 204.

curl -X PUT "https://api.sashido.io/apps/$APP/scale" \
  -H "Authorization: Bearer $SASHIDO_TOKEN" -H "Content-Type: application/json" \
  -d '{"auto": true, "min": 2, "max": 4}'

PUT /apps/{appObjectId}/highAvailability

Scope: apps:write. Billable. Body: { "enabled": true }. Keeps at least three instances with health checks, and resets min and max to your limits. An engine without supportsHighAvailability answers 400. Answers 204.

Environment variables

GET /apps/{appObjectId}/env

Scope: env:read.

Example response

{
  "data": [
    { "name": "STRIPE_KEY", "value": "sk_test_…", "perm": "rw" },
    { "name": "NODE_ENV", "value": "production", "perm": "ro" }
  ]
}

perm: rw are your variables; ro are set by SashiDo and read-only. A token sees your own variables and the app's addresses (NODE_ENV, PARSE_SERVER_URL, PARSE_PUBLIC_SERVER_URL). Credentials SashiDo manages, such as the database connection string, are never returned to a token; the dashboard shows them to you.

PUT /apps/{appObjectId}/env

Scope: env:write. Replaces your variables (rw) with the list you send: your variables left out are removed, so send all of them. Variables SashiDo manages stay as they are; you don't need to send them. Answers 204. When the variables change, the app is redeployed with them, as when you save them in the dashboard; follow it in the deployments.

Body Type Required
env array yes [{ "name": "STRIPE_KEY", "value": "sk_live_…" }]

Refused with 400 and the names in the text:

  • names that aren't letters, digits and _, or start with a digit: Invalid env variable name 9LIVES;
  • the same name twice: Cannot update vars more than once in the same request …;
  • read-only or reserved names, such as NODE_ENV, PORT, PATH, the variables SashiDo manages and those starting with PARSE_SERVER_, PARSEGROUND_ or SASHIDO_: Cannot update read-only/reserved env variables ….

POST /apps/{appObjectId}/deploy-env

Scope: deploy. Redeploys the app with its current variables, e.g. when a redeploy after a change didn't go through. PUT /env doesn't need it. Optional ?region=. Answers 204.

App Settings

The settings of App Settings in the dashboard: users and passwords, social logins, email, custom pages, push, Live Queries and API options.

GET /apps/{appObjectId}/settings

Scope: settings:read. Secrets are never returned, only whether they are set (clientSecretSet, appSecretSet, consumerSecretSet).

Example response (shortened)

{
  "data": {
    "users": {
      "usernameAndPassword": true,
      "anonymousUsers": false,
      "sessionLengthSeconds": 31536000,
      "passwordPolicy": { "validatorPattern": null, "doNotAllowUsername": true, "maxPasswordAgeDays": null, "maxPasswordHistory": 5, "resetTokenValidityDurationSeconds": null },
      "accountLockout": { "threshold": 5, "durationMinutes": 15 },
      "mfaTotp": false
    },
    "socialLogin": {
      "google": { "enabled": true, "clientIds": ["1234-abc.apps.googleusercontent.com"] },
      "github": { "enabled": true, "clientId": "Iv1.0123456789abcdef", "clientSecretSet": true, "acceptAccessToken": false },
      "facebook": { "enabled": false, "appId": null, "appSecretSet": false }
    },
    "email": { "verifyUserEmails": true, "senderAddress": "hello@example.com", "senderDisplayName": "My App" },
    "push": { "clientPush": false, "restPush": true },
    "liveQueries": { "classes": ["Message", "Todo"] },
    "api": { "allowClientClassCreation": false, "maxLimit": null, "allowHeaders": [] },
    "availability": { "mfaTotp": "8.6.104" }
  }
}

availability names the Parse Server version a setting needs when the app's version is older (null when the app has it). Such a setting can't be turned on until you upgrade.

PATCH /apps/{appObjectId}/settings

Scope: settings:write. Send only what changes, in the sections GET shows; everything else stays as it is, nested settings too. null clears a text, number or object. Secrets (clientSecret, appSecret, consumerSecret) can be set, never read.

curl -X PATCH https://api.sashido.io/apps/AbC123xYz/settings \
  -H "Authorization: Bearer $SASHIDO_TOKEN" -H "Content-Type: application/json" \
  -d '{ "push": { "restPush": true }, "liveQueries": { "classes": ["Message", "Todo"] } }'

The answer is { "data": { "settings": {…}, "redeploys": true } }: the settings as saved, and whether the app redeploys to apply them (most changes do; follow it in the deployments).

The same checks as the dashboard apply: an unknown setting, a wrong type or a setting the app's version lacks is refused with code 310, and the platform's rules (such as allowed email senders) with 311.

Custom domain

Serve the app's API, files, email links and website on your own domain. See Hosting and domains.

GET /apps/{appObjectId}/domain

Scope: settings:read.

{
  "data": {
    "hostName": "api.example.com",
    "letsEncrypt": true,
    "dns": {
      "target": "pg-app-abc123.scalabl.cloud",
      "records": [
        "api.example.com CNAME pg-app-abc123.scalabl.cloud",
        "pg-app-abc123.api.example.com CNAME pg-app-abc123.scalabl.cloud"
      ]
    }
  }
}

records lists the CNAME records the domain needs: either one works.

PUT /apps/{appObjectId}/domain

Scope: settings:write. Body: { "hostName": "api.example.com", "letsEncrypt": true }; letsEncrypt is optional. The CNAME record must exist first, or the change is refused with the record to create. Answers like GET, with redeploys: true when the host name changed.

DELETE /apps/{appObjectId}/domain

Scope: settings:write. Removes the host name and the Let's Encrypt certificate; the app redeploys on its scalabl.cloud address. Clients using your domain stop reaching the app.

Your app's data: the Parse proxy

/apps/{appObjectId}/parse/<path> forwards the request to your app's Parse Server and adds the master key on our side. Send the same method, path, query and body as to the Parse REST API, with only your token: no Application ID, no keys. Responses and errors are Parse Server's own.

Paths Scope
GET classes/<Class>, GET classes/<Class>/<objectId>, GET aggregate/<Class> data:read
GET schemas, GET schemas/<Class>, GET config data:read
GET hooks/functions, GET hooks/triggers data:read
POST classes/<Class>, PUT classes/<Class>/<objectId>, DELETE classes/<Class>/<objectId> data:write
POST batch (1 to 50 writes on classes) data:write
POST schemas/<Class>, PUT schemas/<Class>, DELETE schemas/<Class>, PUT config data:write
POST files/<name> (up to 10 MB), POST functions/<name>, POST jobs/<name>, POST push data:write
POST/PUT/DELETE hooks/functions/…, hooks/triggers/… data:write
GET scriptlog logs:read
GET health apps:read

Query parameters per path: classes takes where, limit, skip, order, keys, excludeKeys, include, count; aggregate takes pipeline, match, group, project, sort, limit, skip, distinct. Others are refused with 400 Unsupported parameter: <name>.

Query objects

curl -G "https://api.sashido.io/apps/$APP/parse/classes/Order" \
  -H "Authorization: Bearer $SASHIDO_TOKEN" \
  --data-urlencode 'where={"status":"paid","total":{"$gte":100}}' \
  --data-urlencode 'order=-createdAt' \
  --data-urlencode 'limit=20' \
  --data-urlencode 'keys=total,status,createdAt'
{
  "results": [
    { "objectId": "0rDeRAbCd0", "total": 129.9, "status": "paid", "createdAt": "2026-11-12T18:20:44.512Z", "updatedAt": "2026-11-12T18:20:44.512Z" }
  ]
}

Add count=1 to get "count" with the results.

Create, update, delete

curl -X POST "https://api.sashido.io/apps/$APP/parse/classes/Order" \
  -H "Authorization: Bearer $SASHIDO_TOKEN" -H "Content-Type: application/json" \
  -d '{"total": 49.5, "status": "new"}'
# 201 {"objectId":"0rDeRAbCd1","createdAt":"2026-11-13T09:30:00.000Z"}

curl -X PUT "https://api.sashido.io/apps/$APP/parse/classes/Order/0rDeRAbCd1" \
  -H "Authorization: Bearer $SASHIDO_TOKEN" -H "Content-Type: application/json" \
  -d '{"status": "paid"}'
# 200 {"updatedAt":"2026-11-13T09:31:00.000Z"}

curl -X DELETE "https://api.sashido.io/apps/$APP/parse/classes/Order/0rDeRAbCd1" \
  -H "Authorization: Bearer $SASHIDO_TOKEN"
# 200 {}

Call a cloud function

curl -X POST "https://api.sashido.io/apps/$APP/parse/functions/hello" \
  -H "Authorization: Bearer $SASHIDO_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Ada"}'
# 200 {"result":"Hello, Ada!"}

The function runs with the master key, so request.master is true.

Read the logs

GET scriptlog, scope logs:read. Query: level (info or error), n or size (number of entries), from and until (dates), order (asc or desc).

curl -G "https://api.sashido.io/apps/$APP/parse/scriptlog" \
  -H "Authorization: Bearer $SASHIDO_TOKEN" --data-urlencode 'level=error' --data-urlencode 'n=20'
[
  {
    "timestamp": "2026-11-13T08:02:13.441Z",
    "level": "error",
    "message": "Failed running cloud function signup for user undefined with:\n  Input: {}\n  Error: {\"message\":\"A username is required.\",\"code\":141}",
    "meta": { "error": { "name": "Error", "message": "A username is required.", "code": 141, "stack": "Error: A username is required.\n    at …" } },
    "hostname": "parse-server-deployment-109-x7k2p"
  }
]

Check the app's health

GET health, scope apps:read:

{
  "status": "ok",
  "database": "ok",
  "version": "6.5.11",
  "nodeVersion": "v18.20.4",
  "region": "1-eu",
  "build": "42",
  "deployment": "109",
  "state": "finished"
}

What the proxy guards

  • Only the paths above. Anything else, such as sessions, login or users/me, answers 403 with { "code": 305, "message": "GET /sessions is not allowed." }. The _Session class is never reachable.
  • No secrets in responses. sessionToken, authData and password hashes are removed from every object, at any depth.
  • Bounded. 30 seconds per request; responses up to 10 MB, above that 502 with The response is too large. Use limit and keys to narrow it.
  • Unreachable app. 502 with The app's Parse Server is unreachable. while the app is down or restarting.
  • Audited. Every write is in your Activity (without its body). Reads are summed up by the hour; reads of your users are recorded one by one.

Recipes

Deploy from GitHub Actions and wait until it's live

# .github/workflows/deploy.yml
name: Deploy
on:
  workflow_dispatch:
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy and wait
        env:
          SASHIDO_TOKEN: ${{ secrets.SASHIDO_TOKEN }}
          APP: ${{ vars.SASHIDO_APP }}
        run: |
          api() { curl -sf -H "Authorization: Bearer $SASHIDO_TOKEN" -H "X-SashiDo-Client: github-actions" "$@"; }
          latest() { api "https://api.sashido.io/apps/$APP/deployments?limit=1" | jq -r '.data[0] | "\(.number) \(.phase)"'; }
          before=$(latest | cut -d' ' -f1)
          api -X POST "https://api.sashido.io/apps/$APP/deploy"
          for i in $(seq 1 60); do
            read number phase <<< "$(latest)"
            if [ "$number" != "$before" ]; then
              case "$phase" in
                Active) echo "Deployment $number is live"; exit 0 ;;
                Failed|Cancelled) echo "Deployment $number: $phase"; exit 1 ;;
              esac
            fi
            sleep 10
          done
          echo "Timed out"; exit 1

Use a token with apps:read and deploy, limited to this app.

Change one environment variable

PUT /env replaces your variables, so read them, change one and write them back. The app redeploys with the new value:

const { data: env } = await sashido(`/apps/${APP}/env`);
const next = env
  .filter((v) => v.perm === 'rw' && v.name !== 'STRIPE_KEY')
  .map(({ name, value }) => ({ name, value }))
  .concat({ name: 'STRIPE_KEY', value: process.env.NEW_STRIPE_KEY });

await sashido(`/apps/${APP}/env`, { method: 'PUT', body: JSON.stringify({ env: next }) });

Export a class page by page

let last = null;
for (;;) {
  const where = last ? { createdAt: { $gt: { __type: 'Date', iso: last } } } : {};
  const params = new URLSearchParams({ where: JSON.stringify(where), order: 'createdAt', limit: '1000' });
  const { results } = await sashido(`/apps/${APP}/parse/classes/Order?${params}`);
  if (!results.length) break;
  // write results somewhere
  last = results[results.length - 1].createdAt;
}

Paging by createdAt stays fast at any depth, unlike skip.

Not available with tokens

These stay in the dashboard: managing tokens, deleting apps, billing, the apps' keys, signing in and two-factor authentication, metrics, and the history of push notifications.

Reading this with an AI assistant? This page as Markdown.