Skip to content
dply/edge

Tools

API reference

Every dply HTTP API v1 endpoint, with the ability it needs, its parameters and an example response.

This page lists every endpoint in the dply HTTP API v1. For authentication, rate limits, errors and how tokens are scoped to your role, read HTTP API first.

All paths are relative to https://edge.dply.io/api/v1. Every request needs Authorization: Bearer dply_… and Accept: application/json. Requests with a body send Content-Type: application/json.

In the examples, $DPLY_TOKEN holds your token and $SITE holds an app ID from List apps.

Note

The API calls apps "sites" in paths and field names (/edge/sites, site_id). They are the same apps you see in the dashboard.

Endpoint summary

Method Path Ability
GET /account account.read
GET /account/organizations account.read
GET /account/sessions account.read
DELETE /account/sessions/{token} account.write
GET /account/git account.read
POST /account/git account.write
DELETE /account/git/{account} account.write
GET /capabilities account.read
GET /billing billing.read
GET /billing/breakdown billing.read
GET /billing/invoices billing.read
GET /edge/sites edge.read
GET /edge/sites/{site} edge.read
PUT /edge/sites/{site}/icon edge.write
DELETE /edge/sites/{site}/icon edge.write
GET /edge/sites/{site}/deployments edge.read
POST /edge/sites/{site}/deployments edge.deploy
GET /edge/sites/{site}/deployments/{deployment} edge.read
POST /edge/sites/{site}/deployments/{deployment}/rollback edge.deploy
GET /edge/sites/{site}/previews edge.read
POST /edge/sites/{site}/previews edge.deploy
DELETE /edge/sites/{site}/previews/{preview} edge.deploy
POST /edge/sites/{site}/previews/{preview}/promote edge.deploy
GET /edge/sites/{site}/domains edge.read
POST /edge/sites/{site}/domains edge.write
POST /edge/sites/{site}/domains/{hostname}/verify edge.write
DELETE /edge/sites/{site}/domains/{hostname} edge.write
GET /edge/sites/{site}/aliases edge.read
GET /edge/sites/{site}/access edge.read
PATCH /edge/sites/{site}/access edge.write
POST /edge/sites/{site}/cache/purge edge.write
GET /edge/sites/{site}/usage edge.read
GET /edge/sites/{site}/logs edge.read
GET /edge/sites/{site}/env edge.env.read
PUT /edge/sites/{site}/env edge.env.write
PATCH /edge/sites/{site}/env/{key} edge.env.write
DELETE /edge/sites/{site}/env/{key} edge.env.write
POST /edge/lint edge.read
GET /edge/sites/{site}/databases edge.read
GET /edge/sites/{site}/databases/{database} edge.read
POST /edge/sites/{site}/databases/{database}/query edge.write
GET /edge/sites/{site}/databases/{database}/exports edge.read
POST /edge/sites/{site}/databases/{database}/exports edge.write
POST /edge/sites/{site}/databases/{database}/imports/upload-link edge.write
POST /edge/sites/{site}/databases/{database}/imports edge.write
POST /edge/sites/{site}/databases/{database}/restore edge.write
POST /edge/sites/{site}/databases/{database}/restore-as-new edge.write
GET /edge/databases edge.read
POST /edge/databases edge.write
DELETE /edge/databases/{database} edge.write
POST /edge/databases/{database}/query edge.write
GET /edge/buckets edge.read
POST /edge/buckets edge.write
DELETE /edge/buckets/{bucket} edge.write
GET /edge/mail/providers edge.write
POST /edge/mail/providers edge.write
PATCH /edge/mail/providers/{provider} edge.write
DELETE /edge/mail/providers/{provider} edge.write
POST /edge/mail/providers/{provider}/test edge.write
GET /edge/messages edge.read
POST /edge/messages/enable edge.write
POST /edge/messages/tokens edge.write
DELETE /edge/messages/tokens/{token} edge.write
GET /edge/messages/keys edge.write
POST /edge/messages/keys/rotate edge.write
GET /edge/queues edge.read
POST /edge/queues edge.write
DELETE /edge/queues/{queue} edge.write
POST /edge/queues/{queue}/messages edge.write
GET /edge/kv edge.read
GET /edge/kv/{store}/keys edge.read
GET /edge/kv/{store}/keys/{key} edge.read
PUT /edge/kv/{store}/keys/{key} edge.write
DELETE /edge/kv/{store}/keys/{key} edge.write
GET /status-pages edge.read
POST /status-pages edge.write
GET /status-pages/{page} edge.read
PATCH /status-pages/{page} edge.write
DELETE /status-pages/{page} edge.write
POST /status-pages/{page}/monitors edge.write
DELETE /status-pages/{page}/monitors/{monitor} edge.write
POST /status-pages/{page}/incidents edge.write
PATCH /status-pages/{page}/incidents/{incident} edge.write
GET /notifications/channels notifications.read
GET /notifications/events notifications.read
POST /notifications/channels/{channel}/test notifications.write
GET /sites/{site}/notifications notifications.read
POST /sites/{site}/notifications notifications.write
POST /auth/device/start None (CLI sign-in)
POST /auth/device/poll None (CLI sign-in)

For endpoints under /edge/sites/{site} and /sites/{site}, your role must also allow the action on that app: GET needs view access, every other method needs change access. See Roles & permissions.

Account

Get the current token's account

GET /account · account.read

Returns the token's user, its organization (with your role there) and the token itself.

bash
curl https://edge.dply.io/api/v1/account \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json"
json
{
  "data": {
    "user": { "id": "01j9…", "name": "Ada Lovelace", "email": "ada@example.com" },
    "organization": {
      "id": "01j8…",
      "name": "Acme",
      "role": "admin",
      "is_current": true,
      "projects_count": 0
    },
    "token": {
      "id": "01ja…",
      "name": "github-actions-deploy",
      "prefix": "dply_AbCdEfGhIjK",
      "masked": "dply_AbCdEfGhIjK…",
      "abilities": ["edge.read", "edge.deploy"],
      "user": { "id": "01j9…", "name": "Ada Lovelace", "email": "ada@example.com" },
      "last_used_at": "2026-09-26T14:02:11+00:00",
      "expires_at": null,
      "created_at": "2026-09-01T09:30:00+00:00",
      "is_cli": false,
      "is_current": true
    }
  }
}

role is owner, admin, member or deployer. abilities is empty for a token created without an ability list, which grants every ability.

List your organizations

GET /account/organizations · account.read

Every organization the token's user belongs to. is_current marks the token's own organization. A token only acts on its own organization.

json
{
  "data": [
    { "id": "01j8…", "name": "Acme", "role": "admin", "is_current": true },
    { "id": "01j7…", "name": "Side project", "role": "owner", "is_current": false }
  ]
}

List CLI sessions

GET /account/sessions · account.read

Lists tokens created by dply-edge login in this organization, newest use first. Owners and admins see every member's sessions; other roles see their own. Each item has the same shape as token in Get the current token's account. Tokens created on Profile → API keys are not included.

Git accounts

Your linked GitHub, GitLab and Bitbucket accounts and access tokens. Their id is the account_id the GitHub webhook endpoints take.

Endpoint What it does
GET /account/git Each account's id, provider, kind (oauth or token) and label, plus link_url: the page where you link one by OAuth.
POST /account/git Add a personal access token. Body: provider (github, gitlab or bitbucket), token, optional label and api_base_url (a self-hosted GitLab, for example). The token is checked with the provider first; a refusal returns 422 saying what to fix.
DELETE /account/git/{account} Remove a token, or unlink an OAuth account. Unlinking your only way to sign in returns 422 until you set a password or add a passkey.

Revoke a CLI session

DELETE /account/sessions/{token} · account.write

Parameter In Description
token path The session's token ID.

Owners and admins can revoke any member's session. Other roles can revoke only their own.

bash
curl -X DELETE https://edge.dply.io/api/v1/account/sessions/01ja… \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json"
json
{ "message": "CLI session revoked.", "revoked_current": false }

If you revoke the token making the request, the message is CLI session revoked. This token no longer works. and revoked_current is true. An unknown ID returns 404 with CLI session not found.

Get instance capabilities

GET /capabilities · account.read

Describes what this dply instance offers. The CLI reads it before dply-edge init.

json
{
  "data": {
    "instance": { "url": "https://edge.dply.io", "name": "dply" },
    "kinds": {
      "edge": {
        "enabled": true,
        "cli_create_supported": false,
        "cli_create": false,
        "create_url": "https://edge.dply.io/…",
        "requires_git": true
      }
    }
  }
}

Billing

All billing endpoints need billing.read, and the token's user must be an organization owner or admin. Otherwise they return 403 with Org admin access is required to view billing. Amounts are in US cents.

Get the billing summary

GET /billing · billing.read

json
{
  "data": {
    "organization_id": "01j8…",
    "summary": {
      "monthly_total_cents": 2000,
      "daily_run_rate_cents": 67,
      "interval": "month",
      "subscribed": true,
      "stripe_status": "active",
      "next_invoice_at": "2026-10-01",
      "edge_count": 12
    },
    "plan": { "key": "pro", "label": "Pro", "price_cents": 2000 },
    "monthly_total_cents": 2000,
    "managed_subtotal_cents": 2000,
    "is_free": false,
    "counts": { "edge": 12 },
    "subscription": {
      "active": true,
      "status": "active",
      "on_grace_period": false,
      "ends_at": null,
      "interval": "month",
      "items": [{ "price_id": "price_…", "quantity": 1 }]
    }
  }
}

With no subscription, subscription is { "active": false, "items": [] }.

Get the cost breakdown

GET /billing/breakdown · billing.read

The current month's estimate by category and by line item. Categories with a zero amount are left out.

json
{
  "data": {
    "monthly_total_cents": 5200,
    "categories": [
      { "key": "plan", "label": "Pro", "cents": 2000, "color": "bg-brand-forest/70" },
      { "key": "usage", "label": "Usage", "cents": 3200, "color": "bg-brand-sage/50" }
    ],
    "line_items": [
      { "label": "Pro plan", "quantity": 1, "unit_cents": 2000, "line_cents": 2000, "detail": null },
      { "label": "Delivery (requests, bandwidth, site storage)", "quantity": 1, "unit_cents": 3200, "line_cents": 3200, "detail": null }
    ]
  }
}

Category keys are plan, seats and usage. color is a dashboard style hint; ignore it in your own tools.

List invoices

GET /billing/invoices · billing.read

The last 24 invoices, newest first.

json
{
  "data": {
    "invoices": [
      { "id": "in_…", "number": "ACME-0007", "date": "2026-09-01", "total_cents": 2400, "status": "paid", "paid": true }
    ]
  }
}

Apps

List apps

GET /edge/sites · edge.read

Every Edge app in the token's organization that the token's user can view, sorted by name. Preview apps are included, marked with is_preview.

bash
curl https://edge.dply.io/api/v1/edge/sites \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json"
json
{
  "data": [
    {
      "id": "01j9z3k6x8m2q4r5t7v9w0y1a2",
      "organization_id": "01j8…",
      "name": "marketing",
      "slug": "marketing",
      "status": "edge_active",
      "is_preview": false,
      "parent_site_id": null,
      "runtime_mode": "static",
      "hostname": "marketing.dply.app",
      "live_url": "https://marketing.dply.app",
      "dashboard_url": "https://edge.dply.io/projects/01j9…/sites/01j9z3k6…",
      "repository": "acme/marketing",
      "branch": "main",
      "repo_root": null,
      "active_deployment_id": "01ja…",
      "preview_protection": { "mode": "off", "enabled": false },
      "created_at": "2026-08-01T10:00:00+00:00",
      "updated_at": "2026-09-26T13:40:00+00:00"
    }
  ]
}
Field Description
status edge_provisioning, edge_active, edge_failed or edge_deleting.
runtime_mode static, hybrid, ssr or container.
hostname, live_url The app's dply hostname and URL.
repo_root The app's directory inside a monorepo, or null.
preview_protection The access rule in force. On a preview it is the parent's rule and adds inherited_from_site_id.

Get an app

GET /edge/sites/{site} · edge.read

Parameter In Description
site path The app ID.

Returns one app in the shape above, wrapped in data. Returns 404 with Edge site not found. if the ID is not an Edge app in the token's organization.

Set an app's icon

PUT /edge/sites/{site}/icon · edge.write

Send the image itself as the request body: PNG, JPEG, WebP, GIF or ICO, at most 1 MB. The type is read from the file, not the Content-Type header. Anything else returns 422. DELETE /edge/sites/{site}/icon goes back to the app's favicon, or its initials.

bash
curl -X PUT https://edge.dply.io/api/v1/edge/sites/01j9…/icon \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  --data-binary @icon.png

Deployments

A deployment object:

json
{
  "id": "01ja…",
  "site_id": "01j9…",
  "status": "live",
  "git_commit": "9f2c1ab7e4d0c3b2a1f0e9d8c7b6a5f4e3d2c1b0",
  "git_branch": "main",
  "storage_prefix": "sites/01j9…/01ja…",
  "pruned": false,
  "cf_kv_version": "…",
  "aliases": ["9f2c1ab-marketing.dply.app"],
  "repo_config": {
    "source_path": "dply.yaml",
    "build_overrides": {},
    "redirect_count": 3,
    "rewrite_count": 0,
    "header_rule_count": 1,
    "warning_count": 0
  },
  "failure_reason": null,
  "published_at": "2026-09-26T13:40:00+00:00",
  "failed_at": null,
  "created_at": "2026-09-26T13:38:12+00:00"
}

status is building, publishing, live, failed or superseded. pruned is true once the deployment's files have been removed, after which it cannot be rolled back to.

List deployments

GET /edge/sites/{site}/deployments · edge.read

Parameter In Description
limit query Number of deployments, newest first. Default 20, between 1 and 100.
bash
curl "https://edge.dply.io/api/v1/edge/sites/$SITE/deployments?limit=5" \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json"

Returns { "data": [ …deployment objects… ] }.

Trigger a deployment

POST /edge/sites/{site}/deployments · edge.deploy

With an empty body, dply rebuilds from the tip of the app's branch. With commit, it deploys that commit; if the commit was already built, dply publishes the existing build.

Parameter In Rules
commit body Optional. 7 to 40 hexadecimal characters.
branch body Optional, up to 200 characters. Used only with commit.
branch_tip body Optional boolean. Same as an empty body.
bash
curl -X POST https://edge.dply.io/api/v1/edge/sites/$SITE/deployments \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"commit": "9f2c1ab"}'

Returns 202 with the new deployment in data. Poll Get a deployment until status is live or failed. Returns 422 with a message if the deploy cannot start.

Get a deployment

GET /edge/sites/{site}/deployments/{deployment} · edge.read

Returns one deployment in data, or 404 with Deployment not found.

Roll back to a deployment

POST /edge/sites/{site}/deployments/{deployment}/rollback · edge.deploy

Points production at an earlier deployment. The deployment must not be pruned. Returns the deployment in data, or 422 with the reason.

bash
curl -X POST https://edge.dply.io/api/v1/edge/sites/$SITE/deployments/01ja…/rollback \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json"

Previews

Previews are separate apps linked to a parent. Call these endpoints with the parent app's ID; a preview ID in {site} returns 404.

List previews

GET /edge/sites/{site}/previews · edge.read

Returns the parent's live previews, newest first, as app objects (see List apps).

Create a preview

POST /edge/sites/{site}/previews · edge.deploy

Parameter In Rules
commit body Required. 7 to 40 hexadecimal characters.
branch body Optional, up to 200 characters. Defaults to the parent's branch, or main.
ref_kind body Optional. branch, tag or commit.
bash
curl -X POST https://edge.dply.io/api/v1/edge/sites/$SITE/previews \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"commit": "4be1d02", "branch": "feature/pricing"}'

Returns 202 with the preview app in data.

Delete a preview

DELETE /edge/sites/{site}/previews/{preview} · edge.deploy

Queues the preview's teardown.

json
{ "message": "Preview teardown queued." }

Status 202. Returns 404 with Preview not found. if the preview does not belong to the parent.

Promote a preview

POST /edge/sites/{site}/previews/{preview}/promote · edge.deploy

Copies the preview's build to production. The preview keeps running. Returns the new production deployment in data, or 422 with the reason.

Domains

List domains

GET /edge/sites/{site}/domains · edge.read

json
{
  "data": [
    {
      "hostname": "www.example.com",
      "mode": "manual",
      "dns_status": "active",
      "ssl_status": "active",
      "cname_target": "marketing.dply.app",
      "cf_custom_hostname_id": "…",
      "ownership_verification": null,
      "dply_verification": null,
      "analytics_zone": null,
      "attached_at": "2026-09-02T08:00:00+00:00",
      "verified_at": "2026-09-02T08:05:00+00:00",
      "error": null,
      "ssl_error": null
    }
  ]
}

When a domain needs a DNS record to prove ownership, ownership_verification or dply_verification holds the record to create. See Domain verification.

Add a domain

POST /edge/sites/{site}/domains · edge.write

Parameter In Rules
hostname body Required. A valid hostname with at least one dot, up to 253 characters.
bash
curl -X POST https://edge.dply.io/api/v1/edge/sites/$SITE/domains \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"hostname": "www.example.com"}'

Returns 202 with the DNS record to create:

json
{
  "data": [
    { "name": "www.example.com", "type": "CNAME", "value": "marketing.dply.app", "status": "pending" }
  ]
}

Plan limits on custom domains apply. Returns 422 with the reason when the domain cannot be added.

Verify a domain

POST /edge/sites/{site}/domains/{hostname}/verify · edge.write

Re-checks DNS and certificate status for a domain already added to the app. Returns the domain's updated record in data, or "data": null if the hostname is not attached to this app.

Remove a domain

DELETE /edge/sites/{site}/domains/{hostname} · edge.write

json
{ "message": "Custom domain removed." }

Aliases

List deployment aliases

GET /edge/sites/{site}/aliases · edge.read

Per-deployment hostnames for the newest 50 live or superseded deployments.

json
{
  "data": [
    {
      "hostname": "9f2c1ab-marketing.dply.app",
      "deployment_id": "01ja…",
      "git_commit": "9f2c1ab7e4…",
      "git_branch": "main",
      "published_at": "2026-09-26T13:40:00+00:00"
    }
  ]
}

Access protection

These endpoints manage the app's password or dply-account protection. See Access control. Calling them with a preview's ID returns 422 with Configure preview protection on the parent Edge site.

Get access protection

GET /edge/sites/{site}/access · edge.read

json
{
  "data": {
    "site_id": "01j9…",
    "mode": "dply_account",
    "enabled": true,
    "password_set": false,
    "allowed_emails": ["ada@example.com"],
    "account_login_url": "https://edge.dply.io/projects/sites/01j9…/preview-access"
  }
}

account_login_url appears only when mode is dply_account and protection is on. The password itself is never returned.

Update access protection

PATCH /edge/sites/{site}/access · edge.write

Parameter In Rules
mode body Required. off, password or dply_account.
password body Optional, up to 200 characters. Used with password mode.
allowed_emails body Optional array of up to 100 email addresses. Used with dply_account mode.
bash
curl -X PATCH https://edge.dply.io/api/v1/edge/sites/$SITE/access \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"mode": "password", "password": "correct horse battery staple"}'

Returns the updated rule in the shape above.

Cache

Purge the cache

POST /edge/sites/{site}/cache/purge · edge.write

Send either tag or paths. If both are sent, tag wins.

Parameter In Rules
tag body Up to 128 characters: letters, digits, ., _ and -.
paths body Array of up to 100 paths, each up to 2048 characters.
bash
curl -X POST https://edge.dply.io/api/v1/edge/sites/$SITE/cache/purge \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"paths": ["/", "/pricing"]}'
json
{ "data": { "ok": true, "purged_keys": 2, "message": "…" } }

Returns 200 when the purge succeeded and 422 (same body, "ok": false) when it did not. See Caching.

Usage

Get app usage

GET /edge/sites/{site}/usage · edge.read

Parameter In Description
days query Days of daily history. Default 30, between 1 and 90.

Returns this billing period's requests, egress, storage and cost estimate for the app, plus a daily series. Usage is collected once a day.

json
{
  "data": {
    "site_id": "01j9…",
    "site_name": "marketing",
    "platform_cents": 0,
    "platform_kind": "included",
    "usage_cents": 0,
    "total_cents": 0,
    "requests": 184220,
    "bytes_egress": 5210093312,
    "daily": [{ "date": "2026-09-25", "label": "Sep 25", "requests": 6120, "bytes_egress": 173004800, "cost_cents": 0 }],
    "requests_7d": 41200,
    "bytes_egress_7d": 1180000000,
    "last_collected_date": "2026-09-25",
    "byo_cloudflare": false
  }
}

The response has more fields than shown (storage operations, usage_detail, peak_day, tracked_hostnames). Before the first daily collection, or for a preview, data is { "message": "Usage not yet available — wait for the first nightly rollup." }. See Usage & metering.

Logs

Tail request logs

GET /edge/sites/{site}/logs · edge.read

Returns requests served by the app, oldest first. dply-edge edge logs --tail polls this endpoint.

Parameter In Description
since query ISO 8601 timestamp. Only requests after it are returned. Default: 60 seconds ago.
limit query Maximum rows. Default 100, between 1 and 500.
bash
curl "https://edge.dply.io/api/v1/edge/sites/$SITE/logs?since=2026-09-26T14:00:00Z" \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json"
json
{
  "data": [
    {
      "occurred_at": "2026-09-26T14:00:03+00:00",
      "deployment_id": "01ja…",
      "hostname": "www.example.com",
      "method": "GET",
      "path": "/pricing",
      "status": 200,
      "duration_ms": 14,
      "bytes_egress": 18342,
      "cache_status": "HIT",
      "country": "US"
    }
  ],
  "meta": {
    "since": "2026-09-26T14:00:00+00:00",
    "count": 1,
    "tail_cursor": "2026-09-26T14:00:03+00:00"
  }
}

To follow the log, pass meta.tail_cursor as since on the next request. See Logs.

Environment variables

These endpoints manage the app's production environment variables. Values are encrypted and never returned. Changes apply on the next deploy. {site} accepts the app ID or slug.

Keys must be uppercase, start with a letter, and contain only A–Z, 0–9 and _ (up to 128 characters). These names are reserved: HOST_MAP, ASSETS, ARTIFACTS, DEPLOYMENT_ID, SITE_ID, STORAGE_PREFIX, EDGE_ANALYTICS, LOG_INGEST_BASE_URL, LOG_INGEST_KEY, ENVIRONMENT.

List variable keys

GET /edge/sites/{site}/env · edge.env.read

json
{
  "data": [
    { "key": "API_BASE_URL", "updated_at": "2026-09-20T10:00:00+00:00", "created_at": "2026-08-01T10:00:00+00:00" }
  ]
}

Replace all variables

PUT /edge/sites/{site}/env · edge.env.write

The body is a JSON object of key-value pairs. It replaces the whole set: keys you leave out are deleted.

bash
curl -X PUT https://edge.dply.io/api/v1/edge/sites/$SITE/env \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"API_BASE_URL": "https://api.example.com", "FEATURE_FLAGS": "beta"}'

Returns the resulting key list, as in List variable keys. If any key is invalid, nothing is saved and the response is 422:

json
{ "message": "Invalid env payload.", "errors": { "host_map": "Key must be ALL_CAPS, start with a letter, and contain only A–Z, 0–9, and underscores." } }

Warning

PUT deletes every production variable not in the body. To change one variable, use PATCH.

Set one variable

PATCH /edge/sites/{site}/env/{key} · edge.env.write

Parameter In Rules
key path A valid key (see above).
value body A string or other scalar.
bash
curl -X PATCH https://edge.dply.io/api/v1/edge/sites/$SITE/env/API_BASE_URL \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"value": "https://api.example.com"}'
json
{ "data": { "key": "API_BASE_URL", "updated_at": "2026-09-26T14:10:00+00:00", "created_at": "2026-08-01T10:00:00+00:00" } }

Delete a variable

DELETE /edge/sites/{site}/env/{key} · edge.env.write

json
{ "deleted": 1 }

deleted is 0 if the key did not exist.

Config linting

Lint a config file

POST /edge/lint · edge.read

Validates the contents of a dply.yaml or dply.json without deploying. See Configuration files.

Parameter In Rules
path body Required. The file name, up to 255 characters, for example dply.yaml.
content body Required. The file contents, up to 65,536 characters.
bash
curl -X POST https://edge.dply.io/api/v1/edge/lint \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile c dply.yaml '{path: "dply.yaml", content: $c}')"
json
{
  "data": {
    "ok": true,
    "source_path": "dply.yaml",
    "errors": [],
    "warnings": [],
    "summary": {
      "redirects": 3, "rewrites": 0, "headers": 1,
      "build_keys": ["command", "output"],
      "tags": 0, "snippets": 0, "forms": 1
    }
  }
}

Status is 200 when ok is true and 422 when the file has errors.

App databases

An app's Postgres, MySQL and MongoDB databases. {database} is the database's ID or name. Creating, attaching, resizing and deleting one stay in the dashboard. See Databases.

List an app's databases

GET /edge/sites/{site}/databases · edge.read

The primary comes first.

json
{
  "data": [
    {
      "id": "01jd…", "name": "shop", "engine": "postgres", "primary": true, "env_prefix": "",
      "host": "pg-01jd….db.dply.io", "region": "weur", "size": "0.25", "suspend_seconds": 300, "disk_gb": 1,
      "last_backup_at": "2026-09-29T03:00:00+00:00", "transfer": null, "restore": null
    },
    { "id": "01je…", "name": "reports", "engine": "mysql", "primary": false, "env_prefix": "REPORTS", "…": "…" }
  ]
}

env_prefix is empty for the primary, which uses DB_* and DATABASE_URL. Another database's connection is behind its prefix, like REPORTS_DB_HOST and REPORTS_DATABASE_URL.

Get a database

GET /edge/sites/{site}/databases/{database} · edge.read

The same fields. transfer is the last export or import and restore is the last restore, each with a status of running, done or failed.

Run a query

POST /edge/sites/{site}/databases/{database}/query · edge.write

Parameter In Rules
sql body Postgres and MySQL: one read-only statement, up to 100,000 characters.
collection body MongoDB: the collection to search.
filter body MongoDB: an optional JSON filter. The default is {}.
bash
curl -X POST https://edge.dply.io/api/v1/edge/sites/$SITE/databases/reports/query \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT id, total FROM orders ORDER BY id DESC"}'
json
{ "data": { "columns": ["id", "total"], "rows": [[42, 1999]], "truncated": false, "ms": 3 } }

At most 200 rows come back; truncated says there were more. A query the database rejects returns 422. A database that doesn't answer returns 502.

Export a database

POST /edge/sites/{site}/databases/{database}/exports · edge.write

Starts an export and returns 202 with the database, its transfer.status set to running. An export or import that is already running returns 422.

GET /edge/sites/{site}/databases/{database}/exports · edge.read lists the exports, newest first, each with a download url.

Import a database

POST /edge/sites/{site}/databases/{database}/imports/upload-link · edge.write

Field In Notes
file body Required. Letters, digits, dot, dash and underscore.

Returns {"data": {"file": "20261029T140000-app.pgdump", "url": "…"}}. PUT the dump to url within an hour (curl -fT app.pgdump "<url>"), then load it by the returned file. The format is the one an export makes: pg_dump -Fc for Postgres, a gzipped mysqldump for MySQL, a mongodump archive for MongoDB.

POST /edge/sites/{site}/databases/{database}/imports · edge.write

Field In Notes
from body Required. imports for an uploaded file, exports for one of its exports.
file body Required. The uploaded file, or an export's file.

Returns 202 with transfer.status set to running. For Postgres and MySQL, the tables in the file are replaced and the others are left alone. For MongoDB, everything in the database is replaced. An export or import that is already running returns 422.

Restore to a point in time

POST /edge/sites/{site}/databases/{database}/restore · edge.write

Parameter In Rules
at body Required. A moment in the last 7 days. A time without a zone is read as UTC.

Returns 202 with restore.status set to running. A time outside the window, or a restore already running, returns 422.

Restore into a new database

POST /edge/sites/{site}/databases/{database}/restore-as-new · edge.write

Parameter In Rules
at body Required. As for a restore.
name body Required. The new database's name, unused in the organization.

Creates a database of the same engine, size and disk, attached to the app, and loads {database} into it as it was at at. {database} is only read. Returns 202 with the new database, restore.status running and restore.from the source. A time outside the window or a name already taken returns 422, and nothing is created.

Databases

Edge SQL databases belong to the organization. See Edge SQL (D1).

List databases

GET /edge/databases · edge.read

json
{
  "data": [
    { "id": "01jb…", "name": "app-db", "cloudflare_id": "…", "created_at": "2026-09-10T09:00:00+00:00" }
  ]
}

Create a database

POST /edge/databases · edge.write

Parameter In Rules
name body Required. Lowercase letters, numbers and dashes, up to 40 characters.
location body Optional: wnam, enam, weur, eeur, apac or oc. Left out, the database is placed automatically.

Returns 201 with the database in data. A taken name or the plan's database limit returns 422.

Delete a database

DELETE /edge/databases/{database} · edge.write

Deletes the database and all its data. Apps bound to it lose the binding.

Run SQL

POST /edge/databases/{database}/query · edge.write

Parameter In Rules
database path The database ID or name.
sql body Required, up to 100,000 characters.
params body Optional array of bound parameters for ? placeholders.
bash
curl -X POST https://edge.dply.io/api/v1/edge/databases/app-db/query \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT id, email FROM users WHERE id = ?", "params": [42]}'

Returns the database's query result in data. SQL errors return 422 with the database's message.

Warning

This runs any SQL, including writes and schema changes, against the live database.

Buckets

Storage buckets belong to the organization. An app attaches one as a resource (POST /edge/sites/{site}/resources with mode: attach).

List buckets

GET /edge/buckets · edge.read

json
{ "data": [{ "id": "dply-…-uploads", "name": "uploads", "used_by": [{ "site_id": "01jd…", "site": "shop", "disk": "uploads" }] }] }

Create a bucket

POST /edge/buckets · edge.write

Parameter In Rules
name body Required. 2 to 31 lowercase letters, numbers and dashes.
location body Optional: wnam, enam, weur, eeur, apac or oc.

Returns 201 with the bucket's id and name. A taken name returns 422.

Delete a bucket

DELETE /edge/buckets/{bucket} · edge.write

bucket is its name or id. A bucket an app still uses, or one with files in it, returns 422.

Mail

The organization's mail providers. An app attaches one as a mail resource and gets MAIL_* on its next deploy. They hold credentials, so every Mail endpoint needs edge.write and a role that can manage the organization. Secrets are never returned.

Endpoint What it does
GET /edge/mail/providers The providers, their non-secret settings, and the apps using each.
POST /edge/mail/providers Body: name (lowercase letters, numbers, dashes), provider (smtp, cloudflare, ses, postmark, resend, or mailgun), fields (the provider's settings, e.g. host, port, username, password for SMTP). Errors come back keyed name, provider or fields.<field>.
PATCH /edge/mail/providers/{provider} Same body; anything left out keeps its saved value, secrets included. provider is its id or name.
DELETE /edge/mail/providers/{provider} Refused (422) while an app uses it.
POST /edge/mail/providers/{provider}/test Sends one test email. Body: from (required), to (defaults to your own address). 422 with the provider's error when it fails.

Messages

dply's HTTP message queue for the organization. Every Messages endpoint returns 404 until it is turned on for your organization.

Endpoint What it does
GET /edge/messages Whether it is on, MESSAGES_URL, and the tokens (label, last four characters, app).
POST /edge/messages/enable Turn it on, with new signing keys.
POST /edge/messages/tokens Make a publishing token. Body: label (required, up to 60 characters). Returns 201 with the secret once; only its hash is kept.
DELETE /edge/messages/tokens/{token} Revoke a token by its id.
GET /edge/messages/keys current and next signing keys, for MESSAGES_SIGNING_KEY and MESSAGES_NEXT_SIGNING_KEY. Needs edge.write: they are secrets.
POST /edge/messages/keys/rotate The next key becomes current and a new next key is made. Returns the new pair.

Queues

Queues belong to the organization. See Queues.

List queues

GET /edge/queues · edge.read

json
{ "data": [{ "id": "01jc…", "name": "emails", "cloudflare_name": "…" }] }

Create a queue

POST /edge/queues · edge.write

Parameter In Rules
name body Required. Lowercase letters, numbers and dashes, up to 40 characters.

Returns 201 with the queue in data. A taken name or the plan's queue limit returns 422.

Delete a queue

DELETE /edge/queues/{queue} · edge.write

Deletes the queue and any messages waiting in it.

Send a message

POST /edge/queues/{queue}/messages · edge.write

Parameter In Rules
queue path The queue ID or name.
body body Required. Any JSON value.
bash
curl -X POST https://edge.dply.io/api/v1/edge/queues/emails/messages \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"body": {"type": "welcome", "user_id": 42}}'
json
{ "data": { "queued": true } }

Status 202.

Key-value stores

Key-value stores belong to the organization. Create and delete them in the dashboard. See Key-value (KV).

These endpoints are for admin work: seeding, inspecting and fixing keys from a script or CI. They share 60 requests per minute per organization across all its tokens. Over that, they return 429. Apps should read and write through their internal host or binding, which has no such limit.

Listing and reading keys, as well as writing and deleting them, also need the token's user to be an organization owner or admin: values often hold sessions and tokens. Otherwise they return 403. Listing stores does not.

If Cloudflare doesn't answer, these endpoints return 503. A store created in the last two minutes may not be listed yet.

In the paths, store is the store's ID or name. key is the key itself, URL-encoded. A / in a key can stay as it is.

List stores

GET /edge/kv · edge.read

json
{ "data": [{ "id": "4f1c…", "name": "cache" }] }

List keys

GET /edge/kv/{store}/keys · edge.read

Parameter In Rules
prefix query Optional. Only keys starting with this.
cursor query Optional. The cursor from the previous page.

Returns up to 1,000 keys per page. cursor is null on the last page.

json
{
  "data": [{ "name": "user:1", "expiration": 1790000000, "metadata": { "plan": "pro" } }],
  "cursor": "AArAbN…"
}

Read a key

GET /edge/kv/{store}/keys/{key} · edge.read

json
{ "data": { "key": "user:1", "value": "hello", "encoding": "utf-8", "metadata": { "plan": "pro" } } }

A value that isn't valid UTF-8 comes back base64-encoded, with encoding set to base64. A missing key returns 404.

Write a key

PUT /edge/kv/{store}/keys/{key} · edge.write

Parameter In Rules
value body Required. A string of up to 1 MB.
ttl body Optional. Seconds until the key expires, 60 or more.
expires_at body Optional. A unix time at least 60 seconds ahead. Send ttl or expires_at, not both.
metadata body Optional JSON object, up to 1024 bytes.
bash
curl -X PUT https://edge.dply.io/api/v1/edge/kv/cache/keys/user:1 \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"value": "hello", "ttl": 3600, "metadata": {"plan": "pro"}}'
json
{ "data": { "key": "user:1", "saved": true } }

Delete a key

DELETE /edge/kv/{store}/keys/{key} · edge.write

json
{ "data": { "key": "user:1", "deleted": true } }

Status pages

The organization's status pages. {page} is its id or slug. Viewers can read; deleting a page needs an owner or admin.

Endpoint What it does
GET /status-pages The pages, with slug, is_public and the public url.
POST /status-pages Body: name (required, up to 120 characters), description. New pages are public.
GET /status-pages/{page} The page, what it shows (monitors), and its incidents with their updates.
PATCH /status-pages/{page} Any of name, description, is_public.
DELETE /status-pages/{page} Deletes the page and its incidents.
POST /status-pages/{page}/monitors Body: site (an app id), optional uptime_monitor (one of that app's uptime checks), optional label. 422 when it is already on the page.
DELETE /status-pages/{page}/monitors/{monitor} Take it off the page.
POST /status-pages/{page}/incidents Body: title, impact (none, minor, major or critical), message (the first update). Starts as investigating.
PATCH /status-pages/{page}/incidents/{incident} Body: body (a new update), state (investigating, identified, monitoring or resolved), or both.

Notifications

Channels are where alerts go (Slack, email, webhooks and others). Routing decides which events reach which channel for an app. See Notification channels.

List channels

GET /notifications/channels · notifications.read

The channels the token's user can route events to: their personal channels, channels of teams whose channels they can manage, and, for organization owners and admins, the organization's channels.

json
{
  "data": [
    { "id": "01jd…", "label": "#deploys", "type": "slack", "destination": "Acme workspace", "owner": "Organization" }
  ]
}

destination is a redacted description, never the full webhook URL or key. owner is Organization, Team or User.

List the event catalog

GET /notifications/events · notifications.read

Parameter In Description
subject query site for the groups that can apply to an app. Omit it for the full catalog.
json
{
  "data": [
    {
      "key": "edge",
      "label": "Edge",
      "events": [
        { "key": "edge.deploy.succeeded", "label": "Edge deploy succeeded" },
        { "key": "edge.deploy.failed", "label": "Edge deploy failed (action required)" }
      ]
    }
  ]
}

Note

The full catalog also lists event groups that do not apply to Edge apps. Use GET /sites/{site}/notifications to see exactly which events an app can route.

Send a test message

POST /notifications/channels/{channel}/test · notifications.write

Sends the channel's test message, the same as Test in the dashboard.

json
{ "data": { "ok": true, "message": "…" } }

200 when the provider accepted it, 422 when it did not, 404 when the channel is not available to the token.

Get an app's event routing

GET /sites/{site}/notifications · notifications.read

{site} accepts the app ID or slug. Returns the event groups that apply to the app and, for each channel you can use, the events routed to it.

json
{
  "data": {
    "groups": [
      { "key": "site_uptime", "label": "…", "events": [{ "key": "site.uptime.down", "label": "Down & recovered" }] },
      { "key": "edge", "label": "Edge", "events": [{ "key": "edge.deploy.failed", "label": "Edge deploy failed (action required)" }] }
    ],
    "channels": [
      { "id": "01jd…", "label": "#deploys", "type": "slack", "events": ["edge.deploy.failed"] }
    ]
  }
}

Change an app's event routing

POST /sites/{site}/notifications · notifications.write

Adds and removes events for one channel. Events you do not mention are left as they are.

Parameter In Rules
channel body Required. A channel ID from List channels.
subscribe body Array of event keys to add.
unsubscribe body Array of event keys to remove.
bash
curl -X POST https://edge.dply.io/api/v1/sites/$SITE/notifications \
  -H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"channel": "01jd…", "subscribe": ["edge.deploy.failed", "site.uptime.down"]}'
json
{
  "data": {
    "channel": "01jd…",
    "events": ["edge.deploy.failed", "site.uptime.down"],
    "added": 1,
    "removed": 0
  }
}

An event that does not apply to the app returns 422 with Unknown event for this subject: … and the list of valid keys in events. Sending neither subscribe nor unsubscribe returns 422. A channel the token cannot use returns 404.

CLI sign-in

The CLI uses these two unauthenticated endpoints for dply-edge login. You do not need them unless you are building your own client. See CLI.

Start a sign-in

POST /auth/device/start

json
{
  "device_code": "…",
  "user_code": "ABCD-EFGH",
  "verification_uri": "https://edge.dply.io/auth/device",
  "verification_uri_complete": "https://edge.dply.io/auth/device?user_code=ABCD-EFGH",
  "expires_in": 900,
  "interval": 2
}

Send the user to verification_uri_complete to approve. The code expires after 15 minutes.

Poll for the token

POST /auth/device/poll

Parameter In Rules
device_code body Required, 16 to 128 characters.

Poll every interval seconds. The response is { "status": "pending" }, "denied" or "expired" until the user approves, then once:

json
{ "status": "authorized", "token": "dply_…" }

The token is returned only once. Later polls with the same code return expired.

View as Markdown llms.txt

dply/edge