> For the complete documentation index, see [llms.txt](https://hub.equipme.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hub.equipme.io/development/api-reference-solvio-beta/message.md).

# Message

A **message** is one entry in a thread's conversation. Messages carry rich text and any number of attachments, and they are what actually travels to the outside world when a thread is bound to a communication channel.

`direction` says where a message stands in that flow:

| Direction  | Meaning                                   |
| ---------- | ----------------------------------------- |
| `INTERNAL` | never leaves Solvio                       |
| `INBOUND`  | arrived from an external channel          |
| `OUTBOUND` | is delivered through the thread's channel |

Posting requires write access to the thread; editing is reserved for the message's own author.

## List the messages of a thread

> Returns the messages of a thread, ordered by \`id\` unless \`sort\` says otherwise.\
> \
> \*\*Filterable\*\* — \`id\`, \`threadId\`, \`authorId\`, \`replyToId\`, \`direction\`, \`originChannelId\`,\
> \`originMessageId\`, \`createdAt\`, \`updatedAt\`, and paths into the author, such as \`author.lastName\`.\
> \
> \*\*Sortable\*\* — \`id\`, \`threadId\`, \`createdAt\`, \`updatedAt\`.\
> \
> \*\*Searchable\*\* — nothing yet.\
> \
> \`direction\` tells where a message came from or is going: \`INTERNAL\` never leaves Solvio, \`INBOUND\`\
> arrived from an external channel, \`OUTBOUND\` is delivered through the thread's channel.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **message** is one entry in a thread's conversation. Messages carry rich text and any number of\nattachments, and they are what actually travels to the outside world when a thread is bound to a\ncommunication channel.\n\n`direction` says where a message stands in that flow:\n\n| Direction | Meaning |\n| --- | --- |\n| `INTERNAL` | never leaves Solvio |\n| `INBOUND` | arrived from an external channel |\n| `OUTBOUND` | is delivered through the thread's channel |\n\nPosting requires write access to the thread; editing is reserved for the message's own author.\n","name":"message"}],"servers":[{"description":"The host serving this document","url":"/api"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"description":"Token issued by the central equipme authentication, sent as `Bearer <token>`.","scheme":"bearer","type":"http"},"ApiKeyAuth":{"description":"Static key for machine-to-machine access. Alternative to BearerAuth.","in":"header","name":"X-API-Key","type":"apiKey"}},"schemas":{"v1.ListThreadMessagesResponse-domain_Message":{"properties":{"count":{"type":"integer"},"data":{"items":{"$ref":"#/components/schemas/domain.Message"},"type":"array","uniqueItems":false}},"type":"object"},"domain.Message":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"author":{"$ref":"#/components/schemas/domain.Contact"},"authorId":{"type":"integer"},"content":{"description":"Content is the message text; an empty string for a message that consists of attachments only.","type":"string"},"contentPreview":{"type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"direction":{"$ref":"#/components/schemas/domain.MessageDirection"},"id":{"type":"integer"},"originChannel":{"$ref":"#/components/schemas/domain.Channel"},"originChannelId":{"type":"integer"},"replyTo":{"$ref":"#/components/schemas/domain.Message"},"replyToId":{"type":"integer"},"threadId":{"description":"A message is only ever listed under its thread, so the way back is redundant\nthere and closes the thread→message→thread cycle in the schema. threadId\nidentifies it; \"?fields=thread\" still works.","type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.Attachment":{"properties":{"composing":{"description":"Composing marks the attachment as belonging to an active message composition session.\nThe term mirrors the \"compose\" state in messaging clients: the user is writing a message but has not sent it yet.\nOnce the message is sent, ClaimAttachments links these attachments to it and clears this flag.\nComposing attachments are excluded from all list queries and are eligible for periodic cleanup if never claimed.","type":"boolean"},"contentId":{"description":"ContentID is the MIME Content-ID value used in cid: URI references within the message body.","type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"id":{"type":"integer"},"inline":{"description":"Inline indicates that the attachment is meant to be displayed within the message content rather than as a separate downloadable file.","type":"boolean"},"link":{"description":"Link refers to the download URL for the attachment. Relative path for the API endpoint that requires authorization,\nnot a pre-signed URL or direct S3 link. This is generated on the fly and not stored in the DB.","type":"string"},"messageId":{"type":"integer"},"name":{"type":"string"},"size":{"type":"number"},"threadId":{"description":"An attachment is listed under the thread or message it belongs to, so the two\nways back are redundant there and close both cycles in the schema. The ids\nidentify them; selecting the relations still works.","type":"integer"}},"type":"object"},"domain.Contact":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext drive automated ticket routing. Set today only on\nGROUP-kind contacts via the group endpoints; nil/empty on ordinary contacts.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ContactKind"},"lastName":{"type":"string"},"mobile":{"type":"string"},"phone":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.ContactKind":{"enum":["","PERSON","INTAKE","GROUP"],"type":"string"},"domain.Tenant":{"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},"domain.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.Channel":{"properties":{"active":{"type":"boolean"},"createdAt":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ChannelKind"},"name":{"type":"string"},"purpose":{"$ref":"#/components/schemas/domain.ChannelPurpose"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.ChannelKind":{"enum":["","EMAIL","WEBHOOK","TEAMS","SMS","WHATSAPP","WEBCHAT","NOTICE"],"type":"string"},"domain.ChannelPurpose":{"enum":["","THREAD","TICKET","NOTIFICATION"],"type":"string"},"middleware.ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/middleware.ErrorBody"}},"type":"object"},"middleware.ErrorBody":{"properties":{"code":{"description":"Code is the stable, machine-readable reason. Clients match on this, not on the message.","type":"string"},"details":{"description":"Details carries structured context where an error has any — a conflict names what it\ncollided with. Absent otherwise."},"message":{"type":"string"}},"type":"object"}}},"paths":{"/v1/threads/{threadId}/messages":{"get":{"description":"Returns the messages of a thread, ordered by `id` unless `sort` says otherwise.\n\n**Filterable** — `id`, `threadId`, `authorId`, `replyToId`, `direction`, `originChannelId`,\n`originMessageId`, `createdAt`, `updatedAt`, and paths into the author, such as `author.lastName`.\n\n**Sortable** — `id`, `threadId`, `createdAt`, `updatedAt`.\n\n**Searchable** — nothing yet.\n\n`direction` tells where a message came from or is going: `INTERNAL` never leaves Solvio, `INBOUND`\narrived from an external channel, `OUTBOUND` is delivered through the thread's channel.\n","operationId":"listThreadMessages","parameters":[{"description":"Thread id","in":"path","name":"threadId","required":true,"schema":{"type":"integer"}},{"description":"JSON filter expression","in":"query","name":"filter","schema":{"type":"string"}},{"description":"Free-text term — no effect here","in":"query","name":"search","schema":{"type":"string"}},{"description":"Comma-separated field:direction terms","in":"query","name":"sort","schema":{"type":"string"}},{"description":"Selection of fields and relations","in":"query","name":"fields","schema":{"type":"string"}},{"description":"Number of entries to skip","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"Page size, 1 to 100","in":"query","name":"limit","schema":{"default":100,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.ListThreadMessagesResponse-domain_Message"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"List the messages of a thread","tags":["message"]}}}}
```

## Post a message to a thread

> Posts a message to a thread. Requires write access, so an \`OBSERVER\` is answered with \`403\`.\
> \
> \`content\` is interpreted as plain text; the format can be changed afterwards when the message is\
> edited. It may be empty when \`attachmentIds\` is not, so a message can consist of attachments only;\
> send no placeholder text such as the file name. A message with neither visible text (whitespace and\
> empty markup do not count) nor attachments is answered with \`400\`. \`replyToId\` has to refer to a message of the same thread, and \`attachmentIds\`\
> refer to attachments uploaded beforehand.\
> \
> Whether the message stays internal or is delivered onward depends on the thread: a thread bound to a\
> communication channel produces an \`OUTBOUND\` message to its participants, a thread without one\
> produces an \`INTERNAL\` message.\
> \
> \`addressees\` records who the message is deliberately addressed to; it is never defaulted from the\
> thread's participants and defaults to empty when omitted. Every \`contactId\` has to belong to the\
> same tenant as the thread, \`role\` is one of \`DIRECT\` or \`COPIED\`, and a contact named twice is kept\
> once with the stronger of its two roles.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **message** is one entry in a thread's conversation. Messages carry rich text and any number of\nattachments, and they are what actually travels to the outside world when a thread is bound to a\ncommunication channel.\n\n`direction` says where a message stands in that flow:\n\n| Direction | Meaning |\n| --- | --- |\n| `INTERNAL` | never leaves Solvio |\n| `INBOUND` | arrived from an external channel |\n| `OUTBOUND` | is delivered through the thread's channel |\n\nPosting requires write access to the thread; editing is reserved for the message's own author.\n","name":"message"}],"servers":[{"description":"The host serving this document","url":"/api"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"description":"Token issued by the central equipme authentication, sent as `Bearer <token>`.","scheme":"bearer","type":"http"},"ApiKeyAuth":{"description":"Static key for machine-to-machine access. Alternative to BearerAuth.","in":"header","name":"X-API-Key","type":"apiKey"}},"schemas":{"v1.AddThreadMessageRequest":{"properties":{"addressees":{"items":{"$ref":"#/components/schemas/v1.AddThreadMessageAddressee"},"type":"array","uniqueItems":false},"attachmentIds":{"items":{"type":"integer"},"type":"array","uniqueItems":false},"content":{"type":"string"},"replyToId":{"type":"integer"}},"type":"object"},"v1.AddThreadMessageAddressee":{"properties":{"contactId":{"type":"integer"},"role":{"$ref":"#/components/schemas/domain.MessageAddresseeRole"}},"required":["contactId","role"],"type":"object"},"domain.MessageAddresseeRole":{"enum":["","DIRECT","COPIED"],"type":"string"},"v1.AddThreadMessageResponse":{"properties":{"data":{"$ref":"#/components/schemas/domain.Message"}},"type":"object"},"domain.Message":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"author":{"$ref":"#/components/schemas/domain.Contact"},"authorId":{"type":"integer"},"content":{"description":"Content is the message text; an empty string for a message that consists of attachments only.","type":"string"},"contentPreview":{"type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"direction":{"$ref":"#/components/schemas/domain.MessageDirection"},"id":{"type":"integer"},"originChannel":{"$ref":"#/components/schemas/domain.Channel"},"originChannelId":{"type":"integer"},"replyTo":{"$ref":"#/components/schemas/domain.Message"},"replyToId":{"type":"integer"},"threadId":{"description":"A message is only ever listed under its thread, so the way back is redundant\nthere and closes the thread→message→thread cycle in the schema. threadId\nidentifies it; \"?fields=thread\" still works.","type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.Attachment":{"properties":{"composing":{"description":"Composing marks the attachment as belonging to an active message composition session.\nThe term mirrors the \"compose\" state in messaging clients: the user is writing a message but has not sent it yet.\nOnce the message is sent, ClaimAttachments links these attachments to it and clears this flag.\nComposing attachments are excluded from all list queries and are eligible for periodic cleanup if never claimed.","type":"boolean"},"contentId":{"description":"ContentID is the MIME Content-ID value used in cid: URI references within the message body.","type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"id":{"type":"integer"},"inline":{"description":"Inline indicates that the attachment is meant to be displayed within the message content rather than as a separate downloadable file.","type":"boolean"},"link":{"description":"Link refers to the download URL for the attachment. Relative path for the API endpoint that requires authorization,\nnot a pre-signed URL or direct S3 link. This is generated on the fly and not stored in the DB.","type":"string"},"messageId":{"type":"integer"},"name":{"type":"string"},"size":{"type":"number"},"threadId":{"description":"An attachment is listed under the thread or message it belongs to, so the two\nways back are redundant there and close both cycles in the schema. The ids\nidentify them; selecting the relations still works.","type":"integer"}},"type":"object"},"domain.Contact":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext drive automated ticket routing. Set today only on\nGROUP-kind contacts via the group endpoints; nil/empty on ordinary contacts.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ContactKind"},"lastName":{"type":"string"},"mobile":{"type":"string"},"phone":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.ContactKind":{"enum":["","PERSON","INTAKE","GROUP"],"type":"string"},"domain.Tenant":{"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},"domain.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.Channel":{"properties":{"active":{"type":"boolean"},"createdAt":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ChannelKind"},"name":{"type":"string"},"purpose":{"$ref":"#/components/schemas/domain.ChannelPurpose"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.ChannelKind":{"enum":["","EMAIL","WEBHOOK","TEAMS","SMS","WHATSAPP","WEBCHAT","NOTICE"],"type":"string"},"domain.ChannelPurpose":{"enum":["","THREAD","TICKET","NOTIFICATION"],"type":"string"},"middleware.ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/middleware.ErrorBody"}},"type":"object"},"middleware.ErrorBody":{"properties":{"code":{"description":"Code is the stable, machine-readable reason. Clients match on this, not on the message.","type":"string"},"details":{"description":"Details carries structured context where an error has any — a conflict names what it\ncollided with. Absent otherwise."},"message":{"type":"string"}},"type":"object"}}},"paths":{"/v1/threads/{threadId}/messages":{"post":{"description":"Posts a message to a thread. Requires write access, so an `OBSERVER` is answered with `403`.\n\n`content` is interpreted as plain text; the format can be changed afterwards when the message is\nedited. It may be empty when `attachmentIds` is not, so a message can consist of attachments only;\nsend no placeholder text such as the file name. A message with neither visible text (whitespace and\nempty markup do not count) nor attachments is answered with `400`. `replyToId` has to refer to a message of the same thread, and `attachmentIds`\nrefer to attachments uploaded beforehand.\n\nWhether the message stays internal or is delivered onward depends on the thread: a thread bound to a\ncommunication channel produces an `OUTBOUND` message to its participants, a thread without one\nproduces an `INTERNAL` message.\n\n`addressees` records who the message is deliberately addressed to; it is never defaulted from the\nthread's participants and defaults to empty when omitted. Every `contactId` has to belong to the\nsame tenant as the thread, `role` is one of `DIRECT` or `COPIED`, and a contact named twice is kept\nonce with the stronger of its two roles.\n","operationId":"addThreadMessage","parameters":[{"description":"Thread id","in":"path","name":"threadId","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"type":"object"},{"$ref":"#/components/schemas/v1.AddThreadMessageRequest","summary":"request","description":"Message to post"}]}}},"description":"Message to post","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.AddThreadMessageResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Post a message to a thread","tags":["message"]}}}}
```

## Edit a message

> Edits a message. Only its \*\*author\*\* may do so — anyone else is answered with \`403\`, including a\
> thread owner.\
> \
> At least one of \`content\` and \`contentType\` has to be given, otherwise the request is answered with\
> \`400\`. \`contentType\` accepts \`text/plain\` and \`text/html\` and decides how \`content\` is rendered.\
> \`content\` may only become empty on a message that has attachments; otherwise the edit is answered\
> with \`400\`.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **message** is one entry in a thread's conversation. Messages carry rich text and any number of\nattachments, and they are what actually travels to the outside world when a thread is bound to a\ncommunication channel.\n\n`direction` says where a message stands in that flow:\n\n| Direction | Meaning |\n| --- | --- |\n| `INTERNAL` | never leaves Solvio |\n| `INBOUND` | arrived from an external channel |\n| `OUTBOUND` | is delivered through the thread's channel |\n\nPosting requires write access to the thread; editing is reserved for the message's own author.\n","name":"message"}],"servers":[{"description":"The host serving this document","url":"/api"}],"security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"BearerAuth":{"description":"Token issued by the central equipme authentication, sent as `Bearer <token>`.","scheme":"bearer","type":"http"},"ApiKeyAuth":{"description":"Static key for machine-to-machine access. Alternative to BearerAuth.","in":"header","name":"X-API-Key","type":"apiKey"}},"schemas":{"v1.PatchThreadMessageRequest":{"properties":{"content":{"type":"string"},"contentType":{"type":"string"}},"type":"object"},"v1.PatchThreadMessageResponse":{"properties":{"data":{"$ref":"#/components/schemas/domain.Message"}},"type":"object"},"domain.Message":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"author":{"$ref":"#/components/schemas/domain.Contact"},"authorId":{"type":"integer"},"content":{"description":"Content is the message text; an empty string for a message that consists of attachments only.","type":"string"},"contentPreview":{"type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"direction":{"$ref":"#/components/schemas/domain.MessageDirection"},"id":{"type":"integer"},"originChannel":{"$ref":"#/components/schemas/domain.Channel"},"originChannelId":{"type":"integer"},"replyTo":{"$ref":"#/components/schemas/domain.Message"},"replyToId":{"type":"integer"},"threadId":{"description":"A message is only ever listed under its thread, so the way back is redundant\nthere and closes the thread→message→thread cycle in the schema. threadId\nidentifies it; \"?fields=thread\" still works.","type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.Attachment":{"properties":{"composing":{"description":"Composing marks the attachment as belonging to an active message composition session.\nThe term mirrors the \"compose\" state in messaging clients: the user is writing a message but has not sent it yet.\nOnce the message is sent, ClaimAttachments links these attachments to it and clears this flag.\nComposing attachments are excluded from all list queries and are eligible for periodic cleanup if never claimed.","type":"boolean"},"contentId":{"description":"ContentID is the MIME Content-ID value used in cid: URI references within the message body.","type":"string"},"contentType":{"type":"string"},"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"id":{"type":"integer"},"inline":{"description":"Inline indicates that the attachment is meant to be displayed within the message content rather than as a separate downloadable file.","type":"boolean"},"link":{"description":"Link refers to the download URL for the attachment. Relative path for the API endpoint that requires authorization,\nnot a pre-signed URL or direct S3 link. This is generated on the fly and not stored in the DB.","type":"string"},"messageId":{"type":"integer"},"name":{"type":"string"},"size":{"type":"number"},"threadId":{"description":"An attachment is listed under the thread or message it belongs to, so the two\nways back are redundant there and close both cycles in the schema. The ids\nidentify them; selecting the relations still works.","type":"integer"}},"type":"object"},"domain.Contact":{"properties":{"assignmentContext":{"type":"string"},"autoAssign":{"description":"AutoAssign / AssignmentContext drive automated ticket routing. Set today only on\nGROUP-kind contacts via the group endpoints; nil/empty on ordinary contacts.","type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ContactKind"},"lastName":{"type":"string"},"mobile":{"type":"string"},"phone":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"title":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"}},"type":"object"},"domain.ContactKind":{"enum":["","PERSON","INTAKE","GROUP"],"type":"string"},"domain.Tenant":{"properties":{"id":{"type":"integer"},"name":{"type":"string"}},"type":"object"},"domain.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.Channel":{"properties":{"active":{"type":"boolean"},"createdAt":{"type":"string"},"id":{"type":"integer"},"kind":{"$ref":"#/components/schemas/domain.ChannelKind"},"name":{"type":"string"},"purpose":{"$ref":"#/components/schemas/domain.ChannelPurpose"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"type":"string"}},"type":"object"},"domain.ChannelKind":{"enum":["","EMAIL","WEBHOOK","TEAMS","SMS","WHATSAPP","WEBCHAT","NOTICE"],"type":"string"},"domain.ChannelPurpose":{"enum":["","THREAD","TICKET","NOTIFICATION"],"type":"string"},"middleware.ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/middleware.ErrorBody"}},"type":"object"},"middleware.ErrorBody":{"properties":{"code":{"description":"Code is the stable, machine-readable reason. Clients match on this, not on the message.","type":"string"},"details":{"description":"Details carries structured context where an error has any — a conflict names what it\ncollided with. Absent otherwise."},"message":{"type":"string"}},"type":"object"}}},"paths":{"/v1/threads/{threadId}/messages/{messageId}":{"patch":{"description":"Edits a message. Only its **author** may do so — anyone else is answered with `403`, including a\nthread owner.\n\nAt least one of `content` and `contentType` has to be given, otherwise the request is answered with\n`400`. `contentType` accepts `text/plain` and `text/html` and decides how `content` is rendered.\n`content` may only become empty on a message that has attachments; otherwise the edit is answered\nwith `400`.\n","operationId":"patchThreadMessage","parameters":[{"description":"Thread id","in":"path","name":"threadId","required":true,"schema":{"type":"integer"}},{"description":"Message id","in":"path","name":"messageId","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"type":"object"},{"$ref":"#/components/schemas/v1.PatchThreadMessageRequest","summary":"request","description":"Fields to change"}]}}},"description":"Fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.PatchThreadMessageResponse"}}},"description":"OK"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Bad Request"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/middleware.ErrorResponse"}}},"description":"Internal Server Error"}},"summary":"Edit a message","tags":["message"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://hub.equipme.io/development/api-reference-solvio-beta/message.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
