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

# Resource link

A **resource link** is a thread's reference to something that lives outside Solvio — an asset in an inventory system, an invoice, a directory entry. It is a pointer, not a copy: Solvio stores what to show and where to go, and never fetches the thing itself.

Links are flat and per-thread. The same external thing referenced from two threads is two independent rows, the way an attachment behaves; there is no shared registry row and no uniqueness constraint, so a thread may reference the same thing more than once.

A link carries a `displayName` and usually a `deepLink` — an `http`/`https` URL opened in a new tab, which requires the viewer's own login in the foreign system. It may additionally carry the identifying triple `source` + `resourceType` + `externalId`, which names the thing in its own system and makes the reverse lookup ("which threads reference this?") possible. The triple is set as a whole or not at all: a caller that cannot supply all three reliably supplies none, because a half-filled identity is findable by nobody.

**A link is visible to everyone who can read its thread**, not scoped to the external resource's own permissions. Keep `displayName` to low-entropy context; it does not belong to sensitive figures or state.

Reading them is part of reading the thread — select `resourceLinks` on a thread or a ticket's thread, there is no separate list endpoint.

## Link resources to a thread

> Adds links to the thread in the path. Each entry needs a \`displayName\` and then either a \`deepLink\`\
> or the complete \`source\` + \`resourceType\` + \`externalId\` triple — an entry with neither points at\
> nothing and is answered with \`400\`, as is a partial triple.\
> \
> \`deepLink\` must be an absolute \`http\` or \`https\` URL. Other schemes are rejected: the value is\
> rendered as a plain link, so anything else would run in the viewer's browser.\
> \
> Duplicates are allowed — the same thing may be linked to a thread more than once. Requires\
> \`WriteAction\` on the thread, the same right that attaching a file needs.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **resource link** is a thread's reference to something that lives outside Solvio — an asset in an\ninventory system, an invoice, a directory entry. It is a pointer, not a copy: Solvio stores what to\nshow and where to go, and never fetches the thing itself.\n\nLinks are flat and per-thread. The same external thing referenced from two threads is two\nindependent rows, the way an attachment behaves; there is no shared registry row and no uniqueness\nconstraint, so a thread may reference the same thing more than once.\n\nA link carries a `displayName` and usually a `deepLink` — an `http`/`https` URL opened in a new tab,\nwhich requires the viewer's own login in the foreign system. It may additionally carry the\nidentifying triple `source` + `resourceType` + `externalId`, which names the thing in its own\nsystem and makes the reverse lookup (\"which threads reference this?\") possible. The triple is set\nas a whole or not at all: a caller that cannot supply all three reliably supplies none, because a\nhalf-filled identity is findable by nobody.\n\n**A link is visible to everyone who can read its thread**, not scoped to the external resource's own\npermissions. Keep `displayName` to low-entropy context; it does not belong to sensitive figures or\nstate.\n\nReading them is part of reading the thread — select `resourceLinks` on a thread or a ticket's\nthread, there is no separate list endpoint.\n","name":"resource_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.AddResourceLinksRequest":{"properties":{"links":{"items":{"$ref":"#/components/schemas/v1.AddResourceLinksRequestItem"},"minItems":1,"type":"array","uniqueItems":false}},"required":["links"],"type":"object"},"v1.AddResourceLinksRequestItem":{"properties":{"deepLink":{"type":"string"},"displayName":{"type":"string"},"externalId":{"type":"string"},"resourceType":{"type":"string"},"source":{"type":"string"}},"required":["displayName"],"type":"object"},"v1.AddResourceLinksResponse-domain_ResourceLink":{"properties":{"data":{"items":{"$ref":"#/components/schemas/domain.ResourceLink"},"type":"array","uniqueItems":false}},"type":"object"},"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.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.ResourceProvider":{"properties":{"id":{"type":"integer"},"key":{"type":"string"}},"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}/resource-links":{"post":{"description":"Adds links to the thread in the path. Each entry needs a `displayName` and then either a `deepLink`\nor the complete `source` + `resourceType` + `externalId` triple — an entry with neither points at\nnothing and is answered with `400`, as is a partial triple.\n\n`deepLink` must be an absolute `http` or `https` URL. Other schemes are rejected: the value is\nrendered as a plain link, so anything else would run in the viewer's browser.\n\nDuplicates are allowed — the same thing may be linked to a thread more than once. Requires\n`WriteAction` on the thread, the same right that attaching a file needs.\n","operationId":"addResourceLinks","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.AddResourceLinksRequest","summary":"request","description":"Links to add"}]}}},"description":"Links to add","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.AddResourceLinksResponse-domain_ResourceLink"}}},"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 resources to a thread","tags":["resource_link"]}}}}
```

## Unlink resources from a thread

> Removes the given links from the thread in the path. Answers with \`204\` and no body.\
> \
> All ids must belong to this thread; if any does not, nothing is removed and the answer is \`404\`.\
> Requires \`WriteAction\` on the thread.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **resource link** is a thread's reference to something that lives outside Solvio — an asset in an\ninventory system, an invoice, a directory entry. It is a pointer, not a copy: Solvio stores what to\nshow and where to go, and never fetches the thing itself.\n\nLinks are flat and per-thread. The same external thing referenced from two threads is two\nindependent rows, the way an attachment behaves; there is no shared registry row and no uniqueness\nconstraint, so a thread may reference the same thing more than once.\n\nA link carries a `displayName` and usually a `deepLink` — an `http`/`https` URL opened in a new tab,\nwhich requires the viewer's own login in the foreign system. It may additionally carry the\nidentifying triple `source` + `resourceType` + `externalId`, which names the thing in its own\nsystem and makes the reverse lookup (\"which threads reference this?\") possible. The triple is set\nas a whole or not at all: a caller that cannot supply all three reliably supplies none, because a\nhalf-filled identity is findable by nobody.\n\n**A link is visible to everyone who can read its thread**, not scoped to the external resource's own\npermissions. Keep `displayName` to low-entropy context; it does not belong to sensitive figures or\nstate.\n\nReading them is part of reading the thread — select `resourceLinks` on a thread or a ticket's\nthread, there is no separate list endpoint.\n","name":"resource_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.RemoveResourceLinksRequest":{"properties":{"ids":{"items":{"type":"integer"},"minItems":1,"type":"array","uniqueItems":false}},"required":["ids"],"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}/resource-links":{"delete":{"description":"Removes the given links from the thread in the path. Answers with `204` and no body.\n\nAll ids must belong to this thread; if any does not, nothing is removed and the answer is `404`.\nRequires `WriteAction` on the thread.\n","operationId":"removeResourceLinks","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.RemoveResourceLinksRequest","summary":"request","description":"Links to remove"}]}}},"description":"Links to remove","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 resources from a thread","tags":["resource_link"]}}}}
```

## Change a resource link

> Changes a link's descriptive fields. Only \`displayName\` and \`deepLink\` can be patched; \`source\`,\
> \`resourceType\` and \`externalId\` are immutable, so a value an external caller already looked up by\
> cannot silently go stale — fix a wrong one by unlinking and relinking.\
> \
> The invariants are re-checked against the result, not the request: clearing the \`deepLink\` of a link\
> that carries no identifying triple is answered with \`400\`, as is an empty \`displayName\`. A link that\
> belongs to another thread is answered with \`404\`. Requires \`WriteAction\` on the thread.<br>

```json
{"openapi":"3.1.0","info":{"title":"Solvio REST API","version":"1.0.0-beta"},"tags":[{"description":"A **resource link** is a thread's reference to something that lives outside Solvio — an asset in an\ninventory system, an invoice, a directory entry. It is a pointer, not a copy: Solvio stores what to\nshow and where to go, and never fetches the thing itself.\n\nLinks are flat and per-thread. The same external thing referenced from two threads is two\nindependent rows, the way an attachment behaves; there is no shared registry row and no uniqueness\nconstraint, so a thread may reference the same thing more than once.\n\nA link carries a `displayName` and usually a `deepLink` — an `http`/`https` URL opened in a new tab,\nwhich requires the viewer's own login in the foreign system. It may additionally carry the\nidentifying triple `source` + `resourceType` + `externalId`, which names the thing in its own\nsystem and makes the reverse lookup (\"which threads reference this?\") possible. The triple is set\nas a whole or not at all: a caller that cannot supply all three reliably supplies none, because a\nhalf-filled identity is findable by nobody.\n\n**A link is visible to everyone who can read its thread**, not scoped to the external resource's own\npermissions. Keep `displayName` to low-entropy context; it does not belong to sensitive figures or\nstate.\n\nReading them is part of reading the thread — select `resourceLinks` on a thread or a ticket's\nthread, there is no separate list endpoint.\n","name":"resource_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.PatchResourceLinkRequest":{"properties":{"deepLink":{"type":"string"},"displayName":{"type":"string"}},"type":"object"},"v1.PatchResourceLinkResponse":{"properties":{"data":{"$ref":"#/components/schemas/domain.ResourceLink"}},"type":"object"},"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.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.ResourceProvider":{"properties":{"id":{"type":"integer"},"key":{"type":"string"}},"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}/resource-links/{linkId}":{"patch":{"description":"Changes a link's descriptive fields. Only `displayName` and `deepLink` can be patched; `source`,\n`resourceType` and `externalId` are immutable, so a value an external caller already looked up by\ncannot silently go stale — fix a wrong one by unlinking and relinking.\n\nThe invariants are re-checked against the result, not the request: clearing the `deepLink` of a link\nthat carries no identifying triple is answered with `400`, as is an empty `displayName`. A link that\nbelongs to another thread is answered with `404`. Requires `WriteAction` on the thread.\n","operationId":"patchResourceLink","parameters":[{"description":"Thread id","in":"path","name":"threadId","required":true,"schema":{"type":"integer"}},{"description":"Resource link id","in":"path","name":"linkId","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"type":"object"},{"$ref":"#/components/schemas/v1.PatchResourceLinkRequest","summary":"request","description":"Fields to change"}]}}},"description":"Fields to change","required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1.PatchResourceLinkResponse"}}},"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":"Change a resource link","tags":["resource_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/resource-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.
