Ressurs
Document
Filer knyttet til en person, et firma eller et prosjekt. Opplasting og nedlasting med multipart, endring av metadata, utbytting av innholdet, og flytting mellom mapper.
Et dokument er en lagret fil pluss metadataene rundt den: et navn som inneholder filendelsen, en MIME-type, en størrelse i byte, en valgfri fritekstbeskrivelse, og en plassering i mappetreet til den eiende posten. Hvert dokument hører til én eiende enhet — en Company, en Person eller et Project — og entityType+entityId identifiserer den. Det finnes ingen dokumentliste for hele organisasjonen: hver lesing er avgrenset til én eier.
Det finnes ingen opprettelse uten innhold. POST /document er en multipart/form-data-opplasting som skriver filen og relasjonen i ett kall, og PUT /document/{documentId}/content bytter ut filen på plass. Metadata-endepunktene (erstatt, patch) rører aldri bytene, og bytene kommer bare tilbake fra GET /document/{documentId}/content, som strømmer dem som et vedlegg og ikke som JSON.
Plassering og eierskap er to forskjellige ting. PUT /document/{documentId}/folder flytter et dokument mellom mapper inne i den eiende enheten; ingenting her flytter et dokument til en annen eier. Ett dokument kan ha flere plasseringer — en hovedkopi pluss snarveier på andre poster — og det er derfor slettingen er relasjonsbevisst og ber om bekreftelse før den fjerner noe som følger med.
Endepunktreferansen under står på engelsk i begge språktrærne. Den gjengir API-kontrakten ordrett, og det finnes ingen generator å bygge den på nytt fra, så en oversatt kopi ville drevet fra API-et første gang et endepunkt endres.
Endepunkter
Elleve endepunkter, i samme rekkefølge som de er dokumentert nedenfor.
- POST
/document— Upload a document - GET
/document/{documentId}— Get a document - GET
/document/{documentId}/content— Download a document's content - GET
/document— List documents (offset-paginated) - GET
/document/cursor— List documents (cursor-paginated, insert-stable) - GET
/document/timeline— List documents (time-windowed, cursor-paginated) - PUT
/document/{documentId}— Replace a document's metadata - PATCH
/document/{documentId}— Partially update a document (RFC 7396 JSON Merge Patch) - PUT
/document/{documentId}/content— Replace a document's content - PUT
/document/{documentId}/folder— Move a document to another folder - DELETE
/document/{documentId}— Delete a document (relation-aware)
Alle endepunktene på denne siden kan i tillegg svare med fellesfeilene — 401, 403, 404 (ukjent instans) og 500. De er dokumentert én gang, under Feil og feilsøking.
To nabofamilier av ruter er dokumentert på egne sider: de seks /document/folder-endepunktene som bygger og river ned mappetreet står på Document folder, og den registrerte endringsstrømmen — den eneste lesestien der en sletting er synlig — står på Document changes.
Upload a document
POST /{instanceName}/api/v1/{locale}/document
Multipart create: writes the uploaded blob and relates it to the resolved owning entity (entityType/entityId). 201 with a Location header. There is no content-free create.
onConflict is applied when a folderId is supplied and that folder already holds a document of the same name: skip leaves the existing document untouched and returns it, creating nothing; replace (the default) overwrites the existing document's content in place, preserving its id and its placements; keepAll creates a second document under a name de-duplicated against the names already in that folder. With no folderId, or no same-name document, all three behave alike. Concurrent creates of the same name in one folder are not serialised — a rare duplicate is possible.
created (ISO-8601) and createdBy (a masked person id) set the creation attribution of a restored document; omitting either defaults it to now / the calling credential's person. Both apply ONLY when a document is actually created: the skip and replace conflict branches create no document and ignore both values. A backdated created also backdates the document's position on GET /document/timeline, so it can land behind a watermark a consumer has already passed and never be seen there. The supplied created is likewise carried as the change timestamp of the recorded creation event on GET /document/changes, so that event is backdated too; only the feed's ordering is unaffected, because that ordering is the insertion sequence and never the timestamp, which is why the feed's cursor is on insertion order and its reported changed timestamp is never a resumption watermark.
Forespørselskropp
multipart/form-data → DocumentCreateForm
Multipart form: the file part is the uploaded blob; the remaining parts are the non-file create fields.
Svar
| Status | Beskrivelse |
|---|---|
| 201 | Document created. DocumentResponseDto · application/json |
| 400 | Missing/empty file, unrecognised entityType/onConflict, or the id is not a valid id. |
| 422 | The referenced entityId does not exist for the given entityType, or the supplied createdBy is not an existing person. |
Eksempel
curl -X POST "https://api.visena.example/acme/api/v1/en/document" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@contract.pdf" \
-F "entityType=Company" \
-F "entityId=bQ4wR8"
Get a document
GET /{instanceName}/api/v1/{locale}/document/{documentId}
Returns the full entity. 404 if unknown; 400 if the id cannot be unmasked.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
documentId required |
path | string |
Masked document id. |
Svar
| Status | Beskrivelse |
|---|---|
| 200 | The document. DocumentResponseDto · application/json |
| 400 | The id is not a valid id. |
| 404 | No document with that id. |
Eksempel
curl "https://api.visena.example/acme/api/v1/en/document/dN3pT7" \
-H "Authorization: Bearer $TOKEN"
Download a document's content
GET /{instanceName}/api/v1/{locale}/document/{documentId}/content
Streams the stored blob as an attachment. Content-Type is the document's stored MIME type, falling back to application/octet-stream when it was never recorded or is unparseable; the file name is carried in Content-Disposition (RFC 5987-encoded), not in the path. Same authorization boundary as getDocument — a caller who cannot read the metadata cannot read the bytes.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
documentId required |
path | string |
Masked document id. |
Svar
| Status | Beskrivelse |
|---|---|
| 200 | The document's content, streamed.file · application/octet-stream |
| 400 | The id is not a valid id. |
| 404 | No document with that id. |
Eksempel
curl "https://api.visena.example/acme/api/v1/en/document/dN3pT7/content" \
-H "Authorization: Bearer $TOKEN"
List documents (offset-paginated)
GET /{instanceName}/api/v1/{locale}/document
Offset/limit paging with a real total. entityType+entityId are REQUIRED (documents are always listed scoped to one owning entity — no org-wide enumeration); optional query (full-text search); and a name/created/modified/modifiedOrCreated sort. RFC 8288 navigation links are in the body.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
offset |
query | integerdefault 0 |
Zero-based item offset. |
limit |
query | integer |
Maximum items to return per page. |
query |
query | string |
Free-text filter; the endpoint description lists the fields it matches. |
entityType required |
query | string, one of Company Person Project |
Owning entity type. |
entityId required |
query | string |
Masked id of the owning record. |
sort |
query | string |
Sort key; the endpoint description lists the allowed values. |
Svar
| Status | Beskrivelse |
|---|---|
| 200 | A page of documents with totals and navigation links. DocumentOffsetListResponse · application/json |
| 400 | Missing/unresolved required entityType/entityId, invalid paging or sort parameter, or a filter id is not a valid id. |
Eksempel
curl "https://api.visena.example/acme/api/v1/en/document?entityType=Company&entityId=bQ4wR8" \
-H "Authorization: Bearer $TOKEN"
List documents (cursor-paginated, insert-stable)
GET /{instanceName}/api/v1/{locale}/document/cursor
Keyset paging over the monotonic entity id (insertion order). Pass the opaque cursor from a previous page's nextCursor/prevCursor; omit it for the first page. No total — keyset skips the count. entityType+entityId are REQUIRED (documents are always listed scoped to one owning entity); optional query.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
cursor |
query | string |
Opaque page cursor from the previous response's links.next; omit for the first page. |
limit |
query | integer |
Maximum items to return per page. |
query |
query | string |
Free-text filter; the endpoint description lists the fields it matches. |
entityType required |
query | string, one of Company Person Project |
Owning entity type. |
entityId required |
query | string |
Masked id of the owning record. |
Svar
| Status | Beskrivelse |
|---|---|
| 200 | A page of documents with next/prev cursors and links. DocumentCursorListResponse · application/json |
| 400 | Missing/unresolved required entityType/entityId, invalid or expired cursor, invalid limit, or a filter id is not a valid id. |
Eksempel
curl "https://api.visena.example/acme/api/v1/en/document/cursor?entityType=Company&entityId=bQ4wR8&limit=100" \
-H "Authorization: Bearer $TOKEN"
List documents (time-windowed, cursor-paginated)
GET /{instanceName}/api/v1/{locale}/document/timeline
Keyset paging from an optional since lower bound on the single timeline key coalesce(modified, created) — Document has no client-selectable key, unlike Company/Project. entityType+entityId are REQUIRED (documents are always listed scoped to one owning entity). RFC 9557 Z timestamps. A cursor minted by the plain cursor endpoint is rejected with 400.
Sync contract: this feed is upsert-only and at-least-once, ordered on coalesce(modified, created), so creation, archival and renaming are the only writes it reports — renaming is the sole partner-reachable write the platform stamps modified on. Folder moves, content replacement, description-only PATCHes and folder renames/reparents are unreported: they leave the row in place. Deletions never appear here at all and are observable only via GET /document/changes.
Consumer contract: since is inclusive, and cursor timestamps are millisecond-truncated relative to the stored precision, so a boundary row is re-served rather than skipped — overlap the window and de-duplicate by document id. A cross-owner relocation is never a move: it shows only its gaining side here (as a creation in the gaining owner's timeline), while the losing side is visible only as a deletion on GET /document/changes. Full synchronisation therefore needs three sources — this timeline for upserts, GET /document/changes for deletions, and a periodic per-owner snapshot diff for placement, content and description changes. That snapshot diff is the consumer's obligation, not a feature of this API.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
since |
query | string (date-time) |
Inclusive lower bound — an RFC 3339 timestamp. |
cursor |
query | string |
Opaque page cursor from the previous response's links.next; omit for the first page. |
limit |
query | integer |
Maximum items to return per page. |
query |
query | string |
Free-text filter; the endpoint description lists the fields it matches. |
entityType required |
query | string, one of Company Person Project |
Owning entity type. |
entityId required |
query | string |
Masked id of the owning record. |
Svar
| Status | Beskrivelse |
|---|---|
| 200 | A page of documents with next/prev cursors and links. DocumentCursorListResponse · application/json |
| 400 | Missing/unresolved required entityType/entityId, invalid timestamp, cursor, limit, or a filter id is not a valid id. |
Eksempel
curl "https://api.visena.example/acme/api/v1/en/document/timeline?entityType=Company&entityId=bQ4wR8&since=2026-08-20T02:00:00Z" \
-H "Authorization: Bearer $TOKEN"
Replace a document's metadata
PUT /{instanceName}/api/v1/{locale}/document/{documentId}
Full replace (PUT) of name/description: an omitted description clears the stored value.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
documentId required |
path | string |
Masked document id. |
Forespørselskropp
application/json → DocumentUpdateDto
Svar
| Status | Beskrivelse |
|---|---|
| 200 | The replaced document. DocumentResponseDto · application/json |
| 400 | Request body failed validation, or the id is not a valid id. |
| 404 | No document with that id. |
Eksempel
curl -X PUT "https://api.visena.example/acme/api/v1/en/document/dN3pT7" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "contract-signed.pdf",
"description": "Signed version"
}'
Partially update a document (RFC 7396 JSON Merge Patch)
PATCH /{instanceName}/api/v1/{locale}/document/{documentId}
Absent fields are unchanged, JSON null clears (name cannot be cleared), a value sets. An empty body {} is a no-op. Wrong content type → 415.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
documentId required |
path | string |
Masked document id. |
Forespørselskropp
application/merge-patch+json → object
Svar
| Status | Beskrivelse |
|---|---|
| 200 | The updated document. DocumentResponseDto · application/json |
| 400 | A patched field had an invalid value (e.g. blank/cleared name), or the id is not a valid id. |
| 404 | No document with that id. |
| 415 | Content-Type was not application/merge-patch+json. |
Eksempel
curl -X PATCH "https://api.visena.example/acme/api/v1/en/document/dN3pT7" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/merge-patch+json" \
-d '{
"description": "Signed version"
}'
Replace a document's content
PUT /{instanceName}/api/v1/{locale}/document/{documentId}/content
Multipart PUT: rewrites the stored blob in place, preserving the document id and its relations.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
documentId required |
path | string |
Masked document id. |
Forespørselskropp
multipart/form-data → DocumentContentReplaceForm
Multipart form: the file part is the replacement blob.
Svar
| Status | Beskrivelse |
|---|---|
| 200 | The document, with the replaced content. DocumentResponseDto · application/json |
| 400 | Missing/empty file, or the id is not a valid id. |
| 404 | No document with that id. |
Eksempel
curl -X PUT "https://api.visena.example/acme/api/v1/en/document/dN3pT7/content" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@contract-v2.pdf"
Move a document to another folder
PUT /{instanceName}/api/v1/{locale}/document/{documentId}/folder
Changes this document's folder placement within its owning entity. A null folderId places it at top level. The destination must belong to the same owning entity — moving a document to another entity is not a move; archive a copy or a shortcut instead. Both the document and the destination folder are resolved only within the declared entity, so either one belonging to another entity is reported as 404 rather than confirming that the id exists elsewhere. A destination that already holds a placement of the same document is rejected with 409. Moving does not count as a modification: modified/modifiedBy are left exactly as the platform recorded them.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
documentId required |
path | string |
Masked document id. |
entityType required |
query | string, one of Company Person Project |
Owning entity type. |
entityId required |
query | string |
Masked id of the owning record. |
Forespørselskropp
application/json → DocumentPlacementDto
Svar
| Status | Beskrivelse |
|---|---|
| 200 | The document, with its new placement. DocumentResponseDto · application/json |
| 400 | An id is not a valid id. |
| 404 | No document with that id, or no folder with that folderId. |
| 409 | The destination folder already holds a placement of this document. |
| 422 | The document is not archived in a folder, so there is no placement to move. |
Eksempel
curl -X PUT "https://api.visena.example/acme/api/v1/en/document/dN3pT7/folder" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"folderId": "fR5sK1"
}'
Delete a document (relation-aware)
DELETE /{instanceName}/api/v1/{locale}/document/{documentId}
Hard delete: no soft-delete/isActive column. When entityType+entityId are supplied, only that relation is removed; otherwise every relation is removed. The document itself is deleted once it is left orphaned (no remaining relations). 204 on success.
A delete that would cascade — no entityType/entityId, on a document holding shortcut placements on other entities — is refused with 409 until acknowledged, and deletes NOTHING when refused. The 409 states how many master documents and shortcut placements would go; re-send with acknowledgeCascade=true to proceed. The targeted (entityType+entityId) form removes exactly one placement, cannot cascade, and is never gated.
Parametere
| Parameter | Plassering | Type | Beskrivelse |
|---|---|---|---|
documentId required |
path | string |
Masked document id. |
entityType |
query | string, one of Company Person Project |
Owning entity type. |
entityId |
query | string |
Masked id of the owning record. |
acknowledgeCascade |
query | booleandefault false |
Confirm a delete whose extent goes beyond the targeted placement; see the endpoint description. |
Svar
| Status | Beskrivelse |
|---|---|
| 204 | The relation(s) — and the document, if orphaned — were removed; no body. |
| 400 | entityType and entityId were not both provided together, or an id is not a valid id. |
| 404 | No document with that id, or no matching relation. |
| 409 | The delete would cascade to shortcut placements and was not acknowledged; nothing was deleted. detail states how many placements would go. |
Eksempel
curl -X DELETE "https://api.visena.example/acme/api/v1/en/document/dN3pT7" \
-H "Authorization: Bearer $TOKEN"
Objektstrukturer
Forespørsels- og svarkroppene som brukes over, i den rekkefølgen de først refereres.
DocumentCreateForm
| Felt | Type | Beskrivelse |
|---|---|---|
file required |
file |
The uploaded file content. |
entityType required |
string, one of Company Person Project |
|
entityId required |
string (masked id) |
Masked id of the owning record. |
folderId |
string (masked id) |
Target folder within the owner's archive. Optional; without it the document is related to the owner but not archived into a folder. |
onConflict |
string, one of replace skip keepAll |
|
created |
string (date-time) |
Audit override: creation timestamp to attribute (ISO-8601). Applied only when a document is actually created. |
createdBy |
string (masked id) |
Audit override: masked person id to attribute the creation to. Applied only when a document is actually created. |
DocumentResponseDto
| Felt | Type | Beskrivelse |
|---|---|---|
id |
string (masked id) |
|
name |
string |
Document display name (includes the file suffix). |
suffix |
string |
File suffix (extension), if any. |
mimeType |
string |
MIME type of the stored blob, if known. |
size |
integer |
Blob size in bytes. |
created |
string (date-time) |
Creation timestamp (UTC). |
createdBy |
PersonRefDto | |
modified |
string (date-time) |
Last-modification timestamp (UTC); null until first modified. |
modifiedBy |
PersonRefDto | |
description |
string |
Free-text description, if set. |
folderId |
string (masked id) |
|
folderPath read-only |
string |
Slash-joined folder path of the placement; empty at top level. |
actedBy |
string (masked id) |
PersonRefDto
Last modifier of the document, if any.
| Felt | Type | Beskrivelse |
|---|---|---|
id |
string (masked id) |
|
name |
string |
Person display name. |
DocumentOffsetListResponse
| Felt | Type | Beskrivelse |
|---|---|---|
totalItems |
integer |
Total number of matching documents. |
totalPages |
integer |
Total number of pages at the current page size. |
page |
integer |
Zero-based index of the current page. |
size |
object |
Page size actually applied. |
items |
array of DocumentListItemDto | The documents on this page. |
links |
DocumentListLinks |
DocumentListItemDto
The documents on this page, in ascending key order.
| Felt | Type | Beskrivelse |
|---|---|---|
id |
string (masked id) |
|
name |
string |
Document display name (includes the file suffix). |
suffix |
string |
File suffix (extension), if any. |
mimeType |
string |
MIME type of the stored blob, if known. |
size |
integer |
Blob size in bytes. |
created |
string (date-time) |
Creation timestamp (UTC). |
createdBy |
PersonRefDto | |
modified |
string (date-time) |
Last-modification timestamp (UTC); null until first modified. |
modifiedBy |
PersonRefDto |
DocumentListLinks
RFC 8288 navigation links: self, and next when more events remain.
| Felt | Type | Beskrivelse |
|---|---|---|
self |
string |
URL of the current page. |
first |
string |
URL of the first page (offset endpoint). |
prev |
string |
URL of the previous page, if any. |
next |
string |
URL of the next page, if any. |
last |
string |
URL of the last page (offset endpoint). |
DocumentCursorListResponse
| Felt | Type | Beskrivelse |
|---|---|---|
items |
array of DocumentListItemDto | The documents on this page, in ascending key order. |
nextCursor |
string |
Opaque cursor for the next page; absent on the last page. |
prevCursor |
string |
Opaque cursor for the previous page; absent on the first page. |
links |
DocumentListLinks |
DocumentUpdateDto
| Felt | Type | Beskrivelse |
|---|---|---|
name required |
string |
Document display name. Required. |
description |
string |
Free-text description. Omitted on a replace clears the stored value. |
DocumentContentReplaceForm
| Felt | Type | Beskrivelse |
|---|---|---|
file required |
file |
The replacement file content. |
onConflict |
string, one of replace skip keepAll |
DocumentPlacementDto
| Felt | Type | Beskrivelse |
|---|---|---|
folderId |
string (masked id) |