NAV
shell python go

Introduction 👋

Our AI agent integrates with many popular customer support platforms out of the box, but if you have an in-house support tool or need more control, you can build your own integration using our RESTful HTTP API.

Our API is accessible at https://api.gradient-labs.ai. There are client libraries available for Python and Go.

Request and response bodies are encoded as JSON, with timestamps encoded as RFC 3339 strings.

We also send webhooks to let you know when our agent wants to reply to the customer, hand-off to a human agent, or bring the conversation to a close.

Authentication 🔒

Requests must be authenticated by providing your API Key in the Authorization header, in the form: Bearer <your api key>.

curl https://api.gradient-labs.ai \
  --header "Authorization: Bearer $api_key"
import os
from gradient_labs import Client

client = Client(os.environ["api_key"])
package main

import (
    "log"
    "os"

    glabs "github.com/gradientlabs-ai/go-client"
)

func main() {
    client, err := glabs.NewClient(
        glabs.WithAPIKey(os.Getenv("api_key")),
    )
    if err != nil {
        log.Fatal(err)
    }
}

Idempotence 🔁

Unless stated otherwise, all endpoints are idempotent and requests can be safely retried.

Conversations 💬

Start a conversation

To begin a conversation, make a POST request to the /conversations endpoint.

curl https://api.gradient-labs.ai/conversations \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "conversation-1234",
    "customer_id": "user-1234",
    "channel": "web",
    "created": "2023-10-23T17:24:50.804952Z"
    "metadata": {
      "chat_entrypoint": "home-page"
    }
  }
BODY
conv = client.start_conversation(
    conversation_id="conversation-1234",
    customer_id="user-1234",
    channel="web",
    metadata={
        "chat_entrypoint": "home-page",
    },
)
conv, err := client.StartConversation(ctx, glabs.StartConversationParams{
    ID:         "conversation-1234",
    CustomerID: "user-1234",
    Channel:    glabs.ChannelWeb,
    Metadata:   map[string]string{"chat_entrypoint": "home-page"},
})
{
  "id": "conversation-1234",
  "customer_id": "user-1234",
  "channel": "web",
  "metadata": {
    "chat_entrypoint": "home-page"
  },
  "created": "2023-10-23T17:24:50.804952Z",
  "updated": "2023-10-23T17:24:50.804952Z",
  "status": "active"
}

Body Parameters

Parameter Type Required Description
id String Unique identifier for this conversation. Can be anything consisting of letters, numbers, or any of the following characters: _ - + =. Tip: use something meaningful to your business (e.g. a ticket number).
customer_id String Unique identifier for the customer. Used to build historical context of conversations the agent has had with this customer.
channel String Identifier for the way the customer is getting in touch: one of web or email.
created Timestamp The time at which the conversation started (defaults to the current time).
metadata Any Arbitrary metadata to attach to this conversation. Will be passed along with webhooks so can be used as action parameters.
assignee_id String Unique identifier for the human agent or bot that is assigned to the conversation. Used to render assignment changes in the web app.
assignee_type String Identifies the type of assignee (must be one of Agent, Bot, or AI Agent).

Add a message

To record a message sent by the customer or a human agent, make a POST request to the /conversations/:conversation_id/messages endpoint.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/messages" \
  --request POST \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "id": "message-1234",
    "body": "Hello! I need some help setting up my toaster oven, please.",
    "participant_id": "user-1234",
    "participant_type": "Customer",
    "created": "2023-10-23T17:24:50.804952Z",
    "metadata": {
      "device_os": "iOS 17"
    }
  }
BODY
from datetime import datetime
from gradient_labs import ParticipantType

msg = client.add_message(
    conversation_id="conversation-1234",
    message_id="message-1234",
    body="Hello! I need some help setting up my toaster oven, please.",
    participant_id="user-1234",
    participant_type=ParticipantType.CUSTOMER,
    metadata={
        "device_os": "iOS 17",
    },
    attachments=[
        Attachment(
            type=AttachmentType.IMAGE,
            file_name="toaster.jpg",
        )
    ],
)
msg, err := client.AddMessage(ctx, "conversation-1234", glabs.AddMessageParams{
    ID:              "message-1234",
    Body:            "Hello! I need some help setting up my toaster oven, please.",
    ParticipantID:   "user-1234",
    ParticipantType: glabs.ParticipantTypeCustomer,
    Metadata:        map[string]string{"device_os": "iOS 17"},
  Attachments: []*glabs.Attachment{
            {
                Type:     glabs.AttachmentTypeImage,
                FileName: "toaster.jpg",
            },
        },
})
{
  "id": "message-1234",
  "body": "Hello! I need some help setting up my toaster oven, please.",
  "participant_id": "user-1234",
  "participant_type": "Customer",
  "created": "2023-10-23T17:24:50.804952Z",
  "metadata": {
    "device_os": "iOS 17"
  }
}

Body Parameters

Parameter Type Required Description
id String Unique identifier for this message. Can be anything consisting of letters, numbers, or any of the following characters: _ - + =. Tip: use something meaningful to your business.
body String The message text. Required if there are no attachments.
attachments List An optional list of attachments that were sent with the message. Currently, our AI agent does not read attachments and will therefore hand-off any conversation where an attachment is uploaded.
participant_id String Identifies the message sender.
participant_type String Identifies the type of sender (must be one of Customer, Agent, or Bot).
created Timestamp The time at which the message was sent (defaults to the current time).
metadata Any Arbitrary metadata to attach to this message.

Assign the conversation

Assignment controls who is currently responsible for responding to the customer, it's how you activate and deactivate the AI agent.

To assign the conversation, make a PUT request to the /conversations/:conversation_id/assignee endpoint.

To assign the conversation to the AI agent, use the assignee type "AI Agent". Note: This will fail if you have not configured a webhook endpoint that we can use to deliver replies to the customer.

If you need to stop the AI agent from responding in a conversation (e.g. because a human agent has taken over), assign the conversation away from the AI agent by using one of the other available assignee types.

By default, conversations will start off without an assignment, unless you pass the assignee_id and assignee_type parameters to the /start endpoint.

Note: a conversation cannot be assigned to a customer.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/assignee" \
  --request PUT \
  --header "Authorization: Bearer $api_key" \
  --header 'Content-Type: application/json' \
  --data @- \
<<BODY
  {
    "assignee_id": "agent-1234",
    "assignee_type": "AI Agent",
    "timestamp": "2023-10-23T17:24:50.804952Z",
  }
BODY
from datetime import datetime
from gradient_labs import ParticipantType

client.assign_conversation(
    conversation_id=conv.id,
    participant_type=ParticipantType.AI_AGENT,
)
err = client.AssignConversation(ctx, conv.ID, &glabs.AssignmentParams{
        AssigneeType: glabs.ParticipantTypeAIAgent,
    })
    if err != nil {
        return err
    }
{
  "assignee_id": "agent-1234",
  "assignee_type": "AI Agent",
}

Body Parameters

Parameter Type Required Description
assignee_id String Unique identifier for the human agent or bot that is assigned to the conversation. Used to render assignment changes in the web app.
assignee_type String Identifies the type of assignee (must be one of Agent, Bot, or AI Agent).
timestamp Timestamp The time at which the conversation was assigned (defaults to the current time).

Read a conversation

To retrieve the latest status of a conversation, make a GET request to the /conversations/:conversation_id endpoint.

curl "https://api.gradient-labs.ai/conversations/$conversation_id" \
  --request GET \
  --header "Authorization: Bearer $api_key"
client.read_conversation(conversation_id="conversation-1234")
err := client.ReadConversation(ctx, "conversation-1234")

End a conversation

When the conversation is finished (e.g., because the customer issue has been resolved, or the chat is being closed after the customer has abandoned it), make a PUT request to the /conversations/:conversation_id/end endpoint.

curl "https://api.gradient-labs.ai/conversations/$conversation_id/end" \
  --request PUT \
  --header "Authorization: Bearer $api_key"
  --data @- \
<<BODY
  {
    "timestamp": "2023-10-23T17:24:50.804952Z",
  }
BODY
client.end_conversation(conversation_id="conversation-1234")
err := client.EndConversation(ctx, "conversation-1234")

Body Parameters

| Parameter | Type | Required | Description | | timestamp | Timestamp | | The time at which the conversation was ended (defaults to the current time). |

Webhooks 🪝

We deliver webhooks by making an HTTP POST request to your configured endpoint.

Request format

PUT /your-endpoint HTTP/1.1
Host: your-api.com
Content-Type: application/json
User-Agent: GradientLabs/1.0
X-GradientLabs-Signature: t=1698953919,v1=9f16975886154e02fb0bb34694baf87b83d385d7941310c55d2021e3f5cdf780

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "agent.message",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      },
      "body": "Sure, I can help you set up your toaster oven!"
    }
  }
}

The request body is a JSON object with the following structure.

Field Type Description
id String Unique identifier for this event.
type String Type of event (documented below).
sequence_number Integer Sequential index of this event within the conversation (see below).
timestamp Timestamp Time at which this event was generated.
data Object Event data.

The data object for all conversation-related webhooks contains a conversation object with the following fields.

Field Type Description
id String Your chosen unique identifier for this conversation.
customer_id String Your chosen identifier for the customer.
metadata Any Metadata you attached to the conversation.

Sequence numbers

Each of the webhooks about a particular conversation includes an incrementing number called the sequence_number, which can be used to establish a total order of events.

You can use this number to detect when events are missed or delivered in the wrong order (e.g. to prevent sending a message with sequence_number = 5 before an earlier message with sequence_number = 4).

Signature verification

import os
from flask import Flask, request
from gradient_labs import Webhook, SignatureVerificationError

app = Flask(__name__)
signing_key = os.environ["webhook_signing_key"]


@app.route("/webhook", methods=["POST"])
def handle_webhook():
    payload = request.data.decode("utf-8")
    signature_header = request.headers.get("X-GradientLabs-Signature", None)

    try:
        event = Webhook.parse_event(
            payload=payload,
            signature_header=signature_header,
            signing_key=signing_key,
        )
    except SignatureVerificationError:
        print("Invalid signature!")
        return "Bad signature", 401

    print(
        "Received event: id={id}, type={type}".format(
            id=event.event_id, type=event.event_type
        )
    )
    return "", 200


if __name__ == "__main__":
    app.run(port=int(os.environ.get("PORT", 5000)))
package main

import (
    "errors"
    "log"
    "net/http"
    "os"

    glabs "github.com/gradientlabs-ai/go-client"
)

func main() {
    client, err := glabs.NewClient(
        glabs.WithAPIKey(os.Getenv("api_key")),
        glabs.WithWebhookSigningKey(os.Getenv("webhook_signing_key")),
    )
    if err != nil {
        log.Fatal(err)
    }

    http.ListenAndServe(":4321", webhookHandler(client))
}

func webhookHandler(client *glabs.Client) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        webhook, err := client.ParseWebhook(r)
        switch {
        case errors.Is(err, glabs.ErrInvalidWebhookSignature):
            w.WriteHeader(http.StatusUnauthorized)
            return
        case errors.Is(err, glabs.ErrUnknownWebhookType):
            log.Printf("unknown webhook type: %q", webhook.Type)
            return
        case err != nil:
            w.WriteHeader(http.StatusInternalServerError)
            log.Printf("failed to parse webhook: %v", err)
            return
        }

        // Handle the webhook...
    })
}

We sign webhooks using HMAC-SHA256, so you can tell they're really from us.

This signature is in the X-GradientLabs-Signature request header. Our client libraries can check it for you, but to do it yourself:

  1. Split on the , character
  2. Split each item by the = character to get a list of key/value pairs
  3. Prevent replay attacks:
    1. Take the item with the key t and parse its value as a UNIX timestamp (the number of seconds since January 1st 1970)
    2. Check the timestamp is less than 5 minutes old
  4. Concatenate the UNIX timestamp, the . character, and the raw request body
  5. Create an HMAC-SHA256 hash of that string, using your webhook signing key
  6. Hex-encode the hash and compare it to each item with the key v1, if one of them is a match, the request is legitimate! 🎉

Failure, timeouts, and retries

If your endpoint does not respond with a 2XX status code within 10 seconds, we'll consider webhook delivery failed, and begin retrying with exponential back-off and jitter.

Critical webhooks (agent.message, conversation.hand_off and conversation.finished) will be retried 36 times over a 24 hour period. If delivery has still not succeeded, the agent will mark the conversation as failed and stop processing it.

Action webhooks (action.execute) will be retried for up to 1 minute, and if they still have not succeeded, the conversation will be handed off to a human agent.

agent.message

We send this event when the agent wants to send a message to the customer.

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "agent.message",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    },
    "body": "Sure, I can help you set up your toaster oven!"
  }
}

Payload Fields

Field Type Description
body String Text of the message the agent wants to send to the customer.

conversation.hand_off

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "conversation.hand_off",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    }
  }
}

We send this event when the agent is escalating or handing the conversation off to a human agent.

conversation.finished

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "conversation.finished",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    }
  }
}

We send this event when the agent has concluded the conversation with the customer and you should close any corresponding ticket, etc.

action.execute

We send this event when the agent wants to execute one of your configured webhook-backed actions.

For data-retrieval actions, you should respond with a JSON object containing the data. Note: don't have to tell us the response schema up-front, the agent is able to extract data from any well-formed JSON object using AI. 🪄

{
  "id": "webhook_01he8rwyp4fn3v870d925kz2nf",
  "type": "action.execute",
  "sequence_number": 5,
  "timestamp": "2023-10-23T17:24:50.804952Z",
  "data": {
    "conversation": {
      "id": "conversation-1234",
      "customer_id": "user-1234",
      "metadata": {
        "chat_entrypoint": "home-page"
      }
    },
    "action": "random-dog-fact",
    "params": {
      "breed": "Golden Retriever"
    }
  }
}

Payload Fields

Field Type Description
action String Your given identifier for this action.
params Object Parameters the agent has generated for this action.