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.
| Interface | Hosted endpoint | Use |
|---|---|---|
| REST | https://api.bex.co/v1/ | Scripts and integrations |
| GraphQL | https://api.bex.co/graphql | Structured queries and dashboard operations |
| MCP | https://api.bex.co/mcp | Agent 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:
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:
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:
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/usagereturns 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 inlego/backend/internal/usage/rest.go(RegisterREST); the same verb also answers GraphQLQuery.usageand the MCPget_usagetool.
Errors and compatibility
| Result | Next step |
|---|---|
400 | Inspect the error and check field names, types, and supported settings. |
401 | Renew credentials; verify API origin and OAuth configuration. |
402 | Payment method required for this create. A billing manager completes Billing setup; then retry. |
403 | Check workspace, role, OAuth scopes, and any protected-environment requirement. |
404 | Verify the endpoint and resource ID in the intended workspace. |
409 | Resolve a name or state conflict before retrying. |
429 | Honor Retry-After and back off; caller and workspace budgets can both apply. |
503 | Check 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.