Skip to content

Typed Metadata Values and Attachments

Business object metadata properties (metadata on GET/PATCH /v1/twin/{contextId}/object/{objectId}/metadata) are not limited to plain strings, numbers, and booleans. A property value can also be a tagged object that carries a special _p3d_type field, used to represent an external link or an uploaded file attachment. This page documents that value union and the upload flow behind the attachment type.


Each property inside a metadata section is one of:

Value kindShape
Plain string"A short text"
Plain number42
Plain booleantrue
External link{ "url": "...", "text": "...", "_p3d_type": "urn:primitive:obj:external-link" }
Attachment{ "name": "...", "id": "...", "_p3d_type": "urn:primitive:obj:attachment" }

_p3d_type is the discriminator: its literal value tells you which of the two tagged shapes you are looking at. Both tagged shapes are otherwise plain objects, and no extra endpoint is needed to read them; they come back inline wherever metadata is returned.

{
"Section name": {
"name": "A short text",
"numeric value": 42,
"toggle name": true,
"documentation link": {
"url": "https://doc.prevu3d.com/",
"text": "https://doc.prevu3d.com/",
"_p3d_type": "urn:primitive:obj:external-link"
},
"landing image": {
"name": "prevu_logo.jpg",
"id": "80559c9a-39ac-4e22-af81-7d7395f45673",
"_p3d_type": "urn:primitive:obj:attachment"
}
}
}
Section titled “External link (urn:primitive:obj:external-link)”
FieldTypeNotes
urlstringThe link target.
textstringDisplay text for the link.
_p3d_typestringAlways urn:primitive:obj:external-link.

An external link is self-contained: set the object directly on a metadata write, no upload involved.

FieldTypeNotes
namestringOriginal file name.
idstringThe attachment’s fileId, obtained from the upload flow below.
_p3d_typestringAlways urn:primitive:obj:attachment.

Unlike the external link, an attachment value references a file that must exist first. Uploading it is a separate, multi-step call sequence, covered next.

Getting a file referenced as an attachment value is a three-step sequence: initiate the upload, upload the parts to S3, finalize it. Then use the returned file id as the id field of an attachment value on your next metadata write.

MethodPathScopePurpose
POST/v1/twin/{contextId}/object/attachmentswrite:assetInitiate an attachment upload
GET/v1/twin/{contextId}/object/attachments/{fileId}/refresh/{startIndex}write:assetReissue expired part URLs
POST/v1/twin/{contextId}/object/attachments/{fileId}/finalizewrite:assetFinalize the upload
GET/v1/twin/{contextId}/object/attachments/{fileId}/downloadread:assetGet a signed download URL
DELETE/v1/twin/{contextId}/object/attachments/{fileId}write:assetRemove an attachment

contextId is the twin, site, or draft the attachment belongs to, the same id you use for business object search.

The upload itself is the same generic initiate → upload parts → finalize mechanic used elsewhere in the API (multipart, presigned S3 URLs, refreshable if they expire). See the shared multipart upload reference for that mechanic and its part-numbering footgun. This section covers only what’s specific to attachments.

POST {api_url}/v1/twin/{contextId}/object/attachments
Authorization: Bearer {access_token}
Content-Type: application/json
{ "fileName": "prevu_logo.jpg", "fileSize": 84213, "mimeType": "image/jpeg" }
{
"fileId": "80559c9a-39ac-4e22-af81-7d7395f45673",
"parts": 1,
"urls": [
{ "partNumber": 1, "url": "https://s3…/part-1?…" }
]
}

PUT each chunk to its presigned url, keeping the returned ETag per part, exactly as described in the shared multipart upload reference.

POST {api_url}/v1/twin/{contextId}/object/attachments/{fileId}/finalize
Authorization: Bearer {access_token}
Content-Type: application/json
{ "parts": [ { "partNumber": 1, "etag": "\"a1b2c3d4e5f6\"" } ] }
{
"fileId": "80559c9a-39ac-4e22-af81-7d7395f45673",
"fileName": "prevu_logo.jpg",
"size": 84213
}

Take the fileId from the finalize response and set it as the id of an attachment value on your next metadata write:

PATCH {api_url}/v1/twin/{contextId}/object/{objectId}/metadata
Authorization: Bearer {access_token}
Content-Type: application/json
{
"metadata": {
"Section name": {
"landing image": {
"name": "prevu_logo.jpg",
"id": "80559c9a-39ac-4e22-af81-7d7395f45673",
"_p3d_type": "urn:primitive:obj:attachment"
}
}
}
}

A metadata write takes the full metadata object (new, updated, and removed sections and properties together), not a single-property patch, so include every section and property you want to keep alongside the new attachment reference.

GET /v1/twin/{contextId}/object/{objectId}/metadata returns each property exactly as it was written: a plain string/number/boolean, or one of the two tagged objects above with _p3d_type intact. There is no separate “resolve” step for reading a tagged value; the object itself, including the attachment’s id, comes back inline in the metadata response.

To get the file’s bytes, pass that id as fileId to the download endpoint below.

GET {api_url}/v1/twin/{contextId}/object/attachments/{fileId}/download
Authorization: Bearer {access_token}
{ "url": "https://d111111abcdef8.cloudfront.net/…" }

The returned URL is a signed CloudFront link, valid for roughly 4 hours. Fetch a fresh one if it expires.

To remove an attachment’s underlying file:

DELETE {api_url}/v1/twin/{contextId}/object/attachments/{fileId}
Authorization: Bearer {access_token}

Attachment storage rides on the same per-organization storage allocation as every other file in RealityConnect: there is no separate “attachment quota.”

QuestionAnswer
Maximum size of a single attachmentNo dedicated size cap is enforced. The practical ceiling is your organization’s remaining storage headroom.
Maximum attachments per objectNo dedicated count cap is enforced. A metadata section can hold any number of attachment-typed properties.
Total per twin or per organizationThere is no separate attachment quota. Every attachment is stored as a regular file and counts toward your organization’s overall storage allocation, the same counter every other uploaded file uses. StorageLimitExceeded (see Error Codes) is returned once usage crosses your organization’s allocated storage. That allocation is plan-specific, so there is no single number to publish here; check with your Prevu3D account team for your organization’s figure.
Accepted file typesAny mimeType you send is accepted; there is no allow-list to satisfy.
Same file attached to several objectsStored once per upload, not once per reference. Uploading identical bytes twice (two separate initiate → finalize sequences) creates two separate stored files; there is no automatic deduplication. To attach one shared document to several objects without storing it twice, upload it once and reuse the returned id in each object’s attachment value instead of re-uploading.