API Spec
Endpoints)
GET /status
/status
Checks the status of the connector, it's mainly used for successfully creating the app in Opal, and verifying that the connector is properly set up.
Query params
param | type | description |
---|---|---|
app_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints |
Response params (200)
No body is required as part of this endpoint for status code 200.
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
GET /resources
/resources
Returns a list of all resources for the app in question.
Query params
param | type | description |
---|---|---|
app_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints |
cursor | string | For pagination. Empty string on the first call, then set to the value provided via next_cursor |
parent_id | string | Optional: This parameter is used for nested resources. If your connector supports this feature, it will receive the ID of the parent resource and return its immediate children. See Create your own connector for more information. |
Response params (200)
param | type | description |
---|---|---|
resources | array of objects | List of the resource objects. See below for what each resource should include. |
next_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
Resource object
field | type | description |
---|---|---|
id | string | The id of the resource that uniquely identifies it in your system. |
name | string | The name of the resource |
description | string | The description of the resource |
Example response
{
"next_cursor": "gjroieapghnfagjfdgpadshfasd",
"resources": [
{
"id": "1",
"name": "Gooli Metadata",
"description": "Access to Gooli customer metadata"
},
{
"id": "2",
"name": "Eviato Metadata",
"description": "Access to Eviato customer metadata"
},
{
"id": "3",
"name": "Moolybib Metadata",
"description": "Access to Moolybib customer metadata"
}
]
}
GET /resources/{resource_id}
/resources/{resource_id}
Get a specific resource by its id.
Path params
param | type | description |
---|---|---|
resource_id | string | The id of the resource |
Query params
param | type | description |
---|---|---|
app_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints |
Response params (200)
field | type | description |
---|---|---|
resource | object | Resource object |
Resource object
field | type | description |
---|---|---|
id | string | The id of the resource that uniquely identifies it in your system. It should match the ID (<resource_id> ) of the request. |
name | string | The name of the resource |
description | string | The description of the resource |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
Example response
# GET /resources/1?app_id=app_unique_id
{
"resource": {
"id": "1",
"name": "Gooli Metadata",
"description": "Access to Gooli customer metadata"
}
}
GET /resources/{resource_id}/access_levels
/resources/{resource_id}/access_levels
Returns all the available access levels for a resource, paginated.
Path params
param | type | description |
---|---|---|
resource_id | string | The id of the resource you’re trying to retrieve the access levels of |
Query params
param | type | description |
---|---|---|
app_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints |
cursor | string | For pagination. Empty string on the first call, then set to the value provided via next_cursor |
Response params (200)
param | type | description |
---|---|---|
access_levels | array of objects | List of the access levels objects. See below for what each object should include. Hot tip: if your resource doesn't require any access level, it's allowed to return [] here. |
next_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
Access level object
field | type | description |
---|---|---|
id | string | ID of the access level to grant to this user. |
name | string | The user-facing name of the access level. |
Example response
{
"next_cursor": "gjroieapghnfagjfdgpadshfasd",
"access_levels": [
{
"id": "1",
"name": "Admin"
},
{
"id": "2",
"name": "Read-Only Admin"
},
{
"id": "3",
"name": "Guest"
}
]
}
Hot tip: if your resource doesn't require any access level, it's allowed to return
"access_levels": []
here.
GET /resources/{resource_id}/users
/resources/{resource_id}/users
Returns the users that currently have access to the provided resource.
Path params
param | type | description |
---|---|---|
resource_id | string | The id of the resource you’re trying to list the details of, as returned as id in the /resources/<resource_id> endpoint. |
Query params
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
cursor | string | For pagination. Empty string on the first call, then passed values provided via next_cursor |
Response params (200)
param | type | description |
---|---|---|
users | array of objects | List of the resource user objects. See below for what each resource should include |
next_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message to expose in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
ResourceUser object
field | type | description |
---|---|---|
user_id | string | The user identifier used in the remote system, this will be provided back to you when making access changes. |
string | The email of the user. This will be used on our end to correlate it with Opal users. Although not required at the moment, we'd like implementers to include this value as well. | |
access_level | object | The access level granted to the user. If omitted, the default access level remote ID value (empty string) is used. |
Example response
{
"next_cursor": "bpaubmkeospb",
"users": [
{
"email": "[email protected]",
"user_id": "1",
"access_level": {
"id": "1",
"name": "Admin"
}
},
{
"email": "[email protected]",
"user_id": "2",
"access_level": {
"id": "2",
"name": "Read-Only Admin"
}
}
]
}
POST /resources/{resource_id}/users
/resources/{resource_id}/users
Adds a user to the access list of the specified resource.
Path params
param | type | description |
---|---|---|
resource_id | string | The id of the resource you’re trying to add the user to |
Body params (JSON encoded)
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
user_id | string | The id of the user to add to the resource |
access_level_id | string (optional) | The ID of the access level to assign to the user. |
Example request
{
"app_id": "datadog-production",
"user_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65",
"access_level_id": "12f5ef35-5de6-4224-a006-7fe4c20db5c6"
}
Response params (200)
No body is required as part of this endpoint for status code 200.
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
DELETE /resources/{resource_id}/users/{user_id}
/resources/{resource_id}/users/{user_id}
Remove a user from the access list of the specified resource.
Path params
param | type | description |
---|---|---|
resource_id | string | The id of the resource to be edited. |
user_id | string | The id of the user to be removed. |
Query params
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
access_level_id | string (optional) | The ID of the access level associated to the user for this resource. |
Response params (200)
No body is required as part of this endpoint for status code 200.
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
GET /groups
/groups
Returns a list of all groups for the app in question.
Query params
param | type | description |
---|---|---|
app_id | string | The Opal app ID to list groups for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints |
cursor | string | For pagination. Empty string on the first call, then set to the value provided via next_cursor |
Response params (200)
param | type | description |
---|---|---|
groups | array of objects | List of the group objects. See below for what each group should include |
next_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all groups have been fetched. |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
Group object
field | type | description |
---|---|---|
id | string | The id of the group that uniquely identifies it in your system. |
name | string | The name of the group |
description | string | The description of the group |
Example response
{
"next_cursor": "gjroieapghnfagjfdgpadshfasd",
"groups": [
{
"id": "1",
"name": "Eng team",
"description": "Eng team users"
},
{
"id": "2",
"name": "Finance team (North America)",
"description": "Users in the finance team in NA"
}
]
}
GET /groups/{group_id}
/groups/{group_id}
Get a specific group by its id.
Path params
param | type | description |
---|---|---|
group_id | string | The id of the resource |
Query params
param | type | description |
---|---|---|
app_id | string | The Opal app ID to list resources for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints |
Response params (200)
field | type | description |
---|---|---|
group | object | A Group object. |
Group object
field | type | description |
---|---|---|
id | string | The id of the resource that uniquely identifies it in your system. |
name | string | The name of the resource |
description | string | The description of the resource |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
Example response
# GET /groups/1?app_id=app_unique_id
{
"group": {
"id": "1",
"name": "Eng team",
"description": "Your friendly eng team."
}
}
GET /groups/{group_id}/users
/groups/{group_id}/users
Returns the users that currently belong to the provided group.
Path params
param | type | description |
---|---|---|
group_id | string | The id of the group you’re trying to list the details of |
Query params
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
cursor | string | For pagination. Empty string on the first call, then passed values provided via next_cursor |
Response params (200)
param | type | description |
---|---|---|
users | array of objects | List of the group user objects. See below for what each group should include |
next_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message to expose in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
GroupUser object
field | type | description |
---|---|---|
user_id | string | The user identifier used in the remote system, this will be provided back to you when making access changes. |
string | The email of the user. This will be used on our end to correlate it with Opal users. Although not required at the moment, we'd like implementers to include this value as well. |
Example response
{
"next_cursor": "bpaubmkeospb",
"users": [
{
"email": "[email protected]",
"user_id": "1",
},
{
"email": "[email protected]",
"user_id": "2",
}
]
}
POST /groups/{group_id}/users
/groups/{group_id}/users
Adds a user to the access list of the specified group.
Path params
param | type | description |
---|---|---|
group_id | string | The id of the group you’re trying to add the user to |
Body params (JSON encoded)
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
user_id | string | The id of the user to add to the group |
Example request
{
"app_id": "datadog-production",
"user_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65",
}
Response params (200)
No body is required as part of this endpoint for status code 200.
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
DELETE /groups/{group_id}/users/{user_id}
/groups/{group_id}/users/{user_id}
Remove a user from the access list of the specified group.
Path params
param | type | description |
---|---|---|
group_id | string | The id of the group to be edited. |
user_id | string | The id of the user to be removed. |
Query params
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
Response params (200)
No body is required as part of this endpoint for status code 200.
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
GET /groups/{group_id}/resources
/groups/{group_id}/resources
Returns the resources that currently belong to the provided group.
Path params
param | type | description |
---|---|---|
group_id | string | The id of the group you’re trying to list the details of |
Query params
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
cursor | string | For pagination. Empty string on the first call, then passed values provided via next_cursor |
Response params (200)
param | type | description |
---|---|---|
resources | array of objects | List of the group resource objects. See below for what each group should include |
next_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message to expose in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
GroupResource object
field | type | description |
---|---|---|
resource_id | string | The resource identifier used in the remote system, this will be provided back to you when making access changes. |
access_level | object | The access level the group has access to the resource with. |
Example response
{
"next_cursor": "bpaubmkeospb",
"resources": [
{
"resource_id": "1",
"access_level": {
"id": "1",
"name": "Admin"
}
},
{
"resource_id": "2",
"access_level": {
"id": "2",
"name": "Read-Only Admin"
}
}
]
}
POST /groups/{group_id}/resources
/groups/{group_id}/resources
Adds a resource to the access list of the specified group.
Path params
param | type | description |
---|---|---|
group_id | string | The id of the group you’re trying to add the resource to |
Body params (JSON encoded)
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
resource_id | string | The id of the resource to add to the group |
access_level_id | string (optional) | The ID of the access level to assign to the group to the resource. |
Example request
{
"app_id": "datadog-production",
"resource_id": "568d0cd1-284d-4e82-a6c7-9c98b5e51a65",
"access_level_id": "12f5ef35-5de6-4224-a006-7fe4c20db5c6"
}
Response params (200)
No body is required as part of this endpoint for status code 200.
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
DELETE /groups/{group_id}/resources/{resource_id}
/groups/{group_id}/resources/{resource_id}
Remove a resource from the access list of the specified group.
Path params
param | type | description |
---|---|---|
group_id | string | The id of the group to be edited. |
resource_id | string | The id of the resource to be removed. |
Query params
param | type | description |
---|---|---|
app_id | string | The app id specified by the end user during connection process. This can be used in your end system to distinguish apps if you are supporting multiple apps via the same set of endpoints |
access_level_id | string (optional) | The ID of the access level associated to the group for this resource. |
Response params (200)
No body is required as part of this endpoint for status code 200.
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
GET /users
/users
Returns a list of users for your custom connector app.
Query params
param | type | description |
---|---|---|
app_id | string | The Opal app ID to list users for. This allows your service to distinguish apps if it’s supporting multiple apps via the same set of endpoints |
cursor | string | For pagination. Empty string on the first call, then set to the value provided via next_cursor |
Response params (200)
param | type | description |
---|---|---|
users | array of objects | List of the user objects. See below for what each user should include |
next_cursor | string | The cursor that should be used for the next call. If cursor is an empty string, it is assumed that all resources have been fetched. |
Response params (Error)
param | type | description |
---|---|---|
message | string | An error message that will be exposed in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
User object
field | type | description |
---|---|---|
id | string | The id of the user that uniquely identifies it in your system. |
string | The email of the user, this is required to associate the user in Opal. |
Example
{
"next_cursor": "kjhiufewnoi",
"users": [
{
"email": "[email protected]",
"id": "6bac7de4-08d7-4437-8397-75f44b6f76ae"
},
{
"email": "[email protected]",
"id": "c1623a9c-cfb8-4c50-bd89-ea05bf597d60"
},
{
"email": "[email protected]",
"id": "968bf191-ba81-4fa0-a5a9-aaff71655fbd"
},
{
"email": "[email protected]",
"id": "d67d4184-6cdf-4c43-9d65-698997bd0c52"
}
]
}
Error codes
All error codes need to implement the following object:
param | type | description |
---|---|---|
message | string | An error message to expose in the Opal UI. |
code | integer | The error code that describes the error. See Error Codes for details. |
In addition, you can distinguish the type of error and meaning by following these codes
Status code | Meaning |
---|---|
200 | Response successful. |
401 | Invalid request signature error. |
404 | Entity not found (eg. resource, user, access level). |
500 | Unexpected error. |
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:
const timestamp = request.header('X-Opal-Request-Timestamp')
const signingSecret = 'SIGNING_SECRET'
// In Typescript/JS, request.body is always an empty object
const sigBaseString = 'v0:' + timestamp + ':' + JSON.stringify(request.body)
const hmac = crypto.createHmac('sha256', signingSecret);
hmac.write(sigBaseString)
console.log(hmac.digest('hex'))
func generateSignature(
signingSecret string,
timestamp string,
serializedBlob []byte,
) (string, error) {
// Concatenate base string
sigBaseString := "v0:" + timestamp + ":" + string(serializedBlob)
// Hash base string to get signature
hash := hmac.New(sha256.New, []byte(signingSecret))
_, err := hash.Write([]byte(sigBaseString))
if err != nil {
return "", errors.Wrap(err, "error writing hash")
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
func validateOpalSignature(signingSecret string) gin.HandlerFunc {
return func(c *gin.Context) {
opalSignature := c.GetHeader("X-Opal-Signature")
if opalSignature == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, &Error{
Code: http.StatusUnauthorized,
Message: "X-Opal-Signature header is missing",
})
return
}
opalRequestTimestamp := c.GetHeader("X-Opal-Request-Timestamp")
if opalRequestTimestamp == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, &Error{
Code: http.StatusUnauthorized,
Message: "X-Opal-Request-Timestamp header is missing",
})
return
}
var bodyStr string
// Read request body, once the request body is read, it cannot be read again
// so we need to save it in a variable and then reassign it to the Request.Body
var bodyBytes []byte
var err error
if c.Request.Body != nil {
bodyBytes, err = ioutil.ReadAll(c.Request.Body)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, &Error{
Code: http.StatusInternalServerError,
Message: "Unable to read request body",
})
return
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
bodyStr = strings.TrimSpace(string(bodyBytes))
}
if bodyStr == "" {
bodyStr = "{}"
}
signature, err := GenerateSignature(signingSecret, opalRequestTimestamp, []byte(bodyStr))
if signature != opalSignature || err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, &Error{
Code: http.StatusUnauthorized,
Message: "Invalid signature",
})
return
}
c.Next()
}
}
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.
Updated 11 months ago