> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lightspark.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send a care chat message

> Run one turn of the cardholder's conversation with the support agent, or,
with an empty `message`, open the chat without saying anything. This is
the entry point to the whole surface: a partner who has never called any
other care endpoint starts here.

**Render `redactedCustomerMessage` if present, then `reply` if present.**
That one rule covers every call: an opening, a resume and a turn.
`redactedCustomerMessage` is what the cardholder typed with card numbers
and other sensitive digit strings masked. Render it, never your own copy of
what they typed, which would put those digits back on their screen and
into your logs.

**One turn, synchronously.** With a non-empty `message` the call holds the
connection until the agent has answered, and returns the answer in
`reply`. An `Idempotency-Key` header is required, and it is the only way
to recover an answer: nothing on this surface returns a transcript, so a
reply that was produced but not delivered is reachable only by retrying
under the same key, which returns the original reply, with the same `id`,
and never a `sessionToken`. **A replay requires the same session token its original
request required.** Replaying a turn that was sent while the chat could
reach the account needs a valid `X-Ls-Care-Session` for this chat; without
one it is refused with `401 CARE_SESSION_EXPIRED` and returns no reply. A
replay of a turn that ran before the cardholder verified, such as the turn
in which they sent their code, needs no token. A replay never returns a
`sessionToken`, even if the original response carried one, and it never
cancels a verification or sends a code. So a partner who lost the token
cannot replay an account turn: send the cardholder's next message as a new
message without a token, which verifies them again (the verification is
cancelled, a new code is sent, and the agent asks for it). A replay is
answered, under the same rule, even after the chat has ended. A retry sent
while the original turn is still running is a replay too: it waits for
that turn's answer rather than running a second turn. A turn that
failed does not pin its key: the same key may be sent again and will run a
fresh turn.

**An empty or absent `message` opens the chat and runs no turn.** On a
cardholder with no open chat it opens one, and `reply` carries the agent's
opening line, which is what a chat window renders before anybody has
typed. On a cardholder who already has one it resumes: the chat's id and
where verification stands come back, and `reply` does not, because an
opening line arriving mid-conversation reads as the agent introducing
itself a second time. `id` and `redactedCustomerMessage` are absent on
both, because nothing was typed. Either way no code is sent and
no `Idempotency-Key` is needed or consumed, so a widget in a reconnect
loop may still call it as often as it likes. The exception is an opening
addressed to a `CareChat:<id>` that has ended: it answers
`409 CARE_CHAT_ENDED`, and the recovery is to address the cardholder as
`Customer:<uuid>`, which opens a new chat.

**`chatEnded` says when to stop.** It is on every `200` body, true or
false, and describes the chat once the call finished: true means the chat
has ended and no further message will be accepted. The agent can end the
chat itself when the cardholder is done; on that turn `chatEnded` is true
and the closing line is in `reply`. It is the chat's state rather than
what this call did, so a replay of that turn also says true. When it is
true, stop sending to that chat.

**Verification happens in the conversation, and is never a `401`.** A
chat that cannot yet reach the account answers `200`, and the first
message with text texts the cardholder a code; later messages on the same
chat send no second one. The agent's own sentence in `reply` asks for the
code, so a partner that only renders the conversation needs nothing else.
`verificationRequired` and `codeLength` are optional hints for an app that
wants a dedicated 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.

**The cardholder completes verification by sending the code as an
ordinary message, and that turn's response carries the session.** When the
code is correct, the chat comes out of that turn able to reach the
account, and the body of that turn carries `sessionToken` and `expiresAt`.
No other turn carries them: a session is issued per verification, not per
message. Store the token and send it from the next call onwards. A code
that does not match is answered in `reply`, not as an error, and the
cardholder can send it again.

**A cardholder whose code never arrived asks the agent for another one**,
in the conversation, and the agent sends a new code to the number on file.
There is no separate route for sending a code.

While the chat's verification is current, send the session token in the
`X-Ls-Care-Session` header on every message.

**A verification goes stale, and the agent then asks for a code again
rather than the call failing.** A chat reaches the cardholder's account
only while its verification is newer than the session lifetime, fifteen
minutes in the default deployment, which is the same window a session
token lives for, so in practice the chat and the token go stale together.
Past that window the next message **sent without the header** is treated
as an unverified one: a fresh code goes out, the body carries
`verificationRequired: true`, and the agent's own `reply` asks the
cardholder to send the code. The turn still runs and still answers.

**A lost or expired session token verifies again rather than being
refused.** A new message sent with no `X-Ls-Care-Session` header, or with a
token past its expiry, on a chat whose verification is still current is
treated the same way: the verification is cancelled before the turn runs,
so the turn cannot reach the account, and the call then proceeds exactly
as on a stale chat. A fresh code goes out, the agent asks the cardholder
for it, and the turn in which they send it returns the next
`sessionToken`. No special handling is needed: a client that lost its
token simply keeps sending messages.

Only a token that is malformed is refused with `CARE_SESSION_EXPIRED`, and
a genuine token minted for another chat or another cardholder with
`NOT_FOUND`, or for another platform with `CARE_SESSION_PLATFORM_MISMATCH`.
Those are a bug in the caller rather than a lost session, and nothing on
the chat changes. An empty message never reads the header at all, so a
widget still holding a dead token can resume with one and be told where
verification stands without texting anybody.

Everything in the response except `chatEnded` describes the chat as of
the moment the turn started. The agent can verify a cardholder during a
turn, so a chat that this very call verified still reads
`verificationRequired: true` here; decide that you hold a session by the
presence of `sessionToken`.

Requires a token with the `VIEW` and `MANAGE` permissions; `VIEW` alone
is not sufficient, because the agent can freeze, unfreeze and replace the
cardholder's cards.




## OpenAPI

````yaml /openapi.yaml post /care/chats/{chatId}/messages
openapi: 3.1.0
info:
  title: Grid API
  description: >
    API for managing global payments on the open Money Grid. Built by
    Lightspark. See the full documentation at https://docs.lightspark.com/.
  version: '2025-10-13'
  contact:
    name: Lightspark Support
    email: support@lightspark.com
  license:
    name: Proprietary
    url: https://lightspark.com/terms
servers:
  - url: https://api.lightspark.com/grid/2025-10-13
    description: Production server
security:
  - BasicAuth: []
  - AgentAuth: []
tags:
  - name: Platform Configuration
    description: >-
      Platform configuration endpoints for managing global settings. You can
      also configure these settings in the Grid dashboard.
  - name: Customers
    description: >-
      Customer management endpoints for creating and updating customer
      information
  - name: Contact Verification
    description: >-
      Endpoints for verifying a customer's email and phone via one-time codes.
      Required only for customers whose payment provider mandates contact
      verification (e.g. EU customers); other providers return 409.
  - name: Strong Customer Authentication
    description: >-
      Endpoints for authorizing money-movement operations that require Strong
      Customer Authentication. Relevant only for customers in a region where SCA
      is required (e.g. EU); customers outside SCA-regulated regions never see
      an SCA challenge and these endpoints return 409.
  - name: KYC/KYB Verifications
    description: >-
      Endpoints for Know Your Customer (KYC) and Know Your Business (KYB)
      verification, including managing beneficial owners and triggering
      verification for customers.
  - name: Documents
    description: >-
      Endpoints for uploading and managing verification documents for customers
      and beneficial owners. Supports KYC and KYB document requirements.
  - name: Internal Accounts
    description: >-
      Internal account management endpoints for creating and managing internal
      accounts
  - name: Periodic Statements
    description: >
      Build, deliver, and evidence a Regulation E periodic statement for a
      customer's internal account. A statement period is a calendar month in US
      Central time (`America/Chicago`).


      A statement is issued for each customer's own USD internal account and
      covers their whole balance. Money received through a rule-based account
      appears on its owner's statement; rule-based, bulk settlement and
      platform-owned accounts have no statement of their own.


      **1. The statement — `GET
      /internal-accounts/{id}/balance-changes?startDate=&endDate=`**


      One row per change to the balance, in the order the money moved, with the
      opening and closing balances for the window in the same response. Page
      until `hasMore` is false, then assert this identity across every page
      before you render anything:


      ```

      openingBalance + Σ(data[].amount) == closingBalance

      ```


      If it does not hold, do not send the statement; contact support instead.
      `startDate` and `endDate` bound a half-open window `[startDate, endDate)`:
      for a monthly statement, pass the first instant of the month and the first
      instant of the following month, both in US Central time (for August 2026,
      `2026-08-01T00:00:00-05:00` and `2026-09-01T00:00:00-05:00`). Grid records
      a statement as fetched only for a window that is exactly one such month.


      **2. The receipt — `POST /internal-accounts/{id}/confirm-statement`**


      Once a month, after you have pulled and issued a period's statement, send
      a receipt with the `statementMonth` it covers. Grid stores it as the
      delivery record for that account and period. Sending it again is harmless:
      the first receipt's time is kept.


      **Timing**


      A window whose card settlement has not closed is refused with `409
      NOT_YET_AVAILABLE` rather than answered with figures that could still
      change; retry once it has settled.
  - name: External Accounts
    description: >-
      External account management endpoints for creating and managing external
      bank accounts
  - name: Same-Currency Transfers
    description: >-
      Deprecated endpoints for transferring funds between internal and external
      accounts with the same currency. Use the quote endpoints under
      Cross-Currency Transfers instead, which now serve same-currency transfers
      as well.
  - name: Cross-Currency Transfers
    description: >-
      Endpoints for creating and confirming quotes for transfers, both
      same-currency and cross-currency
  - name: Transactions
    description: Endpoints for retrieving transaction information
  - name: Webhooks
    description: Webhook endpoints and configuration for receiving notifications
  - name: Invitations
    description: Endpoints for creating, claiming and managing UMA invitations
  - name: Sandbox
    description: Endpoints to trigger test cases in sandbox
  - name: API Tokens
    description: Endpoints to programmatically manage API tokens
  - name: Exchange Rates
    description: >-
      Endpoints for retrieving cached foreign exchange rates. Rates are cached
      for approximately 5 minutes and include platform-specific fees.
  - name: Discoveries
    description: >-
      Endpoints for discovering available payment rails, banks, and providers
      for a given country and currency corridor.
  - name: Embedded Wallet Auth
    description: >-
      Endpoints for registering and verifying end-user authentication
      credentials (email OTP, OAuth, passkey) used to sign Embedded Wallet
      actions.
  - name: Agent Management
    description: >-
      Endpoints for creating and managing agents (experimental), called by the
      partner's backend using platform credentials. Covers the full agent
      lifecycle: creation, policy configuration, pausing, deletion, the device
      code installation flow, and approving or rejecting transactions initiated
      by agents.
  - name: Agent Operations
    description: >-
      Experimental endpoints called by the agent itself using its own
      credentials (obtained via device code redemption). Scoped to the agent's
      associated customer — all requests automatically operate on behalf of that
      customer and are subject to the agent's policy. When an action requires
      approval, the resulting transaction enters a pending state and must be
      approved by the platform via `POST /transactions/{transactionId}/approve`.
  - name: Cards
    description: >-
      Card management endpoints. Issue debit cards against an internal account,
      freeze / unfreeze, close, manage a card's funding source, and list card
      transactions.
  - name: Stablecoins
    description: >-
      Stablecoin issuance endpoints. Link provider accounts, register
      provider-created stablecoins, create direct mint/burn issuer operations,
      and track operation status.
  - name: Embedded Care
    description: |
      A support chat between the cardholder and Lightspark's care agent, driven
      from the partner's own app. The platform calls with its ordinary Grid
      credential, but the person typing is the cardholder rather than the
      caller, so reaching that cardholder's account takes a second proof: an
      SMS code they send into the chat, after which the partner carries a
      session token in the `X-Ls-Care-Session` header.

      **The surface is two routes, and verification happens inside the
      conversation.**

      1. **Open the chat.** `POST /care/chats/{chatId}/messages` with an empty
         `message`, addressed to the cardholder as `Customer:<uuid>`. Returns
         the chat's id and the agent's opening line in `reply`, which is what
         a chat window renders before anybody has typed. Sends no SMS, needs
         no `Idempotency-Key` and consumes none, and is safe to call again: a
         widget that reloaded gets the same chat back, this time without a
         `reply`.
      2. **Send the cardholder's first message.** The same route with their
         text and an `Idempotency-Key` header. One turn runs synchronously and
         the agent's answer comes back in `reply`. On a chat that cannot yet
         reach the account this call also texts the cardholder a code, and the
         agent's `reply` asks for it.
      3. **The cardholder sends the code as an ordinary message.** When it is
         correct, the response to that turn carries `sessionToken` and
         `expiresAt`. Send that token as the `X-Ls-Care-Session` header on
         every message from then on. A code that does not match is answered in
         `reply`, and the cardholder can simply send it again.
      4. **Close the chat** with `DELETE /care/chats/{chatId}` when the
         cardholder is done. Every response also carries `chatEnded`, and the
         agent can end the chat itself: when `chatEnded` is true, the closing
         line is in `reply` and the chat takes no further messages, so stop
         sending to it. An opening addressed to that `CareChat:<id>` answers
         `409 CARE_CHAT_ENDED`; address the cardholder as `Customer:<uuid>`
         to open a new chat.

      **Render `redactedCustomerMessage` if present, then `reply` if
      present.** That one rule covers an opening, a resume and a turn.

      **A cardholder whose code never arrived asks the agent for another
      one.** The agent sends a new code to the number on file and says so in
      its `reply`. There is no separate route for it.

      **The verification fields are optional hints.** `verificationRequired`
      and `codeLength` let an app show a dedicated 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. A partner can ignore them, since the agent asks for the
      code in its reply and says where it went, and the app still sends the
      code as an ordinary message.

      Step 1 is optional. A message addressed to a `Customer:` id opens that
      cardholder's chat if they have none, so a partner whose cardholder has
      just typed something spends one call rather than two.

      A cardholder who has not verified yet is never a `401`, because the
      first message from every new cardholder is unverified and a `401` there
      would be indistinguishable from a bad credential. On these endpoints a
      `401` means the platform's Grid credential is wrong, the session token
      presented is malformed, or a replay lacks the session token its original
      request required.

      **Verification expires, and a lost session is recoverable in the
      chat.** A chat reaches the cardholder's account only while its
      verification is newer than the session lifetime, fifteen minutes in the
      default deployment, which is also how long a session token lives. Past
      that window a message gets a fresh code, and the agent asks the
      cardholder to send it back inside the conversation, which hands the
      partner the next session token. A new message sent with no
      `X-Ls-Care-Session` header, or with a token past its expiry, on a chat
      whose verification is still current is treated the same way: the
      verification is cancelled before the turn runs, a new code is sent, and
      the agent asks for it. So a client that lost its token, or held one too
      long, simply keeps sending messages. Only a token that is malformed, or
      that was minted for another platform, chat or cardholder, is refused.

      Two things an integration has to get right:

      - **Render `redactedCustomerMessage`, never your own copy of what was
        typed.** Grid masks card numbers and other sensitive digit strings in
        it. A UI that renders its own copy of the input instead puts a card
        number the cardholder pasted back on their screen unmasked, and into
        your logs.
      - **The session token is returned once and is never re-readable.** No
        endpoint hands it back, it cannot be revoked, and it is short-lived by
        design. Hold it for the life of the cardholder's session. It arrives
        on the message response to the turn in which the cardholder sent their
        code, and on no other. Losing it costs the cardholder a new code, never
        access to the chat.

      Some things are absent by construction, so no integration can come to
      depend on them: nothing returns a transcript or an earlier turn's reply
      (retrying under the original `Idempotency-Key` is how an answer that was
      produced but not delivered is recovered; a replay requires the same
      session token its original request required and never carries a
      `sessionToken`, so a lost token is recovered by sending the cardholder's
      next message as a new message, which verifies them again), nothing sets
      a verification state, and nothing changes the number a code is sent to.

      Errors from these endpoints carry `code` and `reason` only (see the
      `CareError` schema), unlike the `Error4xx` schemas used elsewhere in this
      specification.
paths:
  /care/chats/{chatId}/messages:
    parameters:
      - name: chatId
        in: path
        description: >-
          Who this message is for. Two forms are accepted, and the id's own
          prefix decides which: a `Customer:<uuid>` addresses the cardholder and
          lands on their open chat, opening one if they have none; a
          `CareChat:<uuid>` addresses that chat and no other. A bare uuid is
          read as a chat id. Addressing the cardholder is what lets a partner
          send their first message in one call instead of two.
        required: true
        schema:
          type: string
        example: Customer:019542f5-b3e7-1d02-0000-000000000001
    post:
      tags:
        - Embedded Care
      summary: Send a care chat message
      description: >
        Run one turn of the cardholder's conversation with the support agent,
        or,

        with an empty `message`, open the chat without saying anything. This is

        the entry point to the whole surface: a partner who has never called any

        other care endpoint starts here.


        **Render `redactedCustomerMessage` if present, then `reply` if
        present.**

        That one rule covers every call: an opening, a resume and a turn.

        `redactedCustomerMessage` is what the cardholder typed with card numbers

        and other sensitive digit strings masked. Render it, never your own copy
        of

        what they typed, which would put those digits back on their screen and

        into your logs.


        **One turn, synchronously.** With a non-empty `message` the call holds
        the

        connection until the agent has answered, and returns the answer in

        `reply`. An `Idempotency-Key` header is required, and it is the only way

        to recover an answer: nothing on this surface returns a transcript, so a

        reply that was produced but not delivered is reachable only by retrying

        under the same key, which returns the original reply, with the same
        `id`,

        and never a `sessionToken`. **A replay requires the same session token
        its original

        request required.** Replaying a turn that was sent while the chat could

        reach the account needs a valid `X-Ls-Care-Session` for this chat;
        without

        one it is refused with `401 CARE_SESSION_EXPIRED` and returns no reply.
        A

        replay of a turn that ran before the cardholder verified, such as the
        turn

        in which they sent their code, needs no token. A replay never returns a

        `sessionToken`, even if the original response carried one, and it never

        cancels a verification or sends a code. So a partner who lost the token

        cannot replay an account turn: send the cardholder's next message as a
        new

        message without a token, which verifies them again (the verification is

        cancelled, a new code is sent, and the agent asks for it). A replay is

        answered, under the same rule, even after the chat has ended. A retry
        sent

        while the original turn is still running is a replay too: it waits for

        that turn's answer rather than running a second turn. A turn that

        failed does not pin its key: the same key may be sent again and will run
        a

        fresh turn.


        **An empty or absent `message` opens the chat and runs no turn.** On a

        cardholder with no open chat it opens one, and `reply` carries the
        agent's

        opening line, which is what a chat window renders before anybody has

        typed. On a cardholder who already has one it resumes: the chat's id and

        where verification stands come back, and `reply` does not, because an

        opening line arriving mid-conversation reads as the agent introducing

        itself a second time. `id` and `redactedCustomerMessage` are absent on

        both, because nothing was typed. Either way no code is sent and

        no `Idempotency-Key` is needed or consumed, so a widget in a reconnect

        loop may still call it as often as it likes. The exception is an opening

        addressed to a `CareChat:<id>` that has ended: it answers

        `409 CARE_CHAT_ENDED`, and the recovery is to address the cardholder as

        `Customer:<uuid>`, which opens a new chat.


        **`chatEnded` says when to stop.** It is on every `200` body, true or

        false, and describes the chat once the call finished: true means the
        chat

        has ended and no further message will be accepted. The agent can end the

        chat itself when the cardholder is done; on that turn `chatEnded` is
        true

        and the closing line is in `reply`. It is the chat's state rather than

        what this call did, so a replay of that turn also says true. When it is

        true, stop sending to that chat.


        **Verification happens in the conversation, and is never a `401`.** A

        chat that cannot yet reach the account answers `200`, and the first

        message with text texts the cardholder a code; later messages on the
        same

        chat send no second one. The agent's own sentence in `reply` asks for
        the

        code, so a partner that only renders the conversation needs nothing
        else.

        `verificationRequired` and `codeLength` are optional hints for an app
        that

        wants a dedicated 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.


        **The cardholder completes verification by sending the code as an

        ordinary message, and that turn's response carries the session.** When
        the

        code is correct, the chat comes out of that turn able to reach the

        account, and the body of that turn carries `sessionToken` and
        `expiresAt`.

        No other turn carries them: a session is issued per verification, not
        per

        message. Store the token and send it from the next call onwards. A code

        that does not match is answered in `reply`, not as an error, and the

        cardholder can send it again.


        **A cardholder whose code never arrived asks the agent for another
        one**,

        in the conversation, and the agent sends a new code to the number on
        file.

        There is no separate route for sending a code.


        While the chat's verification is current, send the session token in the

        `X-Ls-Care-Session` header on every message.


        **A verification goes stale, and the agent then asks for a code again

        rather than the call failing.** A chat reaches the cardholder's account

        only while its verification is newer than the session lifetime, fifteen

        minutes in the default deployment, which is the same window a session

        token lives for, so in practice the chat and the token go stale
        together.

        Past that window the next message **sent without the header** is treated

        as an unverified one: a fresh code goes out, the body carries

        `verificationRequired: true`, and the agent's own `reply` asks the

        cardholder to send the code. The turn still runs and still answers.


        **A lost or expired session token verifies again rather than being

        refused.** A new message sent with no `X-Ls-Care-Session` header, or
        with a

        token past its expiry, on a chat whose verification is still current is

        treated the same way: the verification is cancelled before the turn
        runs,

        so the turn cannot reach the account, and the call then proceeds exactly

        as on a stale chat. A fresh code goes out, the agent asks the cardholder

        for it, and the turn in which they send it returns the next

        `sessionToken`. No special handling is needed: a client that lost its

        token simply keeps sending messages.


        Only a token that is malformed is refused with `CARE_SESSION_EXPIRED`,
        and

        a genuine token minted for another chat or another cardholder with

        `NOT_FOUND`, or for another platform with
        `CARE_SESSION_PLATFORM_MISMATCH`.

        Those are a bug in the caller rather than a lost session, and nothing on

        the chat changes. An empty message never reads the header at all, so a

        widget still holding a dead token can resume with one and be told where

        verification stands without texting anybody.


        Everything in the response except `chatEnded` describes the chat as of

        the moment the turn started. The agent can verify a cardholder during a

        turn, so a chat that this very call verified still reads

        `verificationRequired: true` here; decide that you hold a session by the

        presence of `sessionToken`.


        Requires a token with the `VIEW` and `MANAGE` permissions; `VIEW` alone

        is not sufficient, because the agent can freeze, unfreeze and replace
        the

        cardholder's cards.
      operationId: sendCareChatMessage
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            A unique identifier for this turn, up to 255 characters. **Required
            whenever `message` is non-empty**; omitting it there is rejected
            with `INVALID_INPUT`. A retry carrying the same key returns the
            first call's answer with the same `id` (subject to the session rule
            for replays); aiming the same key at a different message, a
            different chat or a different cardholder is rejected with `409
            CONFLICT`. An opening (empty `message`) neither needs nor consumes a
            key: one sent anyway is ignored, so that the partner's first real
            message can still use it.
          schema:
            type: string
            maxLength: 255
            example: 550e8400-e29b-41d4-a716-446655440000
        - name: X-Ls-Care-Session
          in: header
          required: false
          description: >-
            The `sessionToken` this route returned on the turn in which the
            cardholder sent their verification code. Send it on every message
            while the chat's verification is current. A message without it, or
            with one past its expiry, is not refused: the chat's verification is
            cancelled before the turn runs and a new code is sent, so the
            cardholder verifies again in the conversation. The exception is a
            replay under an `Idempotency-Key` whose original request required a
            session: it must carry a valid token, or it is refused with
            `CARE_SESSION_EXPIRED` and returns no reply. A token that is sent is
            always checked for what it names: a malformed one is refused with
            `CARE_SESSION_EXPIRED`, one minted for another chat or another
            cardholder is `NOT_FOUND`, and one minted for another platform is
            `CARE_SESSION_PLATFORM_MISMATCH`, whether or not it has expired. An
            opening never reads the header at all, because a widget that
            reloaded has lost its token and that is the case the opening exists
            for.
          schema:
            type: string
          example: >-
            eyJwbGF0Zm9ybV9pZCI6IjAxOTU0MmY1LWIzZTctMWQwMi0wMDAwLTAwMDAwMDAwMDAwMSJ9.aGVsbG8.c2lnbmF0dXJl
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CareChatMessageRequest'
            examples:
              openTheChat:
                summary: >-
                  Open or resume the cardholder's chat; an opening returns the
                  agent's opening line as reply
                value:
                  message: ''
              firstMessage:
                summary: The cardholder's first message, which also sends the code
                value:
                  message: My card was declined at the grocery store
              verificationCode:
                summary: >-
                  The cardholder sends their code, and this turn returns the
                  session
                value:
                  message: The code is 482913
              afterVerification:
                summary: A later message, sent with the X-Ls-Care-Session header
                value:
                  message: Can you tell me the last four digits on file?
      responses:
        '200':
          description: >-
            The turn ran, or the chat was opened or resumed. Which fields are
            present depends on which of those happened.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareChatMessageResponse'
        '400':
          description: >-
            Bad request. `INVALID_INPUT` when the `Idempotency-Key` header is
            missing on a message that carries text or exceeds 255 characters,
            when the body is not a JSON object or carries an unknown field, or
            when the path id is a `Customer:` id that is not well formed.
            `CARE_MESSAGE_REJECTED` when the cardholder's message was refused
            before any turn ran; `reason` is written to be shown to them.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '401':
          description: >-
            `UNAUTHORIZED` when the platform's Grid credential is missing or
            wrong. `CARE_SESSION_EXPIRED` when the `X-Ls-Care-Session` token
            presented is malformed, or when a replay under an `Idempotency-Key`
            lacks the valid session token its original request required; no
            reply is returned. Otherwise a missing or expired session token is
            never a `401`: a new message verifies the cardholder again inside a
            `200` carrying `verificationRequired: true`, as does an unverified
            or stale chat. `CARE_SESSION_EXPIRED` is also returned, rarely, when
            a verification lands on the chat at the same moment a message
            without a live token takes it away; send the message again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '403':
          description: >-
            `FORBIDDEN` when the token lacks the `VIEW` or `MANAGE` permission,
            or embedded care is not enabled for this platform.
            `CARE_SESSION_PLATFORM_MISMATCH` when the session token was minted
            for another platform.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '404':
          description: >-
            `NOT_FOUND` when the path names a chat this platform does not own,
            or when the `X-Ls-Care-Session` token was minted for a different
            chat or for a different cardholder; a token that does not belong to
            the chat in the path is a chat-scoped fact and answers the same way
            a missing chat does. `CUSTOMER_NOT_FOUND` when the path names a
            cardholder this platform does not own. A resource that belongs to
            somebody else and one that never existed are the same answer.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '409':
          description: >-
            `CONFLICT` when the `Idempotency-Key` was already used for a
            different message or a different chat (use a new key), or when the
            chat has no cardholder bound to it. `CARE_CHAT_ENDED` when a
            message, or an opening, is sent to a chat that has ended, other than
            a replay of a turn that completed or is still running; open a new
            one by sending a message addressed to the cardholder as
            `Customer:<uuid>`, which finds no open chat and opens a fresh one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '422':
          description: >-
            `CARE_NOT_CONFIGURED_FOR_PLATFORM` when a message addressed to a
            cardholder has to find or open their chat and the platform, though
            entitled to embedded care, has no care programme configured.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '429':
          description: >-
            `CARE_BUDGET_EXCEEDED` when this chat has reached its limit; opening
            a new chat for the cardholder does not clear it. `RATE_LIMITED` when
            the platform is calling this endpoint too often; back off for the
            interval in the `Retry-After` header.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '500':
          description: Internal service error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '502':
          description: >-
            `CARE_TURN_FAILED` when the agent could not answer, and
            `CARE_VERIFICATION_UNAVAILABLE` when the code could not be sent.
            Both are transient and neither pins the key: retry with the same
            `Idempotency-Key`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '503':
          description: >-
            `SERVICE_UNAVAILABLE` when embedded care is not available in this
            deployment, or the support agent is temporarily turned off.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
        '504':
          description: >-
            `CARE_TURN_TIMEOUT` when the turn is still running. The answer is
            not lost: retry with the same `Idempotency-Key` to collect it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CareError'
      security:
        - BasicAuth: []
components:
  schemas:
    CareChatMessageRequest:
      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`.
      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
    CareChatMessageResponse:
      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 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'
    CareError:
      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:
          $ref: '#/components/schemas/CareErrorCode'
        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.
    CareErrorCode:
      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 | The token lacks a permission the endpoint
        requires, or 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
  securitySchemes:
    BasicAuth:
      type: http
      scheme: basic
      description: >-
        API token authentication using format `<api token id>:<api client
        secret>`
    AgentAuth:
      type: http
      scheme: bearer
      description: >-
        Bearer token authentication for agent-scoped endpoints. The token is the
        `accessToken` returned when redeeming a device code via `POST
        /agents/device-codes/redeem`. Agent credentials are user-scoped: all
        requests are automatically bound to the agent's associated customer and
        subject to the agent's policy.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.