View in product

Some customer issues, like checking recent orders, require Operator to access your product’s data and functionalities. You can grant this access by linking your APIs to Operator.

Authentication

All requests to your APIs are HMAC-signed to ensure request authenticity and prevent replay attacks. You can view your Operator’s signing key on the API page.

To protect against unauthorized access, verify the request signatures using the following logic:

# Variables:
#
# - headers:      The HTTP request headers
# - url:          The HTTP request URL (excluding query parameters)
# - signing_key:  Your Operator's signing key
# - query_params: The HTTP request query parameters 
# - body:         The HTTP request body

# Is the request too old? You should reject all requests that arrive more
# than 30s after to the X-Timestamp header. 
x_timestamp = headers["X-Timestamp"]
now = int(time.time())
if now - x_timestamp > 30
  return False

# Depending on whether the request is a GET or POST, we determine what the 
# `content` of the request is.
if method == "GET":
  content = json.dumps(query_params, sort_keys=True) # note sorted order!
elif method == "POST":
  content = body

# Concatenate URL, relevant headers and content to create payload.
x_idempotency_key = headers["X-Idempotency-Key"]
payload = f"{url}:{x_idempotency_key}:{x_timestamp}:{content}"

# Compute the HMAC signature using your signing key.
signature = hmac.new(signing_key.encode("utf-8", payload.encode("utf-8"), hashlib.sha256).hexdigest()

# Check if the signature matches what you saw in the request.
x_signature = headers["X-Signature"]
return hmac.compare_digest(signature, x_signature)

We’re happy to help you write and test your signature verification logic in any programming language or framework your company uses. We have sample implementations available for common frameworks:

If you need a different authentication mechanism—such as API keys, asymmetric signatures, or mTLS—let us know! We’ll work with your team to meet your internal security requirements.

Idempotency

If a request to your API fails, it will be retried multiple times using exponential backoff and jitter. To prevent duplicate operations, each request includes a unique X-Idempotency-Key header.

To implement idempotency, cache both the idempotency key and the request result. When you receive a request with a previously seen idempotency key, return the cached response instead of executing the operation again.

Requests may be retried within a 5-minute window, so ensure your idempotency cache has a TTL of more than 5 minutes. We guarantee that Operators won’t reuse an idempotency key for at least 24 hours.

Note: Idempotency guarantees are only necessary for endpoints that modify your internal systems—read-only endpoints don’t require it.

Describing APIs

We currently only support integrations with HTTP APIs. Please reach out if your API follows a different protocol.

Each API description consists of:

  • Name: Your Operator will refer to this API by its name. Choose a name that clearly represents the API’s action and output to ensure your Operator calls it at the appropriate time.
  • Description: Your Operator uses this to determine if the API can help solve the current customer problem.
  • URL: The URL of the API.
  • Method: The HTTP method of the API—only GET and POST are supported.
  • Inputs: The specification of the data that the API accepts.
  • Outputs: The specification of the data that the API returns.

Currently, inputs and outputs only support two formats: URL Search Parameters (for GET request inputs) and JSON serialization (for POST request inputs and outputs). Please reach out if you need support for a different format!

Inputs and outputs values are described by the following specification:

{
  // The name/key of the field.
  "name": "field_name",
  // A description of what the field is; your Operator will use this field to
  // provide extra context to the customer in conversations.
  "description": "Unique ID for the transactions.",
  // The type of the field. Common computing types are supported, as well as
  // higher-level domain-specific types. Your Operator will use this type
  // information to determine how to collect and validate the field from
  // the customer. Using the domain-specific types improves Operator
  // performance. The supported types are:
  //
  // General:
  // - string
  // - boolean
  // - integer
  // - number
  // - date
  // - datetime
  // - object
  // - identifier
  // - uuid
  //
  // Location:
  // - us_state
  //
  // People:
  // - name
  // - phone
  // - email
  //
  // Financial:
  // - card_number
  // - last4
  // - money
  "type": "identifier",
  // If this field takes in an array of values as opposed to a single value,
  // then set `repeated` to true. This will set the type of the field to an
  // array of `type`.
  "repeated": false,
  // If the `type` is `string`, then the valid types can be restricted to an
  // enumerated set. Your Operator will then only submit one of the specified
  // values. Specify the valid values as an array of strings.
  "enum": null,
  // If the `type` is `object`, then the specification of the object's fields
  // is described here.
  "children": [
    { ... }, // Field schema, recursive.
    { ... }  // Field schema, recursive.
  ],
  // Whether the field must be collected and included in the API call or
  // whether it can be omitted.
  "required": true,
}

An example API input/output spec is:

{
  "input": [
    {
      "name": "last_4_card_no",
      "type": "last4",
      "description": "The last 4 digits of the card number used for the transaction.",
      "required": true,
      "enum": null,
      "repeated": false,
      "children": []
    }
  ],
  "output": [
    {
      "name": "status",
      "type": "string",
      "description": "Status of the transaction retrieval request.",
      "required": true,
      "enum": [
        "success",
        "error"
      ],
      "repeated": false,
      "children": []
    },
    {
      "name": "transactions",
      "type": "object",
      "description": "List of recent transactions.",
      "required": false,
      "enum": null,
      "repeated": true,
      "children": [
        {
          "name": "timestamp",
          "type": "datetime",
          "description": "When the transaction occurred.",
          "required": true,
          "enum": null,
          "repeated": false,
          "children": []
        },
        {
          "name": "amount",
          "type": "money",
          "description": "The amount transferred in the transaction.",
          "required": true,
          "enum": null,
          "repeated": false,
          "children": []
        }
      ]
    },
    {
      "name": "reason",
      "type": "string",
      "description": "Reason for the error, if any.",
      "required": false,
      "enum": null,
      "repeated": false,
      "children": []
    }
  ]
}