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.
curl https://edge.dply.io/api/v1/account \
-H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/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.
{
"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.
curl -X DELETE https://edge.dply.io/api/v1/account/sessions/01ja… \
-H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/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.
{
"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
{
"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.
{
"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.
{
"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.
curl https://edge.dply.io/api/v1/edge/sites \
-H "Authorization: Bearer $DPLY_TOKEN" -H "Accept: application/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.
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:
{
"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. |
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. |
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.
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. |
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.
{ "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
{
"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. |
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:
{
"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
{ "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.
{
"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
{
"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. |
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. |
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"]}'
{ "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.
{
"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. |
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"
{
"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
{
"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.
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:
{ "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. |
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"}'
{ "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
{ "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. |
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}')"
{
"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.
{
"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 {}. |
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"}'
{ "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
{
"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. |
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
{ "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.
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
{ "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. |
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}}'
{ "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
{ "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.
{
"data": [{ "name": "user:1", "expiration": 1790000000, "metadata": { "plan": "pro" } }],
"cursor": "AArAbN…"
}
Read a key
GET /edge/kv/{store}/keys/{key} · edge.read
{ "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. |
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"}}'
{ "data": { "key": "user:1", "saved": true } }
Delete a key
DELETE /edge/kv/{store}/keys/{key} · edge.write
{ "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.
{
"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. |
{
"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.
{ "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.
{
"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. |
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"]}'
{
"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
{
"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:
{ "status": "authorized", "token": "dply_…" }
The token is returned only once. Later polls with the same code return expired.