Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this
project adheres to [Semantic Versioning](https://semver.org/).

## [0.5.0rc3] - 2026-10-21

Release candidate for 0.5.0.

### Upgrade from 0.5.0rc2

`client_now_date` must now have a UTC offset, such as
`2026-10-21T09:00:00-04:00`. A value without one raises `ValueError`. Pine
ignored such values before.

### New

- **Update a managed user.** `platform.managed_users.update()` changes the
email, name, phone or assistant. `phone=""` removes the phone.
- **Choose the assistant.** The assistant places and answers phone calls for
the user. Before, every managed user had Pine's built-in assistant. Now you
can choose its identity (voice and name) from
`settings.assistant_identities()` and rename it, with `assistant=` on
`create()` or `update()`.
- **Choose the caller ID.** `sessions.update_config(caller_id="platform")`
makes calls show a platform number instead of the user's phone.
- **Time zone on Socket.IO.** `chat()` and `send_message()` take
`client_now_date`, as REST `send_message()` does.

### Fixed

- Socket.IO messages sent a time without a UTC offset, so the agent always
used UTC.

### Smaller changes

- `ManagedUser.assistant` is `{identity_id, name}`, or `None` for Pine's
default assistant.
- `assistant={"identity_id": ...}` alone also replaces a custom name.
- `sessions.get_config()` reads the caller ID.
- An invalid `assistant`, `caller_id` or `client_now_date` raises `ValueError`
before any request.
- `PineAI` has the same new REST methods.

## [0.5.0rc2] - 2026-10-19

Internal release candidate for 0.5.0.
Expand Down
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,18 @@ All managed users of one partner use that partner's credits. A partner can
limit the total credits that its managed users use.

The SDK methods for managed users are listed in
[docs/sdk-api.md](docs/sdk-api.md). If you do not use Python, see the HTTP API
in [docs/http-api.md](docs/http-api.md) and the Socket.IO reference in
[docs/socketio.md](docs/socketio.md).
[docs/sdk-api.md](https://github.com/19PINE-AI/pine-assistant-python/blob/main/docs/sdk-api.md).
If you do not use Python, see the HTTP API in
[docs/http-api.md](https://github.com/19PINE-AI/pine-assistant-python/blob/main/docs/http-api.md)
and the Socket.IO reference in
[docs/socketio.md](https://github.com/19PINE-AI/pine-assistant-python/blob/main/docs/socketio.md).

Create the managed user once, and store its `id` in your own user table. The
call is idempotent per `external_id`: a repeat returns the stored user
unchanged, even if the profile you send differs. `phone` is optional, in E.164
form, and must be a number you have verified. `get()` finds a user by
`external_id` if you did not store the `id`.
`external_id` if you did not store the `id`. `update()` changes only the
fields you give; `phone=""` removes the phone.

```python
import os
Expand All @@ -145,6 +148,7 @@ async with AsyncPineAI(api_key=api_key) as partner_client:
phone="+14155550100",
)
same_user = await partner_client.platform.managed_users.get("usr_01J9Z3K8QF")
moved_user = await partner_client.platform.managed_users.update("usr_01J9Z3K8QF", phone="+14155550123")
# Save user.id with your own user record.
```

Expand Down Expand Up @@ -174,7 +178,8 @@ A key client keeps its key; use a separate client for a user token.

Every error below is a `PineAIError` with `code` and `status_code`, so catch
`PineAIError`. Some methods raise a subclass: `SessionError` from
`sessions.list`, `get`, `send_message`, `history` and `outcomes`, and
`sessions.list`, `get`, `get_config`, `update_config`, `send_message`,
`history` and `outcomes`, and
`AuthError` from `auth.*`. Other `sessions` methods, such as `create` and
`start_task`, raise `PineAIError` itself.

Expand Down Expand Up @@ -418,8 +423,11 @@ Two conditions have no remedy once a session is running:

- **Metered billing.** The account must be billed against a credit balance. On
the alternative path a session halts at a payment step the SDK cannot answer.
- **Phone verification.** Must be completed at provisioning time. It has no
in-session remedy and no in-session signal.
- **Phone verification.** A Pine user account must verify its phone at
provisioning time. It has no in-session remedy and no in-session signal. A
managed user needs no verification: its phone is the one the partner gives.
Without a phone, the agent still places calls, from a number of the
platform's pool.

## Attachments

Expand Down
101 changes: 96 additions & 5 deletions docs/http-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ Use `error.code` in your code. The `message` text can change.
| `email` | yes | email address |
| `name` | yes | 1-100 characters |
| `phone` | no | E.164, for example `+14155550100`; a number you have verified |
| `assistant` | no | `{"identity_id": "...", "name": "..."}`; see "Assistant identity" |

No value may be `null`: omit a field instead. Unknown fields return 400.

> **Caution:** use only the end user's own email address and phone number, and
> verify them first. Never use an email address or phone number that does not
Expand All @@ -51,8 +54,9 @@ repeated call returns 200 with the stored user unchanged, even if the fields
you send are different.

The response `data` is the managed user:
`{"id", "external_id", "email", "name", "phone", "created_at"}`. `phone` is
`null` when you did not give one.
`{"id", "external_id", "email", "name", "phone", "assistant", "created_at"}`.
`phone` is `null` when the user has no phone. `assistant` is
`{"identity_id", "name"}`, or `null` when the user uses Pine's default assistant.

Errors: 400 `invalid_request`, 401 `unauthorized`, 403
`platform_access_suspended`, 409 `platform_managed_user_limit_reached`.
Expand All @@ -65,6 +69,57 @@ Percent-encode `external_id` in the path (for example `|` is `%7C`).
It returns the same managed user as create. A user that does not exist, or that
belongs to another partner, returns 404 `platform_managed_user_not_found`.

### Update a managed user

`PATCH /platform/v1/managed-users/{external_id}` with `Authorization: Bearer <key>`.

Send only the fields that you change. A field that you omit keeps its stored
value. `{}` changes nothing.

| Field | Rule |
|---|---|
| `email` | email address |
| `name` | 1-100 characters |
| `phone` | E.164, or `""` to remove the phone |
| `assistant` | `{"identity_id": "...", "name": "..."}`; either member can be omitted; see "Assistant identity" |

The rules for `null` and unknown fields are the same as for create.
`external_id` cannot change. The agent uses the new profile from the user's
next message. A call in progress does not change. The response is the same as
for get.

Errors: 400 `invalid_request` (nothing is changed), 401 `unauthorized`, 403
`platform_access_suspended`, 404 `platform_managed_user_not_found`.

### Assistant identity

The assistant is the voice of Pine on the phone. It places calls for the
user, for example to a business, and it answers calls for the user. Each
user has one assistant. By default, it is Pine's built-in assistant.

You can choose another identity from the catalog. The identity sets the
assistant's voice and the name that it gives on calls. You can also give it
your own name.

The catalog:

`GET /v2/app/settings/assistance_identities?identities=catalog`

You can send it without any header. The response `data` is
`{"version", "identities": [{"id", "first_name", "last_name", "locale", "description", "greeting_audio_url", "status"}]}`.
Use only an identity whose `status` is `"active"`. To hear an identity, play
its `greeting_audio_url`.

| `assistant` | Result |
|---|---|
| `{"identity_id": "agt_..."}` | The assistant gets the identity and the identity's own name. On update, this also replaces a custom name: to keep it, send `name` too. |
| `{"identity_id": "agt_...", "name": "Zoe Park"}` | The assistant gets the identity and this name. |
| `{"name": "Zoe Park"}` | Update only: the current identity gets a new name. A user with no identity gets 400. |

The `name` has at most 100 characters. It can contain only letters (CJK
included) and spaces between words. One word is enough. A user created
without `assistant` uses Pine's default assistant.

### Act for a managed user

Send both headers on a `/v2` request:
Expand Down Expand Up @@ -97,7 +152,9 @@ These routes take the two headers above.
| `POST /v2/sessions` | Creates a session. | `sessions.create()` |
| `GET /v2/sessions` | Lists the user's sessions. | `sessions.list()` |
| `GET /v2/sessions/{id}` | Returns one session. | `sessions.get()` |
| `POST /v2/sessions/{id}/messages` | Sends a message. Body: `{"content": "..."}`. | `sessions.send_message()` |
| `GET /v2/sessions/{id}/config` | Returns the session config, for example `{"caller_id": "platform"}`, or `{}`. | `sessions.get_config()` |
| `PUT /v2/sessions/{id}/config` | Sets the caller ID. Body: `{"caller_id": "user"}` or `{"caller_id": "platform"}`. See "Caller ID". | `sessions.update_config()` |
| `POST /v2/sessions/{id}/messages` | Sends a message. Body: `{"content": "...", "client_now_date": "..."}`; `client_now_date` is optional, see "The user's time". | `sessions.send_message()` |
| `GET /v2/sessions/{id}/messages` | Reads the stored messages, newest first. | `sessions.history()` |
| `POST /v2/sessions/{id}/start` | Starts a prepared or paused task. | `sessions.start_task()` |
| `POST /v2/sessions/{id}/stop` | Stops a running task. | `sessions.stop_task()` |
Expand All @@ -116,12 +173,46 @@ Routes that make the agent work can also return these errors:
`type`, `version`, `data` (a JSON string) and `created_at`. To read older
messages, send the returned `next_message_id` as `before_message_id`. An empty
`next_message_id` means there are no older messages.
A new reply can take a few seconds to appear in this list. If the `state`
changed but the reply is missing, read again.
A reply appears in this list only after the agent finishes it. This can take
a minute or more. Socket.IO shows the reply at once. If the `state` changed
but the reply is missing, read again later.

Other `/v2` routes accept the key and header in the same way. This page does
not describe them.

### Caller ID

When the agent calls a third party, such as a business, the call shows a
caller ID. `caller_id` is a session setting that selects it:

| `caller_id` | The call shows |
|---|---|
| `"user"`, or not set | The user's assistant number if it has one, else the user's phone, else a number of the platform's pool. |
| `"platform"` | A number of the platform's pool. |

- Set or change it at any time. The agent uses the value for its next call. A
call already placed, including its retries, keeps its caller ID. A change
while the session is paused applies when the session resumes.
- `caller_id` does not change how the agent reaches the user. The agent still
uses the user's phone for that.
- Any other key or value returns 400 `invalid_request`.
- 503 `session_config_push_failed` means that the value is saved, but the
running agent did not get it yet. Send the same request again.

### The user's time

The agent must know the user's time zone to understand words such as
"tomorrow at 9 am". Send the user's current local time, with its UTC offset,
as `client_now_date` with each message. The Pine app does the same.

- The value is an RFC 3339 time with an offset, for example
`2026-10-21T09:00:00-04:00`. Pine uses only the offset.
- A value without an offset is ignored.
- If a session never got `client_now_date`, the agent uses UTC. After one
message with it, the agent keeps that offset until a message brings a new
one.
- On Socket.IO, put the same field in the `session:message` data.

### How a task runs

1. Create a session and send the user's request as a message.
Expand Down
Loading
Loading