Skip to main content
Use this API spec to build your custom connector after you’ve generated boilerplate code following the steps in Create your own connector. You can also reference the Datadog custom connector as you build your implementation.

Endpoints

GET /status

Checks the status of the connector. This endpoint is mainly used for successfully creating the app in Opal and verifying that the connector is properly configured. Query params Response params (200) No body is required in the response for status code 200. Response params (Error)

GET /resources

Returns a list of all resources for the app in question. This endpoint is called after you’ve imported resources to the app, so you may not see it being called when you first create your app. To distinguish resources pulled from multiple sources, you can include the type in the identifier, then base64-encode the ID so it can be properly parsed—e.g., base64(resource-type:1). Query params Response params (200) Response params (Error) Resource object Example response

GET /resources/{resource_id}

Get a specific resource by its ID. Path params Query params Response params (200) Resource object Response params (Error) Example response

GET /resources/{resource_id}/access_levels

If your resource doesn’t require any access level, this endpoint can return "access_levels": [].
Returns all available access levels for a resource, paginated. Access levels are exposed in the Opal dashboard and to end users as roles, which users select when they request access to the resource. Path params Query params Response params (200) Response params (Error) Access level object Example response

GET /resources/{resource_id}/users

Returns the users that currently have access to the provided resource. Path params Query params Response params (200) Response params (Error) ResourceUser object Example response

POST /resources/{resource_id}/users

Adds a user to the access list of the specified resource. Path params Body params (JSON encoded) Example request
Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

DELETE /resources/{resource_id}/users/{user_id}

Remove a user from the access list of the specified resource. Path params Query params Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

GET /groups

Returns a list of all groups for the app in question. The /groups endpoints are only called if Connector Groups is enabled in the custom app form. Query params Response params (200) Response params (Error) Group object Example response

GET /groups/{group_id}

Get a specific group by its id. Path params Query params Response params (200) Group object Response params (Error) Example response

GET /groups/{group_id}/users

Returns the users that currently belong to the provided group. Path params Query params Response params (200) Response params (Error) GroupUser object Example response

POST /groups/{group_id}/users

Adds a user to the access list of the specified group. Path params Body params (JSON encoded) Example request
Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

DELETE /groups/{group_id}/users/{user_id}

Remove a user from the access list of the specified group. Path params Query params Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

GET /groups/{group_id}/resources

Returns the resources that currently belong to the provided group. Path params Query params Response params (200) Response params (Error) GroupResource object Example response

POST /groups/{group_id}/resources

Adds a resource to the access list of the specified group. Path params Body params (JSON encoded) Example request
Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

DELETE /groups/{group_id}/resources/{resource_id}

Remove a resource from the access list of the specified group. Path params Query params Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

GET /groups/{group_id}/member-groups

Returns member groups for the specified group. Required if you enable Nested Groups when you create the custom app. Path params Query params Response params (200) Response params (Error) Member group object

POST /groups/{group_id}/member-groups

Add a member group to the specified group. Required if you enable Nested Groups when you create the custom app. Path params Body params (JSON encoded) Example request
Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

DELETE /groups/{group_id}/member-groups/{member_group_id}

Remove a member group from the specified group. Required if you enable Nested Groups when you create the custom app. Path params Query params Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

GET /users

Returns a list of users for your custom connector app. Query params Response params (200) Response params (Error) User object Example

POST /users

This endpoint enables users to be provisioned into a custom connector app. This functionality is only available if user provisioning has been enabled for the connection. Body params (JSON encoded) UserAttributes object UserTag object Response params (200) Response params (Error)

PUT /users/{user_id}

This endpoint updates a user’s identity in your custom connector app. Opal only calls this endpoint when Update users in app is enabled for the connection. The request body contains the same Opal-managed identity fields as create/delete—replace those fields in your end system and leave any end-system-only fields untouched. Path params Body params (JSON encoded) UserAttributes object UserTag object Response params (200) No body is required as part of this endpoint for status code 200. Response params (404) Return this status when the remote user does not exist in your end system. See Error Codes for the response body shape. Response params (Error)

DELETE /users/{user_id}

This endpoint deprovisions user accounts in a custom connector application. This functionality is only available if user deprovisioning has been enabled for the connection. Return 404 if the user has already been removed — Opal treats this as an idempotent success and does not surface it as an error. Path params UserAttributes object UserTag object Query params Response params (200) No body is required as part of this endpoint for status code 200. Response params (Error)

GET /events

This endpoint is only called if Connector Event Ingestion is enabled in the custom app form. Query params Response params (200) Response params (Error) Event object ActorUserIdentifier object EventContentLoginSuccess object Example

Error codes

All error responses use the following object: Return exactly these two fields with Content-Type: application/json. Extra or missing fields, or a non-JSON content type, mean Opal can’t decode the body — the HTTP status is still honored, but your message is not shown in the UI.

Success and failure

Opal uses the HTTP status code to decide whether a call succeeded. Any 2xx is treated as a success — a body field like code or status is never read as a failure signal. To report a failure, return a non-2xx HTTP status with the error object above. Opal does decode the 2xx response body on the data endpoints: every GET except /status, plus POST /users. On those, the response must use Content-Type: application/json and parse cleanly against the schema. For the GETs, a malformed or empty body is recorded as a failure. All other endpoints ignore the 2xx body entirely. POST /users must return {"remote_user_id": "<id>"} with the user’s identifier in your system — Opal stores this to correlate with the Opal user on later calls. An empty body is accepted as success but leaves Opal without the remote ID; a body present but not matching the schema (missing remote_user_id, extra fields, wrong types) fails the call.

Status codes and retries

Opal retries 429, 5xx (except 501), and network errors up to 3 times (4 attempts in total) with exponential backoff: 1s, then 2s, then 4s. The Retry-After header is honored on 429 and 503 — see Rate Limiting below. That column covers the HTTP request only. A call that fails without an immediate retry can still be re-attempted later: a provisioning push is re-sent if the request is approved again, and a failing deprovision is re-attempted on subsequent syncs before Opal gives up on it. Make your endpoints idempotent — no status code currently tells Opal that a failure is permanent and should not be re-attempted. Your message is shown in the UI for statuses that aren’t retried immediately. For 429 and 5xx, once the retry budget is spent the body is dropped and the UI shows a generic retry-exhaustion message instead (containing giving up after N attempt(s): status N).

Body code values

Opal reads the body code to classify the failure, but only on the error responses it does not retry — the statuses marked No in Status codes and retries above. On a retried status the body is dropped once the retry budget is spent, so the code never reaches Opal. For a non-retried error, the behavior depends on the endpoint:
  • Group, resource, and membership writes (POST/DELETE on /groups/{group_id}/users, /groups/{group_id}/resources, /groups/{group_id}/member-groups, and /resources/{resource_id}/users): code: 404 is recorded as permanent “resource not found”. Any other value is a generic remote error.
  • Every GET: code: 401 is recorded as “not authorized to query resource”, and code: 404 as “resource not found”. Any other value is a generic remote error. GET /resources/{resource_id} and GET /groups/{group_id} additionally treat HTTP 404 (or code: 404) as “no longer exists” and not an error.
  • User endpoints (POST/PUT/DELETE /users) ignore the body code. PUT /users/{user_id} maps HTTP 404 to “user not found”, and DELETE /users/{user_id} treats HTTP 404 as success (already deleted).
Set code to the same value as the HTTP status (404 for not found, 401 for not authorized) so both layers agree. The HTTP status itself determines success versus failure and whether Opal retries.

Rate Limiting

If your custom connector enforces rate limits, return a 429 HTTP status. Opal honors the Retry-After response header on both 429 and 503 to decide how long to wait before retrying. The header accepts either of two formats:
  • A non-negative integer number of seconds:
  • An HTTP-date in RFC 1123 format:
Without a Retry-After header, Opal falls back to the exponential backoff described in Status codes and retries. The overall retry budget (3 retries, 4 attempts total) applies either way.

Signature

To ensure that the API calls originate from Opal, we provide a header in each request that represents the encrypted request payload with a secret that is generated when creating the app connector in Opal. See Setup connector app in Opal for more details. On each HTTP request that Opal sends, we add an X-Opal-Signature HTTP header. The signature is created by combining the signing secret with the body of the request we’re sending using a standard HMAC-SHA256 keyed hash. Here is an example with Node to compute the signature using your signing secret. You may compare it against the value retrieved from the X-Opal-Signature header:
Note: when the body is empty, do coalesce the empty/null stringified body to {}. See the link below for more examples. Check Create your own connector for signature examples.
Last modified on October 20, 2026