-
Notifications
You must be signed in to change notification settings - Fork 11
feat(care): document the embedded care chat endpoints #971
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b499585
43dadba
3d33538
d54a4e7
9ab210e
bfc2e69
1c1ba1e
8b70dbc
daeeb66
001db51
eae2f6c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| type: object | ||
| description: >- | ||
| One turn of the cardholder's conversation, or, with `message` empty or | ||
| absent, the call that opens or resumes the chat without saying anything. | ||
| Unknown fields are rejected with `INVALID_INPUT`. | ||
|
kphurley7 marked this conversation as resolved.
|
||
| additionalProperties: false | ||
| properties: | ||
| message: | ||
| type: string | ||
| description: >- | ||
| What the cardholder typed, verbatim and unredacted. Send it exactly as | ||
| they entered it: Grid masks card numbers and other sensitive digit | ||
| strings and returns the result as `redactedCustomerMessage`, which is | ||
| what your UI must render. | ||
|
|
||
|
|
||
| Empty, absent, or whitespace-only opens the chat (or resumes an open | ||
| one) and runs no turn: no agent reply to a message, no SMS, and no | ||
| `Idempotency-Key` needed or consumed. A non-empty value runs exactly one | ||
| turn synchronously and requires an `Idempotency-Key` header. | ||
| example: My card was declined at the grocery store | ||
| locale: | ||
| type: string | ||
| minLength: 2 | ||
| maxLength: 32 | ||
| description: >- | ||
| BCP 47 locale the agent should answer in. Read only by the call that | ||
| opens the chat and ignored afterwards, because changing it later would | ||
| rewrite the language earlier turns were answered in. Defaults to | ||
| `en-US`. | ||
| example: en-US | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| type: object | ||
| description: >- | ||
| The result of one call to the message route. Which fields are present | ||
| depends on what the call did: `chatId`, `chatEnded` and | ||
| `verificationRequired` are always present; `codeLength` is added when | ||
| verification is required and the length is known; a turn adds `id`, | ||
| `redactedCustomerMessage` and `reply`, and the call that opened the chat adds | ||
| `reply` alone, carrying the agent's opening line; and the turn in which the | ||
| cardholder sent their verification code adds `sessionToken` and `expiresAt`. | ||
| Render `redactedCustomerMessage` if present, then `reply` if present. | ||
| required: | ||
| - chatId | ||
| - verificationRequired | ||
| - chatEnded | ||
| properties: | ||
| chatId: | ||
| type: string | ||
| description: >- | ||
| The chat this call ran on. When the request addressed a cardholder | ||
| (`Customer:<uuid>` in the path) this is the chat that was found or | ||
| opened for them. Use it for every later call on this conversation: | ||
| later messages and closing the chat. | ||
| example: CareChat:019542f5-b3e7-1d02-0000-000000000030 | ||
| chatEnded: | ||
| type: boolean | ||
| description: >- | ||
| Whether the chat has ended as of this response, so no further message | ||
| will be accepted. Present on every response, true or false, so a client | ||
| reads a field rather than an absence. It is the chat's state, not | ||
| whether this call ended it: on the turn where the agent ends the chat it | ||
| is true and the closing line is in `reply`, and a replay of that turn | ||
| after the chat ended is also true. When it is true, stop sending to this | ||
| chat; to talk to the cardholder again, address them as | ||
| `Customer:<uuid>`, which opens a new chat. | ||
| example: false | ||
| verificationRequired: | ||
| type: boolean | ||
| description: >- | ||
| An optional hint: whether the cardholder still has to verify before the | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you imagine the chat UX behaving when the verificationRequired response is true? Does it mean the next message must be only the code, or do we use regex / AI to extract the code from the next message? I'm having a hard time imagining what additional logic I would add if I were building a chat UI. |
||
| agent can reach their account. Present on every response, true or false. | ||
| A partner can ignore it, because when a code is needed the agent asks | ||
| for it in `reply`; it lets an app show a dedicated code input beside the | ||
| chat. Either way, the cardholder sends the code as an ordinary message. | ||
| An outstanding verification is never reported as a `401`. | ||
|
|
||
|
|
||
| It describes the chat as of the moment the turn started, so the one turn | ||
| in which the cardholder sent their code reports | ||
| `verificationRequired: true` and carries `sessionToken` as well. Decide | ||
| that you hold a session by the presence of `sessionToken`, never by this | ||
| field going false. | ||
| example: true | ||
| id: | ||
| type: string | ||
| description: >- | ||
| Id of the cardholder's message. Present only when a turn ran, so absent | ||
| on an opening or a resume. A retry under the same `Idempotency-Key` | ||
| returns the same `id`, so a client that has already rendered this id can | ||
| skip the exchange instead of showing it twice. | ||
| example: CareMessage:019542f5-b3e7-1d02-0000-000000000032 | ||
| redactedCustomerMessage: | ||
| type: string | ||
| description: >- | ||
| What the cardholder typed, with card numbers and other sensitive digit | ||
| strings masked, and **the string your app must render in the | ||
| transcript**. Render this, never your own copy of what the cardholder | ||
| typed: your copy would put a card number they pasted back on their | ||
| screen and into your logs. Present only when a turn ran. | ||
| example: My card [redacted card number ending 1111] was declined | ||
| reply: | ||
| type: string | ||
| description: >- | ||
| What the agent said on this call, to render as the assistant's message. | ||
| Present whenever the agent said something: its answer when a turn ran, | ||
| or its opening line on the call that opened the chat. Absent on a | ||
| resume. An opening needs no `Idempotency-Key` and consumes none, even | ||
| though it carries a `reply`. A retry under the same `Idempotency-Key` | ||
| returns the same reply and the same `id`, provided it carries the session | ||
| token its original request required: retrying a turn sent while the chat | ||
| could reach the account needs a valid `X-Ls-Care-Session`, and without | ||
| one is refused with `401 CARE_SESSION_EXPIRED`; a turn that ran before | ||
| the cardholder verified can be retried without one. A retry never carries | ||
| a `sessionToken`, even if the original response did. When verification | ||
| is outstanding the | ||
| agent's own sentence asks for the code, and when a code the cardholder | ||
| sent did not match, this is where they are told so. | ||
| example: I can help with that. To look at your account I've texted you a code. What does it say? | ||
| codeLength: | ||
| type: integer | ||
| description: >- | ||
| An optional hint: the number of digits in the code, so an app can show a | ||
| one-time-code input of the right length, marked as a one-time code so | ||
| iOS and Android offer to fill it from the SMS. The app still sends the | ||
| code as an ordinary message. Absent when the length is not known; leave | ||
| the input unbounded rather than guessing, since a wrong length makes | ||
| every correct code look like a typo. | ||
| example: 6 | ||
| sessionToken: | ||
| type: string | ||
| description: >- | ||
| The session token for this chat, present only on the turn in which the | ||
| cardholder verified themselves, and absent on every other turn. | ||
|
|
||
|
|
||
| The cardholder completes verification by sending the SMS code as an | ||
| ordinary message. When the code is correct, the chat comes out of that | ||
| turn able to reach the account, and this field is how the partner | ||
| receives the credential that turn earned. It is the only place a session | ||
| token is ever issued. A later message sent without it cancels the | ||
| verification and asks the cardholder for a new code. | ||
|
|
||
|
|
||
| Send it as the `X-Ls-Care-Session` header from the next call onwards. | ||
| It is returned once and never re-readable, it cannot be revoked, and it | ||
| is bound to this platform, this chat and this cardholder. Never log it, | ||
| never put it in a URL, and never store it past `expiresAt`. | ||
|
|
||
|
|
||
| One session is issued per verification rather than per message, so an | ||
| ordinary turn on a chat that is already verified carries no | ||
| `sessionToken`. The one you are already holding is still the right one. | ||
|
|
||
|
|
||
| A replay under the same `Idempotency-Key` never returns a | ||
| `sessionToken`, even if the original response carried one, and a replay | ||
| of a turn that required a session needs that session itself. So if the | ||
| response carrying the token was lost, you cannot recover it or replay an | ||
| account turn: send the cardholder's next message as a new message | ||
| without a token. The verification is cancelled, a new code is sent, and | ||
| the agent asks for it. A replay of the turn in which the cardholder sent | ||
| their code needs no token and returns the original reply without one. | ||
| example: eyJwbGF0Zm9ybV9pZCI6IjAxOTU0MmY1LWIzZTctMWQwMi0wMDAwLTAwMDAwMDAwMDAwMSJ9.aGVsbG8.c2lnbmF0dXJl | ||
| expiresAt: | ||
| type: string | ||
| format: date-time | ||
| description: >- | ||
| ISO 8601 timestamp, in UTC, after which the `sessionToken` beside it no | ||
| longer counts as a session: a message carrying it past this time is | ||
| treated as carrying none, and the cardholder is asked to verify again. | ||
| Present exactly when `sessionToken` is present. Read it rather than | ||
| assuming a duration. | ||
| example: '2026-09-12T14:25:00Z' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| type: object | ||
| description: | | ||
| The error body returned by every endpoint under **Embedded Care**. | ||
|
|
||
| It carries `code` and `reason`, and nothing else. This differs from the | ||
| `Error4xx` / `Error5xx` schemas the rest of this specification uses, which | ||
| declare `status` and `message`: the care endpoints do not send those two | ||
| fields. Read the HTTP status from the response line and the human-readable | ||
| text from `reason`. | ||
|
|
||
| Branch on `code`, never on `reason`: the text is written for a human | ||
| reading a log and may be reworded at any time. | ||
| required: | ||
| - code | ||
| - reason | ||
| properties: | ||
| code: | ||
| type: string | ||
| description: | | ||
| Machine-readable error code. Each endpoint's response descriptions say | ||
| which of these it can send at which status. | ||
|
|
||
| | Error Code | HTTP | Description | | ||
| |------------|------|-------------| | ||
| | `INVALID_INPUT` | 400 | The request body or headers are malformed: an unknown field, a body that is not a JSON object, a missing or over-long `Idempotency-Key` on a message that carries text, a `chatId` path id beginning `Customer:` that is not well formed, or a chat that cannot be verified at all. | | ||
| | `CARE_MESSAGE_REJECTED` | 400 | The cardholder's message was refused, for example because it is too long, and no turn ran. `reason` carries the sentence to show them. | | ||
| | `UNAUTHORIZED` | 401 | The platform's Grid credential is missing or wrong. On this surface a `401` never means the cardholder is unverified; that is signalled in a `200` body. | | ||
| | `CARE_SESSION_EXPIRED` | 401 | The `X-Ls-Care-Session` token presented is malformed, or not a care session token at all; or a replay under an `Idempotency-Key` lacks the valid session token its original request required, in which case nothing from the record is returned. For a new message, a missing or expired token is never this error: the message verifies the cardholder again inside a `200` instead. Rarely, also returned when a verification lands on the chat at the same moment a message without a live token takes it away; send the message again. | | ||
| | `FORBIDDEN` | 403 | Embedded care is not enabled for this platform. | | ||
| | `CARE_SESSION_PLATFORM_MISMATCH` | 403 | The session token was minted for a different platform. Stop; retrying will not help. | | ||
| | `NOT_FOUND` | 404 | No such chat for this platform. A chat belonging to another platform, a chat that never existed, a malformed chat id, and an `X-Ls-Care-Session` token minted for a different chat or a different cardholder are one answer. | | ||
| | `CUSTOMER_NOT_FOUND` | 404 | No such cardholder for this platform. | | ||
| | `CONFLICT` | 409 | The `Idempotency-Key` was already used for a different message or a different chat (use a new key), or the chat has no cardholder bound to it. | | ||
| | `CARE_CHAT_ENDED` | 409 | The chat has ended, whether the agent ended it or it was closed, and it accepts no further messages or openings; a replay of a turn that completed or is still running is still answered. Open a new one by sending an empty message to the cardholder as `Customer:<uuid>`. | | ||
| | `CARE_NOT_CONFIGURED_FOR_PLATFORM` | 422 | The platform is entitled to embedded care but has no care programme configured. | | ||
| | `CARE_BUDGET_EXCEEDED` | 429 | This chat has reached its limit. Opening a new chat for the cardholder does not clear it. | | ||
| | `RATE_LIMITED` | 429 | The platform is calling this endpoint too often. Back off for the interval in the `Retry-After` response header. | | ||
| | `INTERNAL_ERROR` | 500 | Unexpected server error. | | ||
| | `CARE_TURN_FAILED` | 502 | The agent could not answer. Transient; the same `Idempotency-Key` may be sent again. | | ||
| | `CARE_VERIFICATION_UNAVAILABLE` | 502 | The verification code could not be sent or checked right now. Distinct from a wrong code: the problem is temporary and not the cardholder's, so retry rather than asking them again. | | ||
| | `SERVICE_UNAVAILABLE` | 503 | Embedded care is not available in this deployment, or the support agent is temporarily turned off. | | ||
| | `CARE_TURN_TIMEOUT` | 504 | The turn is still running. Retry with the same `Idempotency-Key` to collect the answer. | | ||
| enum: | ||
| - INVALID_INPUT | ||
| - CARE_MESSAGE_REJECTED | ||
| - UNAUTHORIZED | ||
| - CARE_SESSION_EXPIRED | ||
| - FORBIDDEN | ||
| - CARE_SESSION_PLATFORM_MISMATCH | ||
| - NOT_FOUND | ||
| - CUSTOMER_NOT_FOUND | ||
| - CONFLICT | ||
| - CARE_CHAT_ENDED | ||
| - CARE_NOT_CONFIGURED_FOR_PLATFORM | ||
| - CARE_BUDGET_EXCEEDED | ||
| - RATE_LIMITED | ||
| - INTERNAL_ERROR | ||
| - CARE_TURN_FAILED | ||
| - CARE_VERIFICATION_UNAVAILABLE | ||
| - SERVICE_UNAVAILABLE | ||
| - CARE_TURN_TIMEOUT | ||
| example: CARE_SESSION_EXPIRED | ||
| reason: | ||
| type: string | ||
| description: >- | ||
| Human-readable explanation, for a log or a support ticket. Not for | ||
| branching, and not always safe to show a cardholder verbatim. The | ||
| exception is `CARE_MESSAGE_REJECTED`, whose `reason` is written to be | ||
| shown. | ||
| example: The care session token is not valid. |
Uh oh!
There was an error while loading. Please reload this page.