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 id with a type prefix (ep_ endpoints, rd_ reserved domains, ts_ tunnel sessions, req_ requests), its uri, and created_at. Times are RFC 3339 in UTC.
  • Objects that point at others nest a reference: {"id": "rd_88", "uri": "…"}.
  • PATCH changes only the fields you send. DELETE and 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.

GET /endpoints?limit=2
{
  "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.

400
{
  "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, …"] }
  }
}
CodeStatusMeaning
ERR_DEVGATE_100400Invalid request: a field, a plan limit, or a state that doesn’t allow it. See msg.
ERR_DEVGATE_101401No API key in the Authorization header.
ERR_DEVGATE_102401The key is not valid: revoked, mistyped, or its account is gone.
ERR_DEVGATE_103403A read-only key on a call that changes something.
ERR_DEVGATE_104403The account is blocked.
ERR_DEVGATE_105403Not allowed on this object, e.g. changing an endpoint an agent started.
ERR_DEVGATE_106404No such object in this account, or no such call.
ERR_DEVGATE_107405The method isn’t used on this path.
ERR_DEVGATE_108409The object isn’t in a state for this, e.g. stopping a session that has ended.
ERR_DEVGATE_109429Rate limit reached.
ERR_DEVGATE_110500Something 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.

CallWhat it does
GET /endpointsList endpoints.
GET /endpoints/:idOne endpoint.
POST /endpointsCreate a cloud endpoint: url, traffic_policy, optional description, bindings, pooling_enabled.
PATCH /endpoints/:idChange description, enabled or traffic_policy of a cloud or reserved endpoint.
DELETE /endpoints/:idDelete a cloud or reserved endpoint. To end an agent endpoint, stop its tunnel session.

The endpoint object

endpoint
{
  "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), or agent (started by an agent with --url or 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.

shell
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

shell
# 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.

CallWhat it does
GET /reserved_domainsList domains.
GET /reserved_domains/:idOne domain.
POST /reserved_domainsReserve domain, with an optional description.
PATCH /reserved_domains/:idChange its description.
POST /reserved_domains/:id/verifyCheck a custom domain’s CNAME now; answers with the domain.
DELETE /reserved_domains/:idRelease 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:

shell
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.

CallWhat it does
GET /tunnel_sessionsSessions running now.
GET /tunnel_sessions/:idOne session, running or ended.
POST /tunnel_sessions/:id/stopStop it.

A session has agent_version, os, ip, running, last_heartbeat_at, stopped_at, stoppable and its endpoint.

shell
# 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.

CallWhat it does
GET /requestsList requests: method, path, status, error, duration, client IP, host.
GET /requests/:idOne 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

What is the difference between an API key and the authtoken?

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.

Does ngrok’s API client work with devgate?

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.

Who in a team can create API keys?

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.