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": [].
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
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
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
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
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. Any2xx 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/DELETEon/groups/{group_id}/users,/groups/{group_id}/resources,/groups/{group_id}/member-groups, and/resources/{resource_id}/users):code: 404is recorded as permanent “resource not found”. Any other value is a generic remote error. - Every
GET:code: 401is recorded as “not authorized to query resource”, andcode: 404as “resource not found”. Any other value is a generic remote error.GET /resources/{resource_id}andGET /groups/{group_id}additionally treat HTTP404(orcode: 404) as “no longer exists” and not an error. - User endpoints (
POST/PUT/DELETE/users) ignore the bodycode.PUT /users/{user_id}maps HTTP404to “user not found”, andDELETE /users/{user_id}treats HTTP404as success (already deleted).
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 a429 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:
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 anX-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:
{}. See the link below for more examples.
Check Create your own connector for signature examples.