> 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/thread-link.md).

# Thread link

A **thread link** connects two threads and says how they relate. Links are directional and are always read from the perspective of the thread in the path: linking A to B as `DISCUSSES` means B shows the link as `DISCUSSED_IN`.

| Link         | Counterpart    |
| ------------ | -------------- |
| `DISCUSSES`  | `DISCUSSED_IN` |
| `RELATES_TO` | `RELATES_TO`   |

`DISCUSSES` is not inert: when the target thread is a ticket's main thread, the ticket's assignee and agent are added to the source thread as `CONTRIBUTOR` — never the ticket's contact. This is retroactive (they see the source thread's full history) and add-only: reassigning the ticket adds the new holder but does not remove the previous ones, and unlinking removes the relation but not the participants it added.

`RELATES_TO` never touches participants and is the right choice for a hand-picked subordinate conversation that should not pull anyone in automatically.

## List the threads linked to a thread

> Returns the threads linked to this one, each with the link type as seen \*\*from this thread\*\*.\
> Links to a thread the caller cannot read are omitted entirely, not just their content — the list\
> never reveals that such a link exists.\
> \
> \*\*Filterable\*\* — \`id\`, \`sourceThreadId\`, \`targetThreadId\`, \`relationType\`, \`creatorId\`, \`createdAt\`.\
> \
> \*\*Sortable\*\* — \`id\`, \`createdAt\`.\
> \
> \*\*Searchable\*\* — nothing yet.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **thread link** connects two threads and says how they relate. Links are directional and are\nalways read from the perspective of the thread in the path: linking A to B as `DISCUSSES` means B\nshows the link as `DISCUSSED_IN`.\n\n| Link | Counterpart |\n| --- | --- |\n| `DISCUSSES` | `DISCUSSED_IN` |\n| `RELATES_TO` | `RELATES_TO` |\n\n`DISCUSSES` is not inert: when the target thread is a ticket's main thread, the ticket's assignee\nand agent are added to the source thread as `CONTRIBUTOR` — never the ticket's contact. This is\nretroactive (they see the source thread's full history) and add-only: reassigning the ticket adds\nthe new holder but does not remove the previous ones, and unlinking removes the\nrelation but not the participants it added.\n\n`RELATES_TO` never touches participants and is the right choice for a hand-picked subordinate\nconversation that should not pull anyone in automatically.\n","name":"thread_link"}],"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.ListThreadLinksResponse-domain_ThreadLinkView":{"properties":{"count":{"type":"integer"},"data":{"items":{"$ref":"#/components/schemas/domain.ThreadLinkView"},"type":"array","uniqueItems":false}},"type":"object"},"domain.ThreadLinkView":{"properties":{"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"relatedThread":{"$ref":"#/components/schemas/domain.Thread"},"type":{"$ref":"#/components/schemas/domain.ThreadLinkType"}},"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.Thread":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"channel":{"$ref":"#/components/schemas/domain.Channel"},"channelId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"messages":{"items":{"$ref":"#/components/schemas/domain.Message"},"type":"array","uniqueItems":false},"participants":{"items":{"$ref":"#/components/schemas/domain.Contact"},"type":"array","uniqueItems":false},"resourceLinks":{"items":{"$ref":"#/components/schemas/domain.ResourceLink"},"type":"array","uniqueItems":false},"status":{"$ref":"#/components/schemas/domain.ThreadStatus"},"subject":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","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.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"},"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.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.ResourceLink":{"properties":{"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"deepLink":{"type":"string"},"displayName":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"provider":{"$ref":"#/components/schemas/domain.ResourceProvider"},"providerId":{"type":"integer"},"resourceType":{"type":"string"},"source":{"type":"string"},"threadId":{"type":"integer"}},"type":"object"},"domain.ResourceProvider":{"properties":{"id":{"type":"integer"},"key":{"type":"string"}},"type":"object"},"domain.ThreadStatus":{"enum":["","OPEN","CLOSED"],"type":"string"},"domain.ThreadLinkType":{"enum":["","DISCUSSES","DISCUSSED_IN","RELATES_TO"],"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}/links":{"get":{"description":"Returns the threads linked to this one, each with the link type as seen **from this thread**.\nLinks to a thread the caller cannot read are omitted entirely, not just their content — the list\nnever reveals that such a link exists.\n\n**Filterable** — `id`, `sourceThreadId`, `targetThreadId`, `relationType`, `creatorId`, `createdAt`.\n\n**Sortable** — `id`, `createdAt`.\n\n**Searchable** — nothing yet.\n","operationId":"listThreadLinks","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.ListThreadLinksResponse-domain_ThreadLinkView"}}},"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 threads linked to a thread","tags":["thread_link"]}}}}
```

## Link threads

> Links this thread to others. Each entry names the \`threadId\` to link and the \`type\` as seen from\
> the thread in the path — the counterpart is recorded automatically for the other side. The type\
> decides which of the two is the side conversation, and so which one gains the ticket's assignee\
> and agent: with \`DISCUSSES\` it is the thread in the path, with \`DISCUSSED\_IN\` the linked one.\
> \
> A thread cannot be linked to itself; that is answered with \`400\`, as is linking across tenants.\
> Linking a pair that is already linked, in any type or direction, answers \`409\`. Requires\
> \`ManageAction\` on the thread in the path — a ticket's own \`ManageAction\` also satisfies this when\
> the thread is that ticket's main thread.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **thread link** connects two threads and says how they relate. Links are directional and are\nalways read from the perspective of the thread in the path: linking A to B as `DISCUSSES` means B\nshows the link as `DISCUSSED_IN`.\n\n| Link | Counterpart |\n| --- | --- |\n| `DISCUSSES` | `DISCUSSED_IN` |\n| `RELATES_TO` | `RELATES_TO` |\n\n`DISCUSSES` is not inert: when the target thread is a ticket's main thread, the ticket's assignee\nand agent are added to the source thread as `CONTRIBUTOR` — never the ticket's contact. This is\nretroactive (they see the source thread's full history) and add-only: reassigning the ticket adds\nthe new holder but does not remove the previous ones, and unlinking removes the\nrelation but not the participants it added.\n\n`RELATES_TO` never touches participants and is the right choice for a hand-picked subordinate\nconversation that should not pull anyone in automatically.\n","name":"thread_link"}],"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.AddThreadLinksRequest":{"properties":{"links":{"items":{"$ref":"#/components/schemas/v1.AddThreadLinksRequestItem"},"minItems":1,"type":"array","uniqueItems":false}},"required":["links"],"type":"object"},"v1.AddThreadLinksRequestItem":{"properties":{"threadId":{"type":"integer"},"type":{"$ref":"#/components/schemas/domain.ThreadLinkType"}},"required":["threadId","type"],"type":"object"},"domain.ThreadLinkType":{"enum":["","DISCUSSES","DISCUSSED_IN","RELATES_TO"],"type":"string"},"v1.AddThreadLinksResponse-domain_ThreadLinkView":{"properties":{"data":{"items":{"$ref":"#/components/schemas/domain.ThreadLinkView"},"type":"array","uniqueItems":false}},"type":"object"},"domain.ThreadLinkView":{"properties":{"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"relatedThread":{"$ref":"#/components/schemas/domain.Thread"},"type":{"$ref":"#/components/schemas/domain.ThreadLinkType"}},"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.Thread":{"properties":{"attachments":{"items":{"$ref":"#/components/schemas/domain.Attachment"},"type":"array","uniqueItems":false},"channel":{"$ref":"#/components/schemas/domain.Channel"},"channelId":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"description":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"messages":{"items":{"$ref":"#/components/schemas/domain.Message"},"type":"array","uniqueItems":false},"participants":{"items":{"$ref":"#/components/schemas/domain.Contact"},"type":"array","uniqueItems":false},"resourceLinks":{"items":{"$ref":"#/components/schemas/domain.ResourceLink"},"type":"array","uniqueItems":false},"status":{"$ref":"#/components/schemas/domain.ThreadStatus"},"subject":{"type":"string"},"tenant":{"$ref":"#/components/schemas/domain.Tenant"},"tenantId":{"type":"integer"},"updatedAt":{"format":"date-time","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.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"},"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.MessageDirection":{"enum":["INTERNAL","INBOUND","OUTBOUND"],"type":"string"},"domain.ResourceLink":{"properties":{"createdAt":{"type":"string"},"creator":{"$ref":"#/components/schemas/domain.Contact"},"creatorId":{"type":"integer"},"deepLink":{"type":"string"},"displayName":{"type":"string"},"externalId":{"type":"string"},"id":{"type":"integer"},"provider":{"$ref":"#/components/schemas/domain.ResourceProvider"},"providerId":{"type":"integer"},"resourceType":{"type":"string"},"source":{"type":"string"},"threadId":{"type":"integer"}},"type":"object"},"domain.ResourceProvider":{"properties":{"id":{"type":"integer"},"key":{"type":"string"}},"type":"object"},"domain.ThreadStatus":{"enum":["","OPEN","CLOSED"],"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}/links":{"post":{"description":"Links this thread to others. Each entry names the `threadId` to link and the `type` as seen from\nthe thread in the path — the counterpart is recorded automatically for the other side. The type\ndecides which of the two is the side conversation, and so which one gains the ticket's assignee\nand agent: with `DISCUSSES` it is the thread in the path, with `DISCUSSED_IN` the linked one.\n\nA thread cannot be linked to itself; that is answered with `400`, as is linking across tenants.\nLinking a pair that is already linked, in any type or direction, answers `409`. Requires\n`ManageAction` on the thread in the path — a ticket's own `ManageAction` also satisfies this when\nthe thread is that ticket's main thread.\n","operationId":"addThreadLinks","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.AddThreadLinksRequest","summary":"request","description":"Links to add"}]}}},"description":"Links to add","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.AddThreadLinksResponse-domain_ThreadLinkView"}}},"description":"Created"},"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":"Link threads","tags":["thread_link"]}}}}
```

## Unlink threads

> Removes the links between this thread and the given ones, in whichever direction they were\
> recorded. Answers with \`204\` and no body.\
> \
> \*\*Topology only\*\*: unlinking does not remove participants a \`DISCUSSES\` link propagated, and does\
> not revoke their access. Reconciling propagated participants is not supported yet.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **thread link** connects two threads and says how they relate. Links are directional and are\nalways read from the perspective of the thread in the path: linking A to B as `DISCUSSES` means B\nshows the link as `DISCUSSED_IN`.\n\n| Link | Counterpart |\n| --- | --- |\n| `DISCUSSES` | `DISCUSSED_IN` |\n| `RELATES_TO` | `RELATES_TO` |\n\n`DISCUSSES` is not inert: when the target thread is a ticket's main thread, the ticket's assignee\nand agent are added to the source thread as `CONTRIBUTOR` — never the ticket's contact. This is\nretroactive (they see the source thread's full history) and add-only: reassigning the ticket adds\nthe new holder but does not remove the previous ones, and unlinking removes the\nrelation but not the participants it added.\n\n`RELATES_TO` never touches participants and is the right choice for a hand-picked subordinate\nconversation that should not pull anyone in automatically.\n","name":"thread_link"}],"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.RemoveThreadLinksRequest":{"properties":{"threadIds":{"items":{"type":"integer"},"minItems":1,"type":"array","uniqueItems":false}},"required":["threadIds"],"type":"object"},"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}/links":{"delete":{"description":"Removes the links between this thread and the given ones, in whichever direction they were\nrecorded. Answers with `204` and no body.\n\n**Topology only**: unlinking does not remove participants a `DISCUSSES` link propagated, and does\nnot revoke their access. Reconciling propagated participants is not supported yet.\n","operationId":"removeThreadLinks","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.RemoveThreadLinksRequest","summary":"request","description":"Threads to unlink"}]}}},"description":"Threads to unlink","required":true},"responses":{"204":{"description":"No Content"},"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":"Unlink threads","tags":["thread_link"]}}}}
```


---

# 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/thread-link.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.
