> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Retrieve a session transcript

GET https://api.jelliu.co/widget/history

Returns the last 50 messages of the session's conversation, visitor and agent alike, oldest first.
Call it after `POST /widget/init` returns `resumed: true` so the panel shows the earlier conversation
instead of greeting again. Older messages are not reachable from the widget API. For live updates use
`GET /widget/poll`.

Credentials, origin check and limits are the same as `POST /widget/init`. The session must belong to
the widget named by the credential.

**Idempotency.** Read-only; safe to retry.

**Access**
- **Required scope:** None; `jl_` API keys are not used. The widget credential plus a matching `x-widget-parent-origin` authorise the call.
- **Rate limit:** Widget public — 60 requests/min per IP, plus the widget's `rate_limit_rpm` per credential and 300 requests/min per workspace; the general limiter also counts 120 requests/min per IP. See [Rate limits](/rate-limits).
- **Plan:** Available on every plan.


Reference: https://developer.jelliu.co/api-reference/widgets/get-widget-history

## Request

### Query parameters

- `wid` (string, optional) — Public widget id as a query parameter. Used when neither header is present.
- `session_id` (string, required) — Session id from `POST /widget/init`. Not a UUID returns 400 `Invalid query`.

### Headers

- `x-widget-api-key` (string, optional) — Secret widget API key (`plaintext_api_key` from `POST /api/widgets`). Takes precedence over `x-widget-id` and `wid`. Send one of the three. Never expose it in a browser.
- `x-widget-id` (string, optional) — Public widget id, the credential the embed script uses. Used when `x-widget-api-key` is absent.
- `x-widget-parent-origin` (string, required) — Origin of the page embedding the widget. Must match one of the widget's `allowed_origins`; see `POST /widget/init`.

## Response

### 200

Transcript. Empty when the visitor has not sent a message yet.

- `data` (object, required)
  - `messages` (list of object, required) — Up to the 50 most recent messages, oldest first.
    - `id` (string, optional) — Message id. Use it to de-duplicate across message, poll and WebSocket deliveries.
    - `role` (enum, optional) — Who wrote it. `user` is the visitor; `agent` is the AI agent or a human operator replying from the dashboard (they are not distinguished). `GET /widget/poll` only returns `agent`.
      - Allowed values: `user`, `agent`
    - `content` (string, optional) — Message text.
    - `created_at` (datetime, optional) — When the message was stored.

## Errors

### 400 Bad Request Error

`session_id` missing or not a UUID.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 401 Unauthorized Error

`Missing widget credential` or `Invalid widget API key` (see `POST /widget/init`).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 403 Forbidden Error

Widget deactivated, workspace deleted, or origin missing / invalid / not allowed (see `POST /widget/init`).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 404 Not Found Error

The session does not exist or belongs to another widget.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 429 Too Many Requests Error

A per-minute limit was hit (see `POST /widget/init` for the messages).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 503 Service Unavailable Error

The per-widget limiter's store is unreachable; it fails closed.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

## Examples

**Response**

```json
{
  "data": {
    "messages": [
      {
        "id": "dd16791f-33db-4eeb-bea8-c3f78f04cfe6",
        "role": "user",
        "content": "Hola, ¿tienen citas disponibles el sábado en la mañana?",
        "created_at": "2026-09-15T13:18:51.204Z"
      },
      {
        "id": "6eaab3b2-7382-4ce3-8429-9c7aaabb15ae",
        "role": "agent",
        "content": "¡Hola María! Sí, tenemos citas el sábado entre 8:00 y 12:00. ¿Qué hora te queda mejor?",
        "created_at": "2026-09-15T13:18:54.880Z"
      },
      {
        "id": "33905cc7-5022-4ba0-b376-231c0bdd7f83",
        "role": "agent",
        "content": "Hola María, soy Carolina del equipo de recepción. Ya te reservé el sábado a las 9:00.",
        "created_at": "2026-09-15T13:20:06.118Z"
      }
    ]
  }
}
```

**SDK Code**

```python Widgets_getWidgetHistory_example
import requests

url = "https://api.jelliu.co/widget/history"

querystring = {"session_id":"b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d","wid":"84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e"}

headers = {
    "x-widget-api-key": "3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284",
    "x-widget-id": "84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e",
    "x-widget-parent-origin": "https://www.clinicasonrisa.co"
}

response = requests.get(url, headers=headers, params=querystring)

print(response.json())
```

```javascript Widgets_getWidgetHistory_example
const url = 'https://api.jelliu.co/widget/history?session_id=b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d&wid=84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e';
const options = {
  method: 'GET',
  headers: {
    'x-widget-api-key': '3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284',
    'x-widget-id': '84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e',
    'x-widget-parent-origin': 'https://www.clinicasonrisa.co'
  }
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Widgets_getWidgetHistory_example
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/widget/history?session_id=b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d&wid=84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("x-widget-api-key", "3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284")
	req.Header.Add("x-widget-id", "84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e")
	req.Header.Add("x-widget-parent-origin", "https://www.clinicasonrisa.co")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Widgets_getWidgetHistory_example
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/widget/history?session_id=b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d&wid=84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["x-widget-api-key"] = '3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284'
request["x-widget-id"] = '84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e'
request["x-widget-parent-origin"] = 'https://www.clinicasonrisa.co'

response = http.request(request)
puts response.read_body
```

```java Widgets_getWidgetHistory_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/widget/history?session_id=b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d&wid=84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e")
  .header("x-widget-api-key", "3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284")
  .header("x-widget-id", "84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e")
  .header("x-widget-parent-origin", "https://www.clinicasonrisa.co")
  .asString();
```

```php Widgets_getWidgetHistory_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/widget/history?session_id=b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d&wid=84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e', [
  'headers' => [
    'x-widget-api-key' => '3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284',
    'x-widget-id' => '84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e',
    'x-widget-parent-origin' => 'https://www.clinicasonrisa.co',
  ],
]);

echo $response->getBody();
```

```csharp Widgets_getWidgetHistory_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/widget/history?session_id=b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d&wid=84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e");
var request = new RestRequest(Method.GET);
request.AddHeader("x-widget-api-key", "3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284");
request.AddHeader("x-widget-id", "84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e");
request.AddHeader("x-widget-parent-origin", "https://www.clinicasonrisa.co");
IRestResponse response = client.Execute(request);
```

```swift Widgets_getWidgetHistory_example
import Foundation

let headers = [
  "x-widget-api-key": "3462145765a6b6c238f79a1e13e10361c24d1740ec30d27bea22dd4106c04284",
  "x-widget-id": "84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e",
  "x-widget-parent-origin": "https://www.clinicasonrisa.co"
]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/widget/history?session_id=b3e227dd-a9ea-48ce-9afe-09c21a5d8d2d&wid=84b5aa0e-f6d6-4d88-b3ad-cd7a5a2aeb9e")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```