Skip to main content

REST & GraphQL API

Authenticate to the Bex API, make REST and GraphQL requests, and handle workspace access, asynchronous operations, and errors.

Share
Last updated on November 5, 2026
On this page

Use the Bex API to automate service deployments, configuration, and operations. REST, GraphQL, and MCP share core operations, but their request shapes and available actions differ. Consult the guide for the operation you need; an endpoint accepting a request does not prove the resource is ready.

InterfaceHosted endpointUse
RESThttps://api.bex.co/v1/Scripts and integrations
GraphQLhttps://api.bex.co/graphqlStructured queries and dashboard operations
MCPhttps://api.bex.co/mcpAgent tools

For a self-hosted installation, use its API and OAuth origins. The operator-only quickstart does not set up all these services.

The Bex Website Content API describes public documentation and blog reads on bex.co. It is separate from these authenticated platform interfaces and does not describe service or database management. For the graded REST operation catalogue (method, path, summary, implementation grade) and interactive explorer, see the API reference. Platform contracts are maintained in the Bex API source.

Get a bearer token​

Create an API key in the dashboard's API Keys settings while signed in as an eligible developer or admin. Save its id and secret when it is created; the secret is returned only once. A Bex API key is an OAuth client credential pair: the secret itself is not a bearer token.

Set BEX_KEY_ID and BEX_KEY_SECRET from your secret store, then exchange them:

bash
export BEX_API_URL='https://api.bex.co'
export BEX_OAUTH_TOKEN_URL='https://oauth.bex.co/oauth2/token'
 
BEX_TOKEN=$(curl --fail-with-body --silent --show-error \
  "$BEX_OAUTH_TOKEN_URL" \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode "client_id=$BEX_KEY_ID" \
  --data-urlencode "client_secret=$BEX_KEY_SECRET" \
  | jq -er '.access_token')
export BEX_TOKEN
 
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $BEX_TOKEN" \
  "$BEX_API_URL/v1/services"

This example requires curl and jq. Keep shell tracing off around credentials. Use the token response's expires_in to schedule another exchange; do not assume an access token is permanent. Client-credentials tokens are renewed by exchanging the key again, rather than by a browser login.

The API also accepts a valid dashboard session when no bearer credential is supplied. A supplied bearer credential takes precedence, including when it is invalid. Browser sessions and user-consented OAuth clients have different credential-issuance permissions from machine callers.

API-key management uses POST /v1/api-keys with name and optional ownerId, GET /v1/api-keys?ownerId=…, and DELETE /v1/api-keys/{id}?ownerId=…. Creating a durable key requires an authorized direct human caller; an existing machine key or third-party agent cannot mint another key for itself. Rotate by creating a replacement, updating consumers, and revoking the old key.

Workspaces and permissions​

Machine API keys are bound to their workspace with the developer role. Creating one as an admin does not give the key admin permissions. Workspace administration, member invitations, GitHub connections, webhooks, and audit-log access require a suitable user credential instead. Use the dashboard's admin session or a user-authorized OAuth token with the required role and scopes for those operations; the client-credentials recipe above does not provide that role. See User-authorized OAuth integrations for a downloadable PKCE sample.

Use IDs returned by the API. Where an endpoint supports ownerId, supply the intended workspace explicitly, especially if your account belongs to several. By-ID requests must still pass authorization for the resource's workspace.

Workspace roles control resource access. Consented OAuth clients also need the relevant scopes: bex.read, bex.write, and/or bex.sensitive. A token scope does not grant a role the account lacks. Reading credentials, environment variables, or other sensitive values requires separate access from ordinary resource reads.

REST requests​

Service listings use entries containing a service object and cursor. Follow the endpoint's pagination fields instead of treating one page as the whole workspace. Default page size is 20 and the maximum is 100. Pass the last entry's cursor as the opaque cursor query parameter; an unknown cursor returns an empty tail. Concurrent creates, deletes, or renames can change what later pages contain — complete traversal of returned pages is not a transactional inventory.

A runnable walker lives in /examples/service-inventory/ and is shared with the OAuth user client sample. The suspended field is a string enum: suspended or not_suspended.

Use the returned service id for subsequent operations:

bash
export BEX_SERVICE_ID='replace-with-service-id'
 
curl --fail-with-body --silent --show-error \
  -H "Authorization: Bearer $BEX_TOKEN" \
  "$BEX_API_URL/v1/services/$BEX_SERVICE_ID"

POST /v1/services creates a service; a name already in use is a conflict, not a redeploy. Use deploy operations for an existing service or the Blueprint workflow to converge a manifest. After an accepted mutation, inspect the deployment or resource until it reaches the expected state. Avoid blindly retrying creation after a network timeout: check whether the first request created the resource. For CI that deploys a tested container digest to an image-backed service, see CI deploys a tested image digest.

GraphQL requests​

GraphQL has its own field names and input types. For example:

bash
curl --fail-with-body --silent --show-error \
  "$BEX_API_URL/graphql" \
  -H "Authorization: Bearer $BEX_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"query":"query { services { id type suspended url phase } }"}'

Inspect both data and errors: HTTP success alone does not establish that a GraphQL operation succeeded. Use the schema supported by your installed Bex version; Render's dashboard schema is not a compatibility guarantee for Bex.

Bex-only endpoints​

Endpoints Bex adds beyond Render's public REST API are documented in their guides, not in the Render-parity API reference:

  • GET /v1/usage returns the calling workspace's month-to-date usage: per-resource meter totals, an advisory cost estimate, the billing projection for callers that may see it, and a coverage record (complete / partial / unknown) describing how complete the totals are. See Month-to-date usage. Implemented in lego/backend/internal/usage/rest.go (RegisterREST); the same verb also answers GraphQL Query.usage and the MCP get_usage tool.
  • GET /v1/services/{serviceId}/deploy-hook and POST /v1/services/{serviceId}/deploy-hook/regenerate manage a service's secret deploy hook. See Deploy hooks.

Deploy hooks​

A deploy hook is a secret URL that starts a deploy of one service without an API token, for CI systems that can only make an HTTP request. A Render deploy hook URL does not work on Bex: after you migrate, recreate the hook on Bex and replace the URL wherever it is stored.

Reveal the hook in the dashboard under the service's Settings → Deploy Hook, where you can also copy or regenerate it. The API offers the same state, returned as {"url": "…"}:

ActionRESTGraphQLMCP
Reveal (created on first read)GET /v1/services/{serviceId}/deploy-hookdeployHook(serviceId)get_deploy_hook
RegeneratePOST /v1/services/{serviceId}/deploy-hook/regenerateregenerateDeployHook(serviceId)regenerate_deploy_hook

Revealing or regenerating a hook needs a signed-in person: a dashboard session or a user-authorized OAuth token with a role that can read sensitive values. A machine API key cannot reveal or regenerate one, because the URL is itself a durable credential. Regenerating replaces the URL immediately; the old URL then returns 404, so update every system that used it.

To trigger a deploy, send GET or POST to the URL:

bash
curl --fail-with-body --silent --show-error -X POST "$BEX_DEPLOY_HOOK_URL"

The URL has the form https://api.bex.co/v1/deploy-hooks?key=dhk-…. Add ref=<commit> to deploy a specific commit of a repository-backed service, or imgURL=<image> to deploy a different image to an image-backed service. A success returns 200 with {"deploy": {"id": "…"}}. An unknown or regenerated key returns 404. Each hook allows six triggers per minute with a burst of two; beyond that the response is 429 with a Retry-After header. Store the URL in your CI secret store and keep it out of logs.

To check which of your existing Render scripts, CI steps and deploy hooks work on Bex, paste them into the Render automation checker. It is a browser preflight: nothing you paste is sent anywhere, and its grades describe a bex source revision rather than the build running at api.bex.co.

Errors and compatibility​

ResultNext step
400Inspect the error and check field names, types, and supported settings.
401Renew credentials; verify API origin and OAuth configuration.
402Payment method required for this create. A billing manager completes Billing setup; then retry.
403Check workspace, role, OAuth scopes, and any protected-environment requirement.
404Verify the endpoint and resource ID in the intended workspace.
409Resolve a name or state conflict before retrying.
429Honor Retry-After and back off; caller and workspace budgets can both apply.
503Check whether a required platform backend is unavailable or unconfigured.

Rate limits depend on installation configuration and the operation. They are not a universal promise of 500 requests per minute for each key.

Bex implements many Render-shaped endpoints plus Bex extensions. Authentication, unsupported fields, and operational behavior can differ; changing a Render client's base URL is not sufficient migration validation. Start with compatibility and migration from Render, then verify each operation your integration uses.

Was this page helpful?

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex