devgate API
The devgate API manages your account from scripts, CI and infrastructure tools: create cloud endpoints and change their
Traffic Policy, reserve domains, stop tunnel sessions, and read recent traffic and this
month’s usage. It is a JSON API over HTTPS, modelled on ngrok’s, at https://api.devgate.online/v1.
It is not the agent API, which a running agent serves on 127.0.0.1:4040
for the tunnels on that machine.
Authentication
Create a key in the dashboard under Identity & Access → API Keys. Give it a description and choose
Read and write or Read only — a read-only key can make GET calls and nothing else. The secret
starts with dgapi_ and is shown once: devgate keeps only a hash of it, so a lost key is revoked and replaced,
not recovered. The list shows when each key was last used.
Send it as a bearer token on every call:
$ curl https://api.devgate.online/v1/endpoints -H "Authorization: Bearer $DEVGATE_API_KEY" A key acts as the account it was created in — in a team, the team’s account. The owner and admins create and revoke keys; members see them. Revoking takes effect on the next call. The key is not your authtoken, and the authtoken does not work here.
Requests and objects
- Send bodies as JSON with
Content-Type: application/json. -
Every object has an
idwith a type prefix (ep_endpoints,rd_reserved domains,ts_tunnel sessions,req_requests), itsuri, andcreated_at. Times are RFC 3339 in UTC. - Objects that point at others nest a reference:
{"id": "rd_88", "uri": "…"}. PATCHchanges only the fields you send.DELETEand stop calls answer 204 with no body.- An id from another account answers 404, the same as one that never existed.
Pagination
Lists come newest first, up to limit objects (100, the most, unless you ask for fewer). While there are more,
next_page_uri holds the next page’s URL — follow it until it is null. It is built from
before_id, the id of the last object on the page, and keeps your filters.
{
"endpoints": [
{ "id": "ep_415", "uri": "https://api.devgate.online/v1/endpoints/ep_415", … },
{ "id": "ep_412", "uri": "https://api.devgate.online/v1/endpoints/ep_412", … }
],
"uri": "https://api.devgate.online/v1/endpoints",
"next_page_uri": "https://api.devgate.online/v1/endpoints?limit=2&before_id=ep_412"
} Rate limits
Each key may make 120 requests a minute; every answer carries X-RateLimit-Limit and
X-RateLimit-Remaining. Past the limit, calls answer 429 with Retry-After in seconds.
Keys count separately, so a busy CI key doesn’t slow down your monitoring.
Errors
A failed call answers with an HTTP status and a body naming the error. msg is the same sentence the dashboard
shows; a field problem also lists every field under details.errors.
{
"error_code": "ERR_DEVGATE_100",
"status_code": 400,
"msg": "A cloud endpoint has no upstream, so its policy must end in a terminating action that runs on every request: forward-internal, custom-response, redirect or deny.",
"details": {
"errors": { "traffic_policy": ["A cloud endpoint has no upstream, …"] }
}
} | Code | Status | Meaning |
|---|---|---|
ERR_DEVGATE_100 | 400 | Invalid request: a field, a plan limit, or a state that doesn’t allow it. See msg. |
ERR_DEVGATE_101 | 401 | No API key in the Authorization header. |
ERR_DEVGATE_102 | 401 | The key is not valid: revoked, mistyped, or its account is gone. |
ERR_DEVGATE_103 | 403 | A read-only key on a call that changes something. |
ERR_DEVGATE_104 | 403 | The account is blocked. |
ERR_DEVGATE_105 | 403 | Not allowed on this object, e.g. changing an endpoint an agent started. |
ERR_DEVGATE_106 | 404 | No such object in this account, or no such call. |
ERR_DEVGATE_107 | 405 | The method isn’t used on this path. |
ERR_DEVGATE_108 | 409 | The object isn’t in a state for this, e.g. stopping a session that has ended. |
ERR_DEVGATE_109 | 429 | Rate limit reached. |
ERR_DEVGATE_110 | 500 | Something failed on our side. Retry later. |
Endpoints
Everything on the dashboard’s Endpoints page: cloud endpoints, reserved ones, and agent endpoints while their agent runs. Cloud endpoints are created, changed and deleted here; reserved ones changed and deleted; agent endpoints only read — the agent that started one sets its options.
| Call | What it does |
|---|---|
GET /endpoints | List endpoints. |
GET /endpoints/:id | One endpoint. |
POST /endpoints | Create a cloud endpoint: url, traffic_policy, optional description, bindings, pooling_enabled. |
PATCH /endpoints/:id | Change description, enabled or traffic_policy of a cloud or reserved endpoint. |
DELETE /endpoints/:id | Delete a cloud or reserved endpoint. To end an agent endpoint, stop its tunnel session. |
The endpoint object
{
"id": "ep_412",
"uri": "https://api.devgate.online/v1/endpoints/ep_412",
"created_at": "2026-10-29T09:12:44Z",
"updated_at": "2026-10-29T09:12:44Z",
"type": "cloud",
"name": "cloud-k2m9x0qa",
"url": "https://shop.devgate.run",
"proto": "https",
"bindings": ["public"],
"description": "Storefront",
"enabled": true,
"online": true,
"paused": false,
"pooling_enabled": false,
"traffic_policy": "on_http_request:\n - actions:\n - type: forward-internal\n config:\n url: https://web.example.internal",
"upstream_url": null,
"domain": { "id": "rd_88", "uri": "https://api.devgate.online/v1/reserved_domains/rd_88" },
"tunnel_session": null
} type—cloud,reserved(created in the dashboard, served by an agent), oragent(started by an agent with--urlor a random URL).enabled— switched on;online— serving now (a cloud endpoint while enabled, an agent one while its agent runs).paused— on a domain your plan keeps but doesn’t serve; an upgrade brings it back.traffic_policy— the policy as written, YAML or JSON; empty when there is none.upstream_url— where the agent forwards, e.g.http://localhost:3000; null on a cloud endpoint.tunnel_session— the agent session serving it now, if any.
Create a cloud endpoint
The URL must be on one of your domains, or end in .internal for an
internal one (the binding follows from the URL unless you pass
bindings). The policy must answer every request itself, and the endpoint counts toward your plan’s online
endpoints — the same checks as the dashboard’s.
curl https://api.devgate.online/v1/endpoints \
-H "Authorization: Bearer $DEVGATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://shop.devgate.run",
"description": "Storefront",
"traffic_policy": "on_http_request:\n - actions:\n - type: forward-internal\n config:\n url: https://web.example.internal"
}' It answers 201 with the endpoint.
Change the policy, switch it off
# Replace the Traffic Policy (an empty string removes a reserved endpoint's)
curl -X PATCH https://api.devgate.online/v1/endpoints/ep_412 \
-H "Authorization: Bearer $DEVGATE_API_KEY" -H "Content-Type: application/json" \
-d "$(jq -n --rawfile p policy.yml '{traffic_policy: $p}')"
# Take it offline without deleting it
curl -X PATCH https://api.devgate.online/v1/endpoints/ep_412 \
-H "Authorization: Bearer $DEVGATE_API_KEY" -H "Content-Type: application/json" \
-d '{"enabled": false}' A policy is checked before it is saved, as in the editor: a mistake answers 400 with the line and column, and paid actions on a plan without them answer 400 too.
Reserved domains
Your dev domain, branded names under devgate.run, and your own domains — the Domains page.
Custom domains wait in pending until their CNAME is verified.
| Call | What it does |
|---|---|
GET /reserved_domains | List domains. |
GET /reserved_domains/:id | One domain. |
POST /reserved_domains | Reserve domain, with an optional description. |
PATCH /reserved_domains/:id | Change its description. |
POST /reserved_domains/:id/verify | Check a custom domain’s CNAME now; answers with the domain. |
DELETE /reserved_domains/:id | Release it. The dev domain can’t be released. |
One name under devgate.run, like shop.devgate.run, is reserved as a branded domain
(Hobby and up); any other hostname is your own domain (Pay-as-you-go), and the answer carries the
cname_target to point it at:
curl https://api.devgate.online/v1/reserved_domains \
-H "Authorization: Bearer $DEVGATE_API_KEY" -H "Content-Type: application/json" \
-d '{"domain": "app.example.com", "description": "Production"}'
# After adding the CNAME record:
curl -X POST https://api.devgate.online/v1/reserved_domains/rd_91/verify -H "Authorization: Bearer $DEVGATE_API_KEY"
The domain object has domain, type (dev,
branded or custom), status (active or
pending), cname_target, verified_at,
checked_at, dns_error — what the last check found missing — and
paused.
Tunnel sessions
One per endpoint a running agent holds open. Stopping a session is Stop Endpoint in the dashboard: the agent leaves
that tunnel down — its other tunnels keep running — until it is restarted, and an agent endpoint goes away with its last session.
Agents older than 0.20.0 can’t be stopped remotely; their sessions show "stoppable": false.
| Call | What it does |
|---|---|
GET /tunnel_sessions | Sessions running now. |
GET /tunnel_sessions/:id | One session, running or ended. |
POST /tunnel_sessions/:id/stop | Stop it. |
A session has agent_version, os, ip,
running, last_heartbeat_at, stopped_at,
stoppable and its endpoint.
# Stop every tunnel serving ep_412
curl -s "https://api.devgate.online/v1/tunnel_sessions" -H "Authorization: Bearer $DEVGATE_API_KEY" \
| jq -r '.tunnel_sessions[] | select(.endpoint.id == "ep_412") | .uri' \
| xargs -I{} curl -X POST {}/stop -H "Authorization: Bearer $DEVGATE_API_KEY" Requests
The HTTP requests Traffic Inspector shows, newest first, with its filters: endpoint_id,
domain_id, method, status (2xx,
3xx, 4xx, 5xx or error) and
q, a piece of the path.
| Call | What it does |
|---|---|
GET /requests | List requests: method, path, status, error, duration, client IP, host. |
GET /requests/:id | One request, with headers and bodies when full capture kept them. |
$ curl "https://api.devgate.online/v1/requests?endpoint_id=ep_412&status=5xx&limit=20" -H "Authorization: Bearer $DEVGATE_API_KEY"
With full capture on, capture holds the request and response headers and the first 10 KB of each body —
text as text, anything else as base64.
Usage
GET /usage answers with this month’s figures from Usage & Limits: transfer
(bytes_in, bytes_out), requests,
connections, endpoint_hours, policy_units, what the plan
included, the credit and how much is used and
remaining, period_cost with its breakdown in US dollars, and
usage per endpoint under endpoints. See usage and limits for
what each meter counts.
$ curl https://api.devgate.online/v1/usage -H "Authorization: Bearer $DEVGATE_API_KEY" | jq '{period_cost, used, remaining}' Questions people actually ask
The authtoken connects agents: it is what devgate config add-authtoken saves. An API key calls the REST API from scripts, CI or Terraform. Neither works in place of the other, and you can revoke API keys without touching any running agent.
The API follows ngrok’s conventions — endpoints, reserved_domains and tunnel_sessions, objects with id, uri and created_at, next_page_uri for paging and errors as error_code, status_code and msg — so porting a script is mostly a change of base URL and key. The fields are devgate’s own, though, so a client generated for ngrok is not guaranteed to work unchanged.
The account owner and admins. Members see the keys and when each was last used, but cannot create or revoke them. A key belongs to the account, not to the person who created it.