Skip to content
Pengo Help Center
Browse all guides

REST API

Read and update Pengo conversations, send messages and list visitors from your own code with the REST API and an API key.

Developers Business plan and up 5 min read Updated
On this page

The Pengo REST API lets your own systems work with your inbox: list and read conversations, change their status, assignee, priority or tags, read and send messages, and list visitors. Use it to sync conversations into a CRM or data warehouse, build reports, or reply from another tool.

#Create an API key

  1. Go to Settings Workspace API keys and click Generate key.

  2. Enter a Key name that tells you where the key is used, such as “Production server”, and click Create key.

  3. Copy the key straight away.

    Pengo shows the full key only once. Click the copy icon and store it somewhere safe, such as your server’s environment variables or a secrets manager. Then click Dismiss.

API keys start with pgk_live_. In the list, Pengo shows each key’s name, the start of the key, when it was created and when it was last used.

Treat keys like passwords. A key has full read and write access to the websites of the person who created it, so never put one in page code, a mobile app or a public repository. To revoke a key, click its delete icon and confirm. Anything using it stops working immediately. You can have up to 10 active keys.

#Authentication

Send your key as a Bearer token in the Authorization header of every request:

Terminal
curl https://app.pengo.chat/api/v1/conversations \
  -H "Authorization: Bearer pgk_live_xxxxxxxxxxxxxxxx"

The base URL for every endpoint is:

Text
https://app.pengo.chat/api/v1

#Choose a website

Every request works on one website. If you have more than one, add property_id to the query string of every request, including POST and PATCH requests. It’s your website ID, the propertyId value in your install snippet. Without it, Pengo picks one of the websites the key can access, so always pass it when you run several sites.

Terminal
curl "https://app.pengo.chat/api/v1/conversations?property_id=YOUR_PROPERTY_ID&status=open" \
  -H "Authorization: Bearer $PENGO_API_KEY"

#Endpoints

MethodPathWhat it does
GET/conversationsLists conversations, newest first.
GET/conversations/{id}Gets one conversation.
PATCH/conversations/{id}Updates a conversation’s status, assignee, priority or tags.
GET/conversations/{id}/messagesLists a conversation’s messages, oldest first.
POST/conversations/{id}/messagesSends a message in a conversation.
GET/visitorsLists visitors, most recently seen first.

The full, interactive reference with every field is at app.pengo.chat/api-docs.

#List conversations

GET /conversations

Query parameterDescription
statusOnly conversations with this status: open, pending or resolved.
assigned_toOnly conversations assigned to this teammate’s user ID.
limitHow many to return. Default 50, maximum 100.
offsetHow many to skip, for paging. Default 0.

Each conversation has id, visitor_id, visitor_name, visitor_email, status, priority, assigned_to, created_at, updated_at, last_message, last_message_at and tags.

JSON
{
  "data": [
    {
      "id": "3f6d1c2e-9a8b-4c7d-a6e5-0f1e2d3c4b5a",
      "visitor_name": "Ada",
      "visitor_email": "ada@example.com",
      "status": "open",
      "priority": "normal",
      "last_message": "Do you ship to Canada?",
      "tags": ["shipping"]
    }
  ],
  "pagination": { "total": 128, "limit": 50, "offset": 0, "has_more": true }
}

#Get a conversation

GET /conversations/{id} returns the full conversation as data. You get a 404 if it doesn’t exist or belongs to another website.

#Update a conversation

PATCH /conversations/{id} with a JSON body. Send only the fields you want to change:

FieldDescription
statusopen, pending or resolved.
assigned_toThe user ID of the teammate to assign.
priorityurgent, high, normal or low.
tagsThe full list of tags. It replaces the current tags.
Terminal
curl -X PATCH "https://app.pengo.chat/api/v1/conversations/CONVERSATION_ID?property_id=YOUR_PROPERTY_ID" \
  -H "Authorization: Bearer $PENGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "resolved", "tags": ["billing", "refund"]}'

The response is the updated conversation as data.

#List messages

GET /conversations/{id}/messages returns the conversation’s messages, oldest first, with the same limit, offset and pagination as conversations.

#Send a message

POST /conversations/{id}/messages with a JSON body:

FieldRequiredDescription
contentYesThe message text, 1 to 10,000 characters.
sender_idYesThe user ID (a UUID) of the teammate sending it, for example a conversation’s assigned_to.
sender_typeNoagent (the default) or bot.
client_message_idNoYour own ID for this message, up to 64 characters of letters, numbers and . _ : -. Makes retries safe.
Terminal
curl -X POST "https://app.pengo.chat/api/v1/conversations/CONVERSATION_ID/messages?property_id=YOUR_PROPERTY_ID" \
  -H "Authorization: Bearer $PENGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "Thanks, your refund is on its way.", "sender_id": "AGENT_USER_ID", "client_message_id": "refund-4821"}'

A new message returns 201 with the message as data. If you retry with the same client_message_id, Pengo doesn’t create a second message. It returns 200 with the original message and "deduplicated": true. Sending a message triggers the message.sent webhook.

#List visitors

GET /visitors

Query parameterDescription
is_onlinetrue or false.
has_active_chattrue or false.
limitDefault 50, maximum 100.
offsetDefault 0.

Each visitor has id, email, name, phone, is_online, has_active_chat, last_seen_at, created_at, country_code and tags.

#Paging

List endpoints return a pagination object with total, limit, offset and has_more. To get every record, keep adding limit to offset until has_more is false:

JavaScript
const base = 'https://app.pengo.chat/api/v1/conversations';
const headers = { Authorization: `Bearer ${process.env.PENGO_API_KEY}` };

let offset = 0;
const all = [];
while (true) {
  const res = await fetch(`${base}?property_id=${process.env.PENGO_PROPERTY_ID}&limit=100&offset=${offset}`, { headers });
  if (!res.ok) throw new Error(`Pengo API error ${res.status}`);
  const page = await res.json();
  all.push(...page.data);
  if (!page.pagination.has_more) break;
  offset += page.pagination.limit;
}

#Rate limits

Each endpoint allows 200 requests per minute from the same IP address. Over the limit, Pengo answers 429 Too Many Requests with these headers:

HeaderMeaning
Retry-AfterSeconds to wait before trying again.
X-RateLimit-LimitThe number of requests allowed per minute.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetWhen the window resets, in milliseconds since 1970.

Wait for Retry-After seconds, then retry.

#Errors

Errors use standard HTTP status codes and a JSON body.

StatusWhen
400The request body is invalid, for example a missing content or sender_id.
401The key is missing, wrong or revoked, or property_id isn’t a website the key can access.
404The conversation doesn’t exist, or belongs to another website.
429You hit the rate limit.
500Something went wrong on Pengo’s side. Retry later.

Authentication errors look like this:

JSON
{ "error": "Unauthorized", "message": "Invalid API key" }

Other errors include a code and a requestId. The request ID is also in the X-Request-ID header. Include it if you contact support@pengo.chat about a failed request.

JSON
{
  "error": true,
  "message": "Conversation not found",
  "code": "NOT_FOUND",
  "requestId": "mgx3k2a1-4f9c2e"
}

#Get notified instead of polling

To react to new conversations and messages as they happen, use webhooks instead of calling the API on a timer. Many integrations use both: webhooks to hear about changes, and the API to read details or reply.

Common questions

I lost my API key. Can I see it again?

No. Pengo only shows a key once, when you create it. Revoke the lost key and create a new one.

How many API keys can I have?

Up to 10 active keys. Revoke keys you no longer use to make room for new ones.

Can I use the API from the browser?

Don't. An API key has full read and write access, so keep it on your server and call the API from there.

Was this guide helpful?

Your answer helps us improve it.