Assets API
The Assets API manages the files in the Runnit asset library: uploading, reading, downloading, updating with automatic version history, deleting, and organising with entity links, tags, and folders.
Assets always live inside a collection. Create or look up a collection with the Asset Collections API first, then upload into it.
Authentication: every endpoint requires an API key. See API Keys & Authentication.
Permissions: asset endpoints check ownership rather than role. Your key’s
organisation must own the asset (or, for reads scoped to a client
organisation, have an active client relationship with it). Requests outside
that boundary return 403.
AI transformations of an asset (POST /api/v1/assets/:assetId/edit and
POST /api/v1/assets/:assetId/convert-to-video) start generation jobs and are
covered by Image Generation and
Video Generation.
The asset object
Section titled “The asset object”{ "id": "5595c819-...", "fileName": "style-guide", "fileType": "markdown", "fileExtension": "md", "mimeType": "text/markdown", "isBinary": false, "isRemoteUrl": false, "storageProvider": "aws", "storageKey": "assets/ddf75d91-.../d5df4edc-.../style-guide", "fileSize": 32, "description": "Tone of voice and formatting rules.", "metadata": {}, "createdByAgentType": null, "createdByUserId": "213ae491-...", "createdByUserName": "Mark Tolson", "updatedByAgentType": null, "createdAt": "2026-07-08T02:31:50.117Z", "updatedAt": "2026-07-08T02:31:58.598Z", "version": 1, "owningOrganizationId": "ddf75d91-...", "visibilityScope": "organization", "sourceType": "upload", "isArchived": false, "summaryExcerpt": "A short AI summary of the content.", "searchStatus": "ready", "folderPath": null, "lastIndexedAt": "2026-07-08T12:31:58.597Z", "folderIds": [], "storageUrl": "https://...", "url": "https://..."}fileTypeis derived from the extension:markdown,html,text,image,video,audio,json,csv,pdf, orother.searchStatusispending,processing,ready, orfailedand tracks content indexing for search and insights.contentis included when the endpoint returns file content (create, update, and single-asset reads). For binary files it is base64.storageUrlandurlappear when the file is held in cloud storage.entityLinksappears when you ask for links.
POST /api/v1/assets
Section titled “POST /api/v1/assets”Upload a file. Send a JSON body; for binary files (images, PDFs, Office
documents, media) put the base64-encoded bytes in content.
Give the file a home in one of two ways:
collectionId(preferred): the asset is stored in that collection.entityLinks(legacy): Runnit finds or creates a suitable collection for the linked job, project, or organisation and stores the asset there.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| fileName | string | Yes | File name. |
| fileExtension | string | Yes | Extension without meaning-changing tricks, e.g. md, png, pdf. Determines the file type and whether content is treated as binary. |
| content | string | Yes | File content. Plain text for text formats, base64 for binary formats, or a URL when isRemoteUrl is true. |
| collectionId | UUID | One of the two homes | Collection to store the asset in. |
| entityLinks | array | One of the two homes | Objects with entityType (job, organization, or project) and entityId. |
| description | string | No | Description stored on the asset. |
| metadata | object | No | Free-form metadata. |
| createdByAgentType | string | No | Label recording which automation produced the file. |
| isRemoteUrl | boolean | No | true when content is a URL to an externally hosted file. Defaults to false. |
| owningOrganizationId | UUID | No | Owning organisation. Normally derived from the collection or links. |
| visibilityScope | string | No | organization, client, public, or private. |
| sourceType | string | No | Where the file came from, e.g. upload. |
| externalSource | string | No | Name of an external system of record. |
| externalId | string | No | ID in that external system. |
| isArchived | boolean | No | Create the asset already archived. |
| tagIds | array of UUIDs | No | Tags to apply immediately. |
| folderIds | array of UUIDs | No | Folders to place the asset in. |
| primaryFolderId | UUID | No | Which of folderIds is the primary folder. |
Text uploads are rejected when the content looks like a placeholder (for
example just placeholder, TBD, or [CONTENT HERE]). Send the real,
final content.
Example request
curl -X POST https://api.runnit.io/api/v1/assets \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "fileName": "style-guide", "fileExtension": "md", "content": "# Style guide\n\nUse sentence case.", "collectionId": "<collection-id>", "description": "Tone of voice and formatting rules." }'Response
Returns 201 with the full asset, including content. The upload is also
recorded as version 1 in the asset’s version history, and content indexing is
queued (searchStatus starts as pending).
{ "success": true, "asset": { "id": "5595c819-...", "fileName": "style-guide", "version": 1 } }Errors
400if a required field is missing or invalid, if neithercollectionIdnorentityLinksis given, or if the referenced collection does not exist.400with errorplaceholder_contentif text content looks like a placeholder.403if you do not have access to the collection, a linked entity, or the owning organisation.
GET /api/v1/assets
Section titled “GET /api/v1/assets”List assets in a collection, or the assets linked to an entity. Provide
collectionId, or both entityType and entityId.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| collectionId | UUID | One of the two filters | Lists the collection’s assets. |
| entityType | string | With entityId | job, organization, or project. |
| entityId | UUID | With entityType | Lists assets linked to this entity. |
Example request
curl "https://api.runnit.io/api/v1/assets?collectionId=<collection-id>" \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "collectionId": "d5df4edc-...", "assets": [ { "id": "5595c819-...", "fileName": "style-guide" } ] }The entity form omits collectionId. Asset objects in lists do not include
content; fetch an asset by ID to read it.
Errors
400if you supply neithercollectionIdnor theentityTypeandentityIdpair.403if you do not have access to the collection or entity.404if the collection does not exist.
GET /api/v1/assets/file
Section titled “GET /api/v1/assets/file”Fetch one file by name for an entity. Text files come back as JSON with
content; images and binary documents come back as the raw bytes with the
file’s own content type.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| entityType | string | Yes | job, organization, or project. |
| entityId | UUID | Yes | Entity the file is linked to. |
| fileName | string | Yes | Exact file name. |
Example request
curl "https://api.runnit.io/api/v1/assets/file?entityType=project&entityId=<project-id>&fileName=style-guide" \ -H 'Authorization: Bearer rnk_your_key'Response
For text formats:
{ "success": true, "asset": { "id": "5595c819-...", "fileName": "style-guide", "content": "# Style guide..." } }For images, PDFs, Office documents, and other binary formats, the response
body is the file itself (Content-Disposition: inline). Remote-URL assets
always return JSON with the URL in content.
You can also fetch a file with a clean path instead of query parameters:
GET /api/v1/assets/:entityType/:entityId/:fileName. That form always serves
the file content directly (redirecting for remote-URL assets), which suits
embedding in src attributes.
Errors
403if you do not have access to the entity.404if no asset with that name is linked to the entity.
GET /api/v1/assets/by-type
Section titled “GET /api/v1/assets/by-type”List an entity’s assets grouped by file type.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| entityType | string | Yes | job, organization, or project. |
| entityId | UUID | Yes | Entity to inspect. |
Example request
curl "https://api.runnit.io/api/v1/assets/by-type?entityType=project&entityId=<project-id>" \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "assetsByType": { "markdown": [ { "id": "5595c819-...", "fileName": "style-guide" } ], "image": [ { "id": "8a41d2b0-...", "fileName": "hero.png" } ] }}Errors
403if you do not have access to the entity.
GET /api/v1/assets/download
Section titled “GET /api/v1/assets/download”Download all of an entity’s assets as a single zip archive.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| entityType | string | Yes | job, organization, or project. |
| entityId | UUID | Yes | Entity whose assets to download. |
| creator | string | No | Only include files with this creator label (an agent type, or manual for human uploads). |
Example request
curl -L -o assets.zip \ "https://api.runnit.io/api/v1/assets/download?entityType=project&entityId=<project-id>" \ -H 'Authorization: Bearer rnk_your_key'Response
A zip file (Content-Type: application/zip) containing every matching asset.
Errors
403if you do not have access to the entity.404if the entity has no matching assets.
GET /api/v1/assets/folders
Section titled “GET /api/v1/assets/folders”List folders, either for a collection or for an entity that owns a folder tree.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| collectionId | UUID | One of the two filters | Lists the collection’s folders. |
| ownerEntityType | string | With ownerEntityId | organization, project, or job. |
| ownerEntityId | UUID | With ownerEntityType | Lists the entity’s folders. |
Example request
curl "https://api.runnit.io/api/v1/assets/folders?ownerEntityType=organization&ownerEntityId=<org-id>" \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "folders": [ { "id": "c1b2a3d4-...", "name": "Logos", "slug": "logos", "path": "/logos", "parentFolderId": null, "sortOrder": 0 } ]}Errors
400if you supply neithercollectionIdnor the owner pair.403if you do not have access to the collection or entity.404if the collection does not exist.
POST /api/v1/assets/folders
Section titled “POST /api/v1/assets/folders”Create a folder, optionally nested under a parent folder.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Folder name. |
| collectionId | UUID | One of the two homes | Creates the folder in the collection’s organisation tree. |
| ownerEntityType | string | With ownerEntityId | organization, project, or job. |
| ownerEntityId | UUID | With ownerEntityType | Entity that owns the folder tree. |
| parentFolderId | UUID | No | Parent folder for nesting. |
Example request
curl -X POST https://api.runnit.io/api/v1/assets/folders \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "ownerEntityType": "project", "ownerEntityId": "<project-id>", "name": "Approved" }'Response
Returns 201 with the new folder:
{ "success": true, "folder": { "id": "c1b2a3d4-...", "name": "Approved", "path": "/approved" } }Errors
400ifnameis missing, or neithercollectionIdnor the owner pair is given.403if you do not have access to the collection or entity.
DELETE /api/v1/assets/folders/:folderId
Section titled “DELETE /api/v1/assets/folders/:folderId”Delete a folder. By default the assets inside are kept (they just lose the
folder link); pass deleteAssets=true to delete them too.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| deleteAssets | boolean | No | true also deletes every asset in the folder. |
Example request
curl -X DELETE "https://api.runnit.io/api/v1/assets/folders/<folder-id>?deleteAssets=true" \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true }Errors
403if you do not have access to the folder’s owner.404if the folder does not exist.
GET /api/v1/assets/search
Section titled “GET /api/v1/assets/search”Semantic search over asset content. Runnit indexes every asset’s content and description, so you can search with natural language rather than exact file names. Any organisation member can call it; results are limited to assets you can access.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| q | string | Yes | Natural-language search text. |
| entityType | string | No | Limit results to assets linked to one entity type: job, organization, or project. Use with entityId. |
| entityId | string (UUID) | No | The entity to limit results to. Use with entityType. |
| organizationId | string (UUID) | No | The organisation to search in. Defaults to your key’s organisation. |
| tagIds | string | No | Comma-separated tag ids; only assets with those tags are returned. |
| folderId | string (UUID) | No | Only assets in this folder. |
| limit | integer | No | Maximum results, 1 to 50. Default 20. |
Example request
curl "https://api.runnit.io/api/v1/assets/search?q=brand%20tone%20of%20voice&limit=10" \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "results": [ { "assetId": "5595c819-...", "fileName": "style-guide", "score": 0.87 } ]}Each result includes the matching asset’s identifying fields and a relevance
score. Fetch the full content with GET /api/v1/assets/:assetId.
Errors
400ifqis missing or empty.403if you ask for an entity you cannot access.
GET /api/v1/assets/:assetId
Section titled “GET /api/v1/assets/:assetId”Get one asset with its full content. The response format depends on the file:
- Text formats return JSON with
content. - Images, PDFs, Office documents, and other binary formats return the raw file bytes with the file’s content type.
- Remote-URL assets return JSON with the URL in
content. format=jsonforces a JSON response for any file (binary content comes back base64-encoded).
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| assetId | UUID | Yes | Asset ID. |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| format | string | No | json forces a JSON response even for binary files. |
| includeLinks | boolean | No | true includes the asset’s entityLinks in JSON responses. |
Example request
curl "https://api.runnit.io/api/v1/assets/<asset-id>?format=json&includeLinks=true" \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "asset": { "id": "5595c819-...", "fileName": "style-guide", "content": "# Style guide...", "version": 2, "entityLinks": [] } }Errors
403if your organisation does not own the asset.404if the asset does not exist.
PATCH /api/v1/assets/:assetId
Section titled “PATCH /api/v1/assets/:assetId”Update an asset’s content or details. Changing content (or fileName)
snapshots the previous state and creates a new version, so earlier versions
stay available for restore.
Request body (all optional)
| Name | Type | Required | Description |
|---|---|---|---|
| content | string | No | New file content. Text formats reject placeholder content. |
| fileName | string | No | New file name. |
| description | string | No | New description. |
| metadata | object | No | Replaces the stored metadata. |
| changelog | string | No | Message recorded on the new version. |
| updatedByAgentType | string | No | Label recording which automation made the change. |
Changing content, file name, description, or metadata also queues reindexing,
so searchStatus returns to pending until the new content is indexed.
Example request
curl -X PATCH https://api.runnit.io/api/v1/assets/<asset-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "content": "# Style guide v2\n\nUse sentence case everywhere.", "changelog": "Second pass" }'Response
{ "success": true, "asset": { "id": "5595c819-...", "version": 2, "content": "# Style guide v2..." } }Errors
400with errorplaceholder_contentif the new text content looks like a placeholder.403if your organisation does not own the asset.404if the asset does not exist.
DELETE /api/v1/assets/:assetId
Section titled “DELETE /api/v1/assets/:assetId”Delete an asset.
Example request
curl -X DELETE https://api.runnit.io/api/v1/assets/<asset-id> \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "message": "Asset deleted successfully" }Errors
403if your organisation does not own the asset.404if the asset does not exist.
POST /api/v1/assets/:assetId/links
Section titled “POST /api/v1/assets/:assetId/links”Link an existing asset to another entity, so it also appears there.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| entityType | string | Yes | job, organization, or project. |
| entityId | UUID | Yes | Entity to link. |
| requestId | string | No | Free-form reference stored on the link. |
Example request
curl -X POST https://api.runnit.io/api/v1/assets/<asset-id>/links \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "entityType": "project", "entityId": "<project-id>" }'Response
Returns 201 with the link:
{ "success": true, "link": { "assetId": "5595c819-...", "entityType": "project", "entityId": "1f0e9a52-..." } }Errors
403if your organisation does not own the asset, or you do not have access to the target entity.404if the asset does not exist.
DELETE /api/v1/assets/:assetId/links
Section titled “DELETE /api/v1/assets/:assetId/links”Unlink an asset from an entity, identified by query parameters.
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
| entityType | string | Yes | job, organization, or project. |
| entityId | UUID | Yes | Entity to unlink. |
Example request
curl -X DELETE "https://api.runnit.io/api/v1/assets/<asset-id>/links?entityType=project&entityId=<project-id>" \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "message": "Asset unlinked from entity successfully" }Errors
403if your organisation does not own the asset.404if the asset or the link does not exist.
POST /api/v1/assets/:assetId/tags
Section titled “POST /api/v1/assets/:assetId/tags”Apply existing tags to an asset. The first five supplied tags become the asset’s primary tags.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| tagIds | array of UUIDs | Yes | At least one tag ID. |
Example request
curl -X POST https://api.runnit.io/api/v1/assets/<asset-id>/tags \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "tagIds": ["<tag-id>"] }'Response
{ "success": true, "tags": [ { "id": "7d31e0aa-...", "name": "Brand", "slug": "brand", "isSystem": false } ] }Errors
400iftagIdsis missing, empty, or contains a non-UUID value.403if your organisation does not own the asset.404if the asset does not exist.
DELETE /api/v1/assets/:assetId/tags/:tagId
Section titled “DELETE /api/v1/assets/:assetId/tags/:tagId”Remove one tag from an asset.
Example request
curl -X DELETE https://api.runnit.io/api/v1/assets/<asset-id>/tags/<tag-id> \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true }Errors
403if your organisation does not own the asset.404if the asset does not exist.
POST /api/v1/assets/:assetId/folders
Section titled “POST /api/v1/assets/:assetId/folders”Place an asset in one or more folders. Use replaceExisting to move it
rather than add it.
Request body
| Name | Type | Required | Description |
|---|---|---|---|
| folderIds | array of UUIDs | Yes | At least one folder ID. |
| primaryFolderId | UUID | No | Which of folderIds is the primary folder. |
| replaceExisting | boolean | No | true removes the asset from all current folders first (a move). |
Example request
curl -X POST https://api.runnit.io/api/v1/assets/<asset-id>/folders \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "folderIds": ["<folder-id>"], "replaceExisting": true }'Response
{ "success": true, "folders": [ { "id": "c1b2a3d4-...", "name": "Approved", "path": "/approved" } ] }Errors
400iffolderIdsis missing or empty.403if your organisation does not own the asset.404if the asset does not exist.
POST /api/v1/assets/:assetId/reindex
Section titled “POST /api/v1/assets/:assetId/reindex”Queue an asset for content reindexing. Use this if search results or the summary excerpt look stale.
Example request
curl -X POST https://api.runnit.io/api/v1/assets/<asset-id>/reindex \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "message": "Asset reindex scheduled" }searchStatus moves to pending and returns to ready once indexing
completes.
Errors
403if your organisation does not own the asset.404if the asset does not exist.
GET /api/v1/assets/:assetId/insights
Section titled “GET /api/v1/assets/:assetId/insights”Get an asset’s derived information: AI summary excerpt, indexing status, tags, and folders.
Example request
curl https://api.runnit.io/api/v1/assets/<asset-id>/insights \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "insights": { "summaryExcerpt": "Tone of voice rules for all campaign copy.", "searchStatus": "ready", "lastIndexedAt": "2026-07-08T12:31:58.597Z", "tags": [ { "id": "7d31e0aa-...", "name": "Brand" } ], "folders": [ { "id": "c1b2a3d4-...", "name": "Approved" } ] }}Errors
403if your organisation does not own the asset.404if the asset does not exist.
GET /api/v1/assets/:assetId/versions
Section titled “GET /api/v1/assets/:assetId/versions”List an asset’s version history, newest first (up to 50 versions). Each version includes its full content, so you can inspect a version before restoring it.
Example request
curl https://api.runnit.io/api/v1/assets/<asset-id>/versions \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "versions": [ { "id": "367be2ca-...", "assetId": "5595c819-...", "versionNumber": 2, "fileName": "style-guide", "fileSize": 47, "content": "# Style guide v2...", "changelog": "Second pass", "createdByUserId": "213ae491-...", "createdAt": "2026-07-08T02:32:22.652Z" } ]}Errors
403if your organisation does not own the asset.404if the asset does not exist.
POST /api/v1/assets/:assetId/versions/:versionId/restore
Section titled “POST /api/v1/assets/:assetId/versions/:versionId/restore”Restore an asset to an earlier version. The asset’s content and name return to that snapshot.
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
| assetId | UUID | Yes | Asset ID. |
| versionId | UUID | Yes | Version to restore, from the versions list. |
Example request
curl -X POST https://api.runnit.io/api/v1/assets/<asset-id>/versions/<version-id>/restore \ -H 'Authorization: Bearer rnk_your_key'Response
{ "success": true, "asset": { "id": "5595c819-...", "content": "# Style guide..." } }Errors
403if your organisation does not own the asset.404if the asset does not exist.
Common workflows
Section titled “Common workflows”An end-to-end file lifecycle. Replace the placeholders with real IDs.
- Create a collection to hold the files:
curl -X POST https://api.runnit.io/api/v1/collections \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "ownerOrganizationId": "<org-id>", "name": "Winter Campaign Assets", "collectionType": "creative" }'- Upload a file into it:
curl -X POST https://api.runnit.io/api/v1/assets \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "fileName": "campaign-brief", "fileExtension": "md", "content": "# Winter campaign brief\n\nGoals, audience, and deliverables.", "collectionId": "<collection-id>" }'- Update the file, which creates version 2 automatically:
curl -X PATCH https://api.runnit.io/api/v1/assets/<asset-id> \ -H 'Authorization: Bearer rnk_your_key' \ -H 'Content-Type: application/json' \ -d '{ "content": "# Winter campaign brief v2\n\nUpdated goals.", "changelog": "Scope revision" }'- Find files by listing the collection, or search asset content with natural language:
curl "https://api.runnit.io/api/v1/assets?collectionId=<collection-id>" \ -H 'Authorization: Bearer rnk_your_key'
curl "https://api.runnit.io/api/v1/assets/search?q=winter%20campaign%20goals" \ -H 'Authorization: Bearer rnk_your_key'AI agents can run the same search through the
MCP server with the same rnk_ key.
- Download the content. A single file:
curl "https://api.runnit.io/api/v1/assets/<asset-id>" \ -H 'Authorization: Bearer rnk_your_key' -o campaign-brief.mdOr everything linked to a project as a zip:
curl -L -o project-assets.zip \ "https://api.runnit.io/api/v1/assets/download?entityType=project&entityId=<project-id>" \ -H 'Authorization: Bearer rnk_your_key'Next steps
Section titled “Next steps”Organise files with the Asset Collections API, or report on the work they support with the Reports API.