Endpoints)

GET /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

paramtypedescription
app_idstringThe 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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

GET /resources

Returns a list of all resources for the app in question.

Query params

paramtypedescription
app_idstringThe 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
cursorstringFor pagination. Empty string on the first call, then set to the value provided via next_cursor
parent_idstringOptional: 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)

paramtypedescription
resourcesarray of objectsList of the resource objects. See below for what each resource should include.
next_cursorstringThe 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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

Resource object

fieldtypedescription
idstringThe id of the resource that uniquely identifies it in your system.
namestringThe name of the resource
descriptionstringThe 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}

Get a specific resource by its id.

Path params

paramtypedescription
resource_idstringThe id of the resource

Query params

paramtypedescription
app_idstringThe 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)

fieldtypedescription
resourceobjectResource object

Resource object

fieldtypedescription
idstringThe id of the resource that uniquely identifies it in your system. It should match the ID (<resource_id>) of the request.
namestringThe name of the resource
descriptionstringThe description of the resource

Response params (Error)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe 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

Returns all the available access levels for a resource, paginated.

Path params

paramtypedescription
resource_idstringThe id of the resource you’re trying to retrieve the access levels of

Query params

paramtypedescription
app_idstringThe 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
cursorstringFor pagination. Empty string on the first call, then set to the value provided via next_cursor

Response params (200)

paramtypedescription
access_levelsarray of objectsList 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_cursorstringThe 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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

Access level object

fieldtypedescription
idstringID of the access level to grant to this user.
namestringThe 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

Returns the users that currently have access to the provided resource.

Path params

paramtypedescription
resource_idstringThe id of the resource you’re trying to list the details of, as returned as id in the /resources/<resource_id> endpoint.

Query params

paramtypedescription
app_idstringThe 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
cursorstringFor pagination. Empty string on the first call, then passed values provided via next_cursor

Response params (200)

paramtypedescription
usersarray of objectsList of the resource user objects. See below for what each resource should include
next_cursorstringThe 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)

paramtypedescription
messagestringAn error message to expose in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

ResourceUser object

fieldtypedescription
user_idstringThe user identifier used in the remote system, this will be provided back to you when making access changes.
emailstringThe 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_levelobjectThe 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

Adds a user to the access list of the specified resource.

Path params

paramtypedescription
resource_idstringThe id of the resource you’re trying to add the user to

Body params (JSON encoded)

paramtypedescription
app_idstringThe 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_idstringThe id of the user to add to the resource
access_level_idstring (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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

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

Remove a user from the access list of the specified resource.

Path params

paramtypedescription
resource_idstringThe id of the resource to be edited.
user_idstringThe id of the user to be removed.

Query params

paramtypedescription
app_idstringThe 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_idstring (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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

GET /groups

Returns a list of all groups for the app in question.

Query params

paramtypedescription
app_idstringThe 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
cursorstringFor pagination. Empty string on the first call, then set to the value provided via next_cursor

Response params (200)

paramtypedescription
groupsarray of objectsList of the group objects. See below for what each group should include
next_cursorstringThe 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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

Group object

fieldtypedescription
idstringThe id of the group that uniquely identifies it in your system.
namestringThe name of the group
descriptionstringThe 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}

Get a specific group by its id.

Path params

paramtypedescription
group_idstringThe id of the resource

Query params

paramtypedescription
app_idstringThe 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)

fieldtypedescription
groupobjectA Group object.

Group object

fieldtypedescription
idstringThe id of the resource that uniquely identifies it in your system.
namestringThe name of the resource
descriptionstringThe description of the resource

Response params (Error)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe 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

Returns the users that currently belong to the provided group.

Path params

paramtypedescription
group_idstringThe id of the group you’re trying to list the details of

Query params

paramtypedescription
app_idstringThe 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
cursorstringFor pagination. Empty string on the first call, then passed values provided via next_cursor

Response params (200)

paramtypedescription
usersarray of objectsList of the group user objects. See below for what each group should include
next_cursorstringThe 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)

paramtypedescription
messagestringAn error message to expose in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

GroupUser object

fieldtypedescription
user_idstringThe user identifier used in the remote system, this will be provided back to you when making access changes.
emailstringThe 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

Adds a user to the access list of the specified group.

Path params

paramtypedescription
group_idstringThe id of the group you’re trying to add the user to

Body params (JSON encoded)

paramtypedescription
app_idstringThe 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_idstringThe 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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

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

Remove a user from the access list of the specified group.

Path params

paramtypedescription
group_idstringThe id of the group to be edited.
user_idstringThe id of the user to be removed.

Query params

paramtypedescription
app_idstringThe 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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

GET /groups/{group_id}/resources

Returns the resources that currently belong to the provided group.

Path params

paramtypedescription
group_idstringThe id of the group you’re trying to list the details of

Query params

paramtypedescription
app_idstringThe 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
cursorstringFor pagination. Empty string on the first call, then passed values provided via next_cursor

Response params (200)

paramtypedescription
resourcesarray of objectsList of the group resource objects. See below for what each group should include
next_cursorstringThe 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)

paramtypedescription
messagestringAn error message to expose in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

GroupResource object

fieldtypedescription
resource_idstringThe resource identifier used in the remote system, this will be provided back to you when making access changes.
access_levelobjectThe 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

Adds a resource to the access list of the specified group.

Path params

paramtypedescription
group_idstringThe id of the group you’re trying to add the resource to

Body params (JSON encoded)

paramtypedescription
app_idstringThe 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_idstringThe id of the resource to add to the group
access_level_idstring (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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

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

Remove a resource from the access list of the specified group.

Path params

paramtypedescription
group_idstringThe id of the group to be edited.
resource_idstringThe id of the resource to be removed.

Query params

paramtypedescription
app_idstringThe 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_idstring (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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

GET /users

Returns a list of users for your custom connector app.

Query params

paramtypedescription
app_idstringThe 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
cursorstringFor pagination. Empty string on the first call, then set to the value provided via next_cursor

Response params (200)

paramtypedescription
usersarray of objectsList of the user objects. See below for what each user should include
next_cursorstringThe 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)

paramtypedescription
messagestringAn error message that will be exposed in the Opal UI.
codeintegerThe error code that describes the error. See Error Codes for details.

User object

fieldtypedescription
idstringThe id of the user that uniquely identifies it in your system.
emailstringThe 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:

paramtypedescription
messagestringAn error message to expose in the Opal UI.
codeintegerThe 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 codeMeaning
200Response successful.
401Invalid request signature error.
404Entity not found (eg. resource, user, access level).
500Unexpected 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.