API 레퍼런스
Media
Management API: GET /sites/{id}/media, POST /sites/{id}/media, POST /sites/{id}/media/uploads, …
List the website's media library (the same files the user sees in the dashboard): public and protected images, videos, audio, PDFs and documents, newest first, plus storage usage (usedBytes vs limitBytes). Filter with assetKind, visibility, unusedOnly and search (file name). Files in the trash, older versions kept after media.replace, and broken records where the original file is missing (unless includeMissing=true) are not listed; files in the trash still count toward storage. Errors: FORBIDDEN, INVALID_CURSOR.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
assetKind"image" | "video" | "audio" | "pdf" | "document" | "product" · queryOnly this kind of file; all kinds by default. Deprecated value product: same as visibility=protected (removed in the next major version).
visibility"public" | "protected" · queryOnly public or only protected files; both by default.
tagsstring · queryComma-separated tags; only files that carry every listed tag (case-insensitive) are returned.
unusedOnlyboolean | "true" | "false" · queryOnly files no content uses (candidates for cleaning up storage).
includeMissingboolean | "true" | "false" · queryInclude broken asset records where the original file no longer exists on storage (defaults to false).
searchstring · queryPart of the file name.
sort"newest" | "oldest" | "largest" · querynewest (default), oldest or largest.
limitinteger · queryMaximum number of items to return, 1–100. Defaults to 20.
cursorstring · queryOpaque cursor from the previous page's nextCursor. Omit for the first page.
Response 200
dataobject[]requireddata[].assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
data[].kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
data[].visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
data[].tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
data[].processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
data[].processing.status"pending" | "processing" | "failed"requireddata[].processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
data[].processing.errorstring | nullrequiredError code when status is failed.
data[].processing.updatedAtstring | nullrequireddata[].streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
data[].status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
data[].missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
data[].versionintegerrequiredIncreases each time media.replace uploads a new version.
data[].urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
data[].thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
data[].fileNamestring | nullrequireddata[].mimeTypestringrequireddata[].bytesinteger | nullrequirednull while the size of an older file is still being calculated.
data[].billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
data[].widthinteger | nullrequireddata[].heightinteger | nullrequireddata[].durationSecondsnumber | nullrequireddata[].pageCountinteger | nullrequireddata[].sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
data[].inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
data[].focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
data[].focus.xnumberrequireddata[].focus.ynumberrequireddata[].edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
data[].altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
data[].watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
data[].createdAtstringrequiredhasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
usageobjectrequiredusage.usedBytesnumberrequiredBytes counted against the storage limit (user-uploaded originals; platform derivatives excluded).
usage.limitBytesnumberrequiredPlan storage limit in bytes (including any purchased storage add-on levels, e.g. 10,737,418,240 for 10 GB on PRO).
usage.remainingBytesnumberrequiredRemaining available storage in bytes (Math.max(0, limitBytes - usedBytes)).
usage.totalBytesnumberrequiredTotal bytes stored on this website including platform derivatives (favicons, auto OG images).
usage.quotaBytesnumberrequiredPlan storage limit in bytes (alias of limitBytes; compare usedBytes <= quotaBytes to check whether storage is within quota).
usage.fileCountintegerrequiredActive file count on this website.
Errors
FORBIDDEN403INVALID_CURSOR400
MCP tool media_list
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/media?assetKind=image&unusedOnly=false" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
],
"usage": {
"usedBytes": 482133,
"limitBytes": 5368709120,
"remainingBytes": 5368226987,
"totalBytes": 482133,
"quotaBytes": 5368709120,
"fileCount": 1
},
"hasMore": false,
"nextCursor": null
}Upload one image or file into the website's media library. Provide exactly one of base64 or url. Files the user attaches in chat are not available to you: use media.create_upload_link for those (it also takes videos and documents), media.list_page_images for a page the user owns, or media.search_stock when they have no photos. Images: put asset.url in sections (pages.replace_sections), blog posts and product images. Videos, audio, PDFs, documents, e-books and ZIP files: pass fileName; use visibility "protected" for deliverable files (course lessons, e-books, downloads) and reference them by assetId in deliverable_units.*. Files over 40 MB (base64) or 100 MB (url): use media.request_upload. Keep asset.assetId to manage the file later (media.get, media.replace, media.trash). Uploads are kept; unused files show as unused in the media library. Errors: FORBIDDEN, VALIDATION, INVALID_IMAGE_DATA, INVALID_FILE_DATA, UNSUPPORTED_IMAGE_TYPE, UNSUPPORTED_FILE_TYPE (extension not accepted, or the file header does not match it), FILE_TOO_LARGE, FETCH_FAILED, UPLOAD_FAILED, SERVICE_UNAVAILABLE, STORAGE_LIMIT_EXCEEDED (plan storage is full; every upload counts toward it).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
base64stringFile bytes as base64 (plain or data: URL), max 40 MB decoded. Images: JPEG, PNG, WebP, GIF, SVG or HEIC, stored as-is (only downscaled when over Cloudflare limits). Other files need fileName.
urlstringPublic https URL to copy from: images up to 40 MB, other files up to 100 MB (use media.request_upload for larger files). Private networks and redirects to them are rejected.
fileNamestringFile name with extension, e.g. lesson-01.mp4 or workbook.pdf. Required for videos, audio, PDFs, documents, e-books and ZIP files (the extension decides the format; the file header must match). Omit for images.
visibility"public" | "protected"public (default): files for pages, posts and collections; anyone with the link can open them. protected: files for deliverables (paid courses, e-books, downloads) stored privately and only served to buyers through signed short-lived links. Protected images (JPG, PNG, WebP; pass fileName) are stored as private files too: no public variants, not offered in page or post pickers, not usable as deliverable covers.
usagestringHint for bookkeeping (images only): content (default), blog, product, favicon.
Response 201
assetobjectrequiredasset.assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
asset.kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
asset.visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
asset.tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
asset.processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
asset.processing.status"pending" | "processing" | "failed"requiredasset.processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
asset.processing.errorstring | nullrequiredError code when status is failed.
asset.processing.updatedAtstring | nullrequiredasset.streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
asset.status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
asset.missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
asset.versionintegerrequiredIncreases each time media.replace uploads a new version.
asset.urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
asset.thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
asset.fileNamestring | nullrequiredasset.mimeTypestringrequiredasset.bytesinteger | nullrequirednull while the size of an older file is still being calculated.
asset.billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
asset.widthinteger | nullrequiredasset.heightinteger | nullrequiredasset.durationSecondsnumber | nullrequiredasset.pageCountinteger | nullrequiredasset.sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
asset.inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
asset.focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
asset.focus.xnumberrequiredasset.focus.ynumberrequiredasset.edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
asset.altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
asset.watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
asset.createdAtstringrequired
Errors
FORBIDDEN403VALIDATION400INVALID_IMAGE_DATA400INVALID_FILE_DATA400UNSUPPORTED_IMAGE_TYPE422UNSUPPORTED_FILE_TYPE422 · extension not accepted, or the file header does not match itFILE_TOO_LARGE413FETCH_FAILED422UPLOAD_FAILED502SERVICE_UNAVAILABLE503STORAGE_LIMIT_EXCEEDED403 · plan storage is full; every upload counts toward it
MCP tool media_upload
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/photos/croissant.jpg"}'{
"asset": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "croissant.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": false,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-06T08:00:00.000Z"
}
}Get a one-time upload URL for a large video, audio, PDF, document, e-book or ZIP file (up to 5 GB) that is too big for media.upload. Send the file with an HTTP PUT to upload.url, with exactly the returned upload.headers and the file bytes as the body (curl -X PUT -H ... --data-binary @file), within an hour; then call media.confirm_upload with fileKey. Unconfirmed uploads are discarded after 24 hours. Use visibility "protected" for deliverable files (no public URL; only served to entitled buyers). Images go through media.upload. Errors: FORBIDDEN, VALIDATION, UNSUPPORTED_FILE_TYPE, INVALID_FILE_DATA (fileSize is 0), FILE_TOO_LARGE, STORAGE_LIMIT_EXCEEDED, SERVICE_UNAVAILABLE.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
fileNamestringrequiredFile name with extension; the extension decides the format.
fileSizeintegerrequiredExact size in bytes; the upload must match it.
visibility"public" | "protected"public (default): files for pages, posts and collections; anyone with the link can open them. protected: files for deliverables (paid courses, e-books, downloads) stored privately and only served to buyers through signed short-lived links. Protected images (JPG, PNG, WebP; pass fileName) are stored as private files too: no public variants, not offered in page or post pickers, not usable as deliverable covers.
Response 201
fileKeystringrequiredPass to media.confirm_upload.
uploadobjectrequiredupload.method"PUT"requiredupload.urlstringrequiredupload.headersobjectrequiredSend every header exactly as given; a missing or changed header is rejected.
upload.expiresAtstringrequired
Errors
FORBIDDEN403VALIDATION400UNSUPPORTED_FILE_TYPE422INVALID_FILE_DATA400 · fileSize is 0FILE_TOO_LARGE413STORAGE_LIMIT_EXCEEDED403SERVICE_UNAVAILABLE503
MCP tool media_request_upload
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media/uploads" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fileName":"lesson-01.mp4","fileSize":734003200,"visibility":"protected"}'{
"fileKey": "ws_example01/deliverables/0123456789abcdef0123456789abcd",
"upload": {
"method": "PUT",
"url": "https://kanorio-products.example.r2.cloudflarestorage.com/ws_example01/deliverables/0123456789abcdef0123456789abcd?X-Amz-Signature=example",
"headers": {
"Content-Type": "video/mp4",
"x-amz-meta-filename": "lesson-01.mp4"
},
"expiresAt": "2026-10-06T08:00:00.000Z"
}
}Finish a media.request_upload: the file is checked (size, format and file header must match the request) and added to the media library. Calling it again for the same fileKey returns the same asset. Protected files are referenced by assetId in deliverable_units.*; public files by asset.url in sections, posts and collections. Errors: FORBIDDEN, UPLOAD_NOT_FOUND (not uploaded yet, expired, or the fileKey is not from this website), UNSUPPORTED_FILE_TYPE, INVALID_FILE_DATA, FILE_TOO_LARGE, STORAGE_LIMIT_EXCEEDED, SERVICE_UNAVAILABLE, UPLOAD_FAILED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
fileKeystringrequiredfileKey from media.request_upload.
visibility"public" | "protected"The same visibility passed to media.request_upload.
Response 201
assetobjectrequiredasset.assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
asset.kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
asset.visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
asset.tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
asset.processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
asset.processing.status"pending" | "processing" | "failed"requiredasset.processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
asset.processing.errorstring | nullrequiredError code when status is failed.
asset.processing.updatedAtstring | nullrequiredasset.streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
asset.status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
asset.missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
asset.versionintegerrequiredIncreases each time media.replace uploads a new version.
asset.urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
asset.thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
asset.fileNamestring | nullrequiredasset.mimeTypestringrequiredasset.bytesinteger | nullrequirednull while the size of an older file is still being calculated.
asset.billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
asset.widthinteger | nullrequiredasset.heightinteger | nullrequiredasset.durationSecondsnumber | nullrequiredasset.pageCountinteger | nullrequiredasset.sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
asset.inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
asset.focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
asset.focus.xnumberrequiredasset.focus.ynumberrequiredasset.edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
asset.altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
asset.watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
asset.createdAtstringrequired
Errors
FORBIDDEN403UPLOAD_NOT_FOUND404 · not uploaded yet, expired, or the fileKey is not from this websiteUNSUPPORTED_FILE_TYPE422INVALID_FILE_DATA400FILE_TOO_LARGE413STORAGE_LIMIT_EXCEEDED403SERVICE_UNAVAILABLE503UPLOAD_FAILED502
MCP tool media_confirm_upload
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media/uploads/confirm" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fileKey":"ws_example01/deliverables/0123456789abcdef0123456789abcd","visibility":"protected"}'{
"asset": {
"assetId": "asset_example01",
"kind": "video",
"visibility": "protected",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": null,
"thumbnailUrl": null,
"fileName": "lesson-01.mp4",
"mimeType": "video/mp4",
"bytes": 734003200,
"streaming": "none",
"billableBytes": 734003200,
"width": null,
"height": null,
"durationSeconds": null,
"pageCount": null,
"source": "PRODUCT",
"inUse": false,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-06T08:00:00.000Z"
}
}Create a short-lived (15 min) upload page the user opens on their phone or computer to send you photos or files. Use it whenever the user offers photos, videos or documents in the conversation: chat clients never pass attachments to tools, so you cannot see or upload those files yourself and must not pretend you did. target: images (default) for photos; files for videos, audio, PDFs and documents shown on pages or posts (public); protected for deliverable files such as course lessons and e-books (private, no public URL). Files go up to 5 GB each. Reply to the user with the returned url in plain words (e.g. 'Open this link and add your photos, then tell me when you're done'). Afterwards call media.upload_link_status with the same token to get the uploaded assets. Errors: FORBIDDEN, SERVICE_UNAVAILABLE (try again shortly).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
maxFilesintegerHow many photos or files the link accepts (default 10, max 20).
purposestringShown on the upload page in the user's words, e.g. 'menu photos' or 'storefront and team'.
target"images" | "files" | "protected"What the link accepts: images (default), files (public) or protected (deliverable files).
Response 201
urlstringrequiredGive this to the user.
tokenstringrequiredKeep it to call media.upload_link_status.
linkIdstringrequiredexpiresAtstringrequiredmaxFilesintegerrequiredtarget"images" | "files" | "protected"required
Errors
FORBIDDEN403SERVICE_UNAVAILABLE503 · try again shortly
MCP tool media_create_upload_link
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media/upload-links" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"maxFiles":6,"purpose":"menu photos"}'{
"url": "https://app.kanorio.com/u/example-upload-link-token",
"token": "example-upload-link-token",
"linkId": "lnk_example01",
"expiresAt": "2026-10-06T08:00:00.000Z",
"maxFiles": 6,
"target": "images"
}Upload link status
/sites/{id}/media/upload-links/statusCheck what the user has uploaded through an upload link (media.create_upload_link). Call it when the user says they are done, or to poll every 20-30 seconds while waiting. Returns the uploaded images or files as assets (put asset:<assetId> in sections; reference protected files by assetId in deliverable_units.*) and how many more the link accepts. The status stays readable for a few minutes after the link expires. Errors: FORBIDDEN, LINK_INVALID (token does not belong to this website or is malformed).
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
tokenstring · queryrequiredThe token returned by media.create_upload_link.
Response 200
linkIdstringrequiredexpiresAtstringrequiredmaxFilesintegerrequireduploadedCountintegerrequiredremainingintegerrequiredpurposestring | nullrequiredtarget"images" | "files" | "protected"requiredassetsobject[]requiredassets[].assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
assets[].kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
assets[].visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
assets[].tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
assets[].processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
assets[].processing.status"pending" | "processing" | "failed"requiredassets[].processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
assets[].processing.errorstring | nullrequiredError code when status is failed.
assets[].processing.updatedAtstring | nullrequiredassets[].streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
assets[].status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
assets[].missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
assets[].versionintegerrequiredIncreases each time media.replace uploads a new version.
assets[].urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
assets[].thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
assets[].fileNamestring | nullrequiredassets[].mimeTypestringrequiredassets[].bytesinteger | nullrequirednull while the size of an older file is still being calculated.
assets[].billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
assets[].widthinteger | nullrequiredassets[].heightinteger | nullrequiredassets[].durationSecondsnumber | nullrequiredassets[].pageCountinteger | nullrequiredassets[].sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
assets[].inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
assets[].focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
assets[].focus.xnumberrequiredassets[].focus.ynumberrequiredassets[].edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
assets[].altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
assets[].watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
assets[].createdAtstringrequiredassets[].uploadedAtstringrequired
Errors
FORBIDDEN403LINK_INVALID404 · token does not belong to this website or is malformed
MCP tool media_upload_link_status
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/media/upload-links/status?token=example-upload-link-token" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"linkId": "lnk_example01",
"expiresAt": "2026-10-06T08:00:00.000Z",
"maxFiles": 6,
"uploadedCount": 2,
"remaining": 4,
"purpose": "menu photos",
"target": "images",
"assets": [
{
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "menu-front.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": false,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-06T08:00:00.000Z",
"uploadedAt": "2026-10-06T08:00:00.000Z"
},
{
"assetId": "asset_example02",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "menu-back.jpg",
"mimeType": "image/jpeg",
"bytes": 391204,
"streaming": "none",
"billableBytes": 391204,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": false,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-06T08:00:00.000Z",
"uploadedAt": "2026-10-06T08:00:00.000Z"
}
]
}Search free stock photos (Pexels and Pixabay) when the user has no photos of their own. Ask the user first whether they have photos; use stock as the fallback and tell them the images are stock photos they can replace later. Write the query in English with concrete nouns (e.g. 'sourdough bread bakery counter', not 'nice bakery'); add orientation=landscape for hero backgrounds. Both libraries allow commercial use without attribution; do not use stock photos as the brand logo or to imply endorsement. Pick a hit, then call media.import_stock to copy it into the website's media library. Errors: FORBIDDEN, SEARCH_BUSY (retry after details.retryAfter seconds), SEARCH_FAILED, UNKNOWN_PROVIDER, INVALID_CURSOR.
Parameters
idstring · pathrequiredWebsite the photos are for (scopes the token's access).
querystring · queryrequiredEnglish search terms work best.
orientation"landscape" | "portrait" | "square" · queryprovider"all" | "pexels" | "pixabay" · queryDefaults to all (results interleaved).
limitinteger · queryMaximum number of items to return, 1–12. Defaults to 9.
cursorstring · queryOpaque cursor from the previous page's nextCursor. Omit for the first page.
Response 200
dataobject[]requireddata[].idstringrequireddata[].providerstringrequiredpexels or pixabay.
data[].previewURLstringrequiredSmall preview; do not put this in sections.
data[].displayURLstringrequireddata[].downloadURLstringrequiredPass to media.import_stock together with id and provider.
data[].widthintegerrequireddata[].heightintegerrequireddata[].photographerNamestringrequireddata[].photoPageURLstringrequireddata[].altstring | nullrequiredhasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
Errors
FORBIDDEN403SEARCH_BUSY429 · retry after details.retryAfter secondsSEARCH_FAILED502UNKNOWN_PROVIDER422INVALID_CURSOR400
MCP tool media_search_stock
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/media/stock?query=sourdough+bread+bakery+counter&orientation=landscape&limit=3" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"data": [
{
"id": "1234567",
"provider": "pexels",
"previewURL": "https://images.pexels.com/photos/1234567/pexels-photo-1234567.jpeg?w=280",
"displayURL": "https://images.pexels.com/photos/1234567/pexels-photo-1234567.jpeg?w=1280",
"downloadURL": "https://images.pexels.com/photos/1234567/pexels-photo-1234567.jpeg",
"width": 4000,
"height": 2667,
"photographerName": "Example Photographer",
"photoPageURL": "https://www.pexels.com/photo/1234567/",
"alt": "Sourdough loaves on a wooden counter"
}
],
"hasMore": false,
"nextCursor": null
}Copy one stock photo from media.search_stock into the website's media library. Pass provider, id and downloadURL exactly as returned by the search. Returns the asset: put asset.url in sections (backgroundImage, images[].url) and keep asset.assetId to manage it. Write the imageAlt yourself in the site's content language, based on the photo's alt and the section's purpose. Errors: FORBIDDEN, UNKNOWN_PROVIDER, INVALID_STOCK_URL, STOCK_BUSY, INVALID_IMAGE_DATA, UPLOAD_FAILED, STORAGE_LIMIT_EXCEEDED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
Request body
provider"pexels" | "pixabay"requiredidstringrequiredStock photo id from the search result.
downloadURLstringrequireddownloadURL from the search result.
usagestringBookkeeping hint: content (default), blog, product.
Response 201
assetobjectrequiredasset.assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
asset.kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
asset.visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
asset.tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
asset.processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
asset.processing.status"pending" | "processing" | "failed"requiredasset.processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
asset.processing.errorstring | nullrequiredError code when status is failed.
asset.processing.updatedAtstring | nullrequiredasset.streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
asset.status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
asset.missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
asset.versionintegerrequiredIncreases each time media.replace uploads a new version.
asset.urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
asset.thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
asset.fileNamestring | nullrequiredasset.mimeTypestringrequiredasset.bytesinteger | nullrequirednull while the size of an older file is still being calculated.
asset.billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
asset.widthinteger | nullrequiredasset.heightinteger | nullrequiredasset.durationSecondsnumber | nullrequiredasset.pageCountinteger | nullrequiredasset.sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
asset.inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
asset.focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
asset.focus.xnumberrequiredasset.focus.ynumberrequiredasset.edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
asset.altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
asset.watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
asset.createdAtstringrequired
Errors
FORBIDDEN403UNKNOWN_PROVIDER422INVALID_STOCK_URL400STOCK_BUSY429INVALID_IMAGE_DATA400UPLOAD_FAILED502STORAGE_LIMIT_EXCEEDED403
MCP tool media_import_stock
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media/stock" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"provider":"pexels","id":"1234567","downloadURL":"https://images.pexels.com/photos/1234567/pexels-photo-1234567.jpeg"}'{
"asset": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "pexels-1234567.jpeg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": false,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-06T08:00:00.000Z"
}
}List the images found on a public web page (its cover image, content images and logo) when the user points you to a site they own, such as their old website or a brand page. Nothing is downloaded; pick the ones you need and copy them with media.upload (url mode). Before copying, confirm with the user that the images are theirs or they have the right to use them; do not copy images from sites that are not the user's. Pages behind a login or anti-bot walls (Instagram, Facebook) fail with FETCH_FAILED: ask the user to use an upload link instead. Errors: FORBIDDEN, INVALID_URL (private or non-http address), FETCH_FAILED (details.status is the upstream status).
Parameters
idstring · pathrequiredWebsite the images are for (scopes the token's access).
urlstring · queryrequiredmaxinteger · queryDefaults to 12.
Response 200
dataobject[]requireddata[].urlstringrequireddata[].role"cover" | "content" | "logo"requireddata[].ownAssetbooleanrequiredfalse when hosted on a website-builder CDN (likely a platform asset, not the brand's).
hasMorebooleanrequiredTrue when another page exists; pass nextCursor to fetch it.
nextCursorstring | nullrequiredCursor for the next page, or null on the last page.
pageUrlstringrequiredfinalUrlstringrequiredAfter redirects.
titlestring | nullrequiredsiteNamestring | nullrequired
Errors
FORBIDDEN403INVALID_URL400 · private or non-http addressFETCH_FAILED422 · details.status is the upstream status
MCP tool media_list_page_images
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/media/page-images?url=https%3A%2F%2Fold-site.example.com%2F&max=5" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"pageUrl": "https://old-site.example.com/",
"finalUrl": "https://old-site.example.com/",
"title": "Sunny Bakery",
"siteName": "Sunny Bakery",
"data": [
{
"url": "https://old-site.example.com/images/og.jpg",
"role": "cover",
"ownAsset": true
},
{
"url": "https://old-site.example.com/images/logo.png",
"role": "logo",
"ownAsset": true
},
{
"url": "https://old-site.example.com/images/croissant.jpg",
"role": "content",
"ownAsset": true
}
],
"hasMore": false,
"nextCursor": null
}Get one file from the media library or the trash, with every place the site uses it (page sections, share images, brand, posts, products, emails). Errors: FORBIDDEN, ASSET_NOT_FOUND.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
assetIdstring · pathrequiredAsset id from media.list.
Response 200
assetobjectrequiredasset.assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
asset.kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
asset.visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
asset.tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
asset.processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
asset.processing.status"pending" | "processing" | "failed"requiredasset.processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
asset.processing.errorstring | nullrequiredError code when status is failed.
asset.processing.updatedAtstring | nullrequiredasset.streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
asset.status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
asset.missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
asset.versionintegerrequiredIncreases each time media.replace uploads a new version.
asset.urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
asset.thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
asset.fileNamestring | nullrequiredasset.mimeTypestringrequiredasset.bytesinteger | nullrequirednull while the size of an older file is still being calculated.
asset.billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
asset.widthinteger | nullrequiredasset.heightinteger | nullrequiredasset.durationSecondsnumber | nullrequiredasset.pageCountinteger | nullrequiredasset.sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
asset.inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
asset.focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
asset.focus.xnumberrequiredasset.focus.ynumberrequiredasset.edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
asset.altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
asset.watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
asset.createdAtstringrequiredusagesobject[]requiredEmpty when no content uses the file.
usages[].kindstringrequiredpageSection, pageShare, siteContent, brand, favicon, watermark, post, blogTaxonomy, product, productContent, deliverable, productCategory, collectionItem, floatingWidgets, messageTemplate or campaign.
usages[].labelstringrequiredWhere it is used, in words you can repeat to the user (page › section, post title, product name…).
renditionsobject[] | nullrequiredPlayback sources for public files: original, poster when there is one, and for transcoded videos hls (adaptive-streaming master manifest, application/vnd.apple.mpegurl; play this one, fall back to original) plus thumbnails (progress-bar storyboard VTT). Check streaming: partial means only the lowest quality is in the manifest yet. null for protected files; buyers get their sources through the Customer API.
Errors
FORBIDDEN403ASSET_NOT_FOUND404
MCP tool media_get
curl -X GET "https://app.kanorio.com/api/v1/sites/{id}/media/{assetId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"asset": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
},
"usages": [
{
"kind": "pageSection",
"label": "Home › Our story"
},
{
"kind": "brand",
"label": "Brand logo"
}
],
"renditions": [
{
"kind": "original",
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"width": 2400,
"height": 1600,
"bytes": 482133
}
]
}Set a file's tags and an image's defaults, kept across new versions. tags (any file): replaces the whole list; up to 20 tags of 1-40 characters, case-insensitive duplicates dropped; filter media.list with tags. Images only: focus (0-1 point on the original that image fields without their own framing crop around; null resets to center), altText (used where the image is shown without an alt of its own; null clears it) and watermarkExempt (show this image without the site watermark; needs the site:manage_settings scope and permission). The Delivery API and the editor use the new focus right away; published pages switch after the next site.publish. Errors: FORBIDDEN, ASSET_NOT_FOUND (also for files in the trash and older versions), IMAGES_ONLY (focus or altText on a non-image), TAGS_INVALID, VISIBILITY_BLOCKED (details.reason: in_use | deliverable | trash | missing | processing | unsupported_kind | older_version; details.locations for in_use), VALIDATION.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
assetIdstring · pathrequiredAsset id from media.list.
Request body
focusobject | nullFocal point as 0-1 coordinates on the original image (x from the left, y from the top); null resets to center.
focus.xnumberrequiredfocus.ynumberrequiredaltTextstring | nullDefault alt text in the site's content language; null clears it.
tagsstring[]Replaces the file's tags (any file). Up to 20 tags of 1-40 characters; no commas, line breaks or leading #.
visibility"public" | "protected"Move a video, audio, PDF or document between public and protected. Images cannot be switched (public images live in the image CDN, protected images in private storage): upload the image again with the visibility you need (VISIBILITY_BLOCKED, reason unsupported_kind). The file is moved between storage buckets in the background: the asset shows processing until done and media.asset.ready fires. public → protected is refused while any content uses the file (VISIBILITY_BLOCKED, reason in_use, with locations); protected → public is refused while a deliverable unit uses it (reason deliverable). Old public URLs may stay cached at the CDN edge for a while after the move.
watermarkExemptbooleantrue shows this image without the site watermark.
streamingtrueVideos only: queue the adaptive-streaming (HLS) version now instead of waiting for the hourly check that transcodes videos placed in pages, posts, collections or deliverable units. The lowest quality becomes playable first and the rest follow; media.asset.ready fires when done. Streaming versions count toward the site's storage quota and cannot be removed separately from the video (ADR 0008). VALIDATION when the file is not a video.
Response 200
assetobjectrequiredasset.assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
asset.kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
asset.visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
asset.tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
asset.processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
asset.processing.status"pending" | "processing" | "failed"requiredasset.processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
asset.processing.errorstring | nullrequiredError code when status is failed.
asset.processing.updatedAtstring | nullrequiredasset.streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
asset.status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
asset.missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
asset.versionintegerrequiredIncreases each time media.replace uploads a new version.
asset.urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
asset.thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
asset.fileNamestring | nullrequiredasset.mimeTypestringrequiredasset.bytesinteger | nullrequirednull while the size of an older file is still being calculated.
asset.billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
asset.widthinteger | nullrequiredasset.heightinteger | nullrequiredasset.durationSecondsnumber | nullrequiredasset.pageCountinteger | nullrequiredasset.sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
asset.inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
asset.focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
asset.focus.xnumberrequiredasset.focus.ynumberrequiredasset.edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
asset.altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
asset.watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
asset.createdAtstringrequired
Errors
FORBIDDEN403ASSET_NOT_FOUND404 · also for files in the trash and older versionsIMAGES_ONLY422 · focus or altText on a non-imageTAGS_INVALID422VISIBILITY_BLOCKED422 · details.reason: in_use | deliverable | trash | missing | processing | unsupported_kind | older_version; details.locations for in_useVALIDATION400
MCP tool media_update
curl -X PATCH "https://app.kanorio.com/api/v1/sites/{id}/media/{assetId}" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"focus":{"x":0.32,"y":0.4},"altText":"Fresh croissants on the bakery counter"}'{
"asset": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 1,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": {
"x": 0.32,
"y": 0.4
},
"edgeColor": null,
"altText": "Fresh croissants on the bakery counter",
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
}
}Permanently delete a file that is already in the trash (call media.trash first) and free its storage. This cannot be undone. If the site still uses the file, the call fails with ASSET_IN_USE and details.usages: tell the user those places will stay empty, and only retry with confirm: true after they agree. Errors: FORBIDDEN, NOT_FOUND, NOT_IN_TRASH, PRODUCT_FILE_PROTECTED, ASSET_IN_USE, EXPORT_HOLD (details.until: an export keeps the file until then), DELETE_FAILED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
assetIdstring · pathrequiredAsset id from media.list.
confirmboolean | "true" | "false" · querySet true (REST: ?confirm=true) only after the user agreed that the places in ASSET_IN_USE details.usages will stay empty.
Response 200
assetIdstringrequireddeletedtruerequired
Errors
FORBIDDEN403NOT_FOUND404NOT_IN_TRASH409PRODUCT_FILE_PROTECTED409ASSET_IN_USE422EXPORT_HOLD409 · details.until: an export keeps the file until thenDELETE_FAILED502
MCP tool media_purge
curl -X DELETE "https://app.kanorio.com/api/v1/sites/{id}/media/{assetId}" \
-H "Authorization: Bearer $KANORIO_TOKEN"{
"assetId": "asset_example01",
"deleted": true
}Replace a media file
/sites/{id}/media/{assetId}/replaceUpload a new version of an image or file (exactly one of base64 or url). Every place that uses it switches to the new version and keeps the same assetId, so no content needs to be edited: the editor, brand logo, share images, products, blog posts and deliverables right away; published pages after the next site.publish (awaitingPublish). When the aspect ratio changes, crops are re-centered per layout (aspectRatioChanged): tell the user to check the pages listed in usages. Tell the user before replacing a file that many places use. Videos, audio, PDFs and documents need fileName and can only become the same kind (video to video, PDF to PDF). Replacing a protected file changes what buyers of the deliverable get right away. Files over 40 MB (base64) or 100 MB (url) are replaced in the dashboard media library. The older version stays until no published page uses it, then moves to the trash. Errors: FORBIDDEN, ASSET_NOT_FOUND (also for files in the trash), KIND_MISMATCH, REPLACE_NOT_AVAILABLE, VALIDATION, INVALID_IMAGE_DATA, INVALID_FILE_DATA, UNSUPPORTED_IMAGE_TYPE, UNSUPPORTED_FILE_TYPE, FILE_TOO_LARGE, FETCH_FAILED, STORAGE_LIMIT_EXCEEDED, SERVICE_UNAVAILABLE, UPLOAD_FAILED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
assetIdstring · pathrequiredAsset id from media.list.
Request body
base64stringFile bytes as base64 (plain or data: URL), max 40 MB decoded. Images: JPEG, PNG, WebP, GIF, SVG or HEIC, stored as-is (only downscaled when over Cloudflare limits). Other files need fileName.
urlstringPublic https URL to copy from: images up to 40 MB, other files up to 100 MB (use media.request_upload for larger files). Private networks and redirects to them are rejected.
fileNamestringName of the new version with extension (required when replacing a video, audio, PDF, document or protected file; must be the same kind as the current file).
Response 200
assetobjectrequiredThe file after the replacement (version increased).
asset.assetIdstringrequiredStable id of the file. Reference files by this id; it stays the same when media.replace uploads a new version.
asset.kind"image" | "video" | "audio" | "pdf" | "document"requiredWhat the file is (image, video, audio, pdf, document). Whether it is protected is a separate property: visibility.
asset.visibility"public" | "protected"requiredpublic: has a public URL and can be placed in pages, posts and collections. protected: no public URL; only served to entitled buyers through signed short-lived links (reference it by assetId in deliverable_units.*).
asset.tagsstring[]requiredOwner-defined tags (up to 20 per file, 1-40 characters each); filter media.list with tags.
asset.processingobject | nullrequiredBackground processing of the file itself (thumbnails, visibility moves, transcoding): null when the file is ready to use. Subscribe to the media.asset.ready and media.asset.failed webhooks instead of polling.
asset.processing.status"pending" | "processing" | "failed"requiredasset.processing.kindstring | nullrequiredBackground job kind (for example DOCUMENT_THUMBNAIL).
asset.processing.errorstring | nullrequiredError code when status is failed.
asset.processing.updatedAtstring | nullrequiredasset.streaming"none" | "processing" | "partial" | "ready"requiredAdaptive-streaming (HLS) version of a video: none (not placed anywhere that plays it, or not a video), processing (queued or transcoding), partial (lowest quality playable, higher qualities still transcoding), ready (all qualities done). Videos are transcoded when first placed in a page, post, collection or deliverable unit, or with media.update { streaming: true }; the streaming version counts toward storage (billableBytes).
asset.status"active" | "trash"requiredtrash = in the trash: not shown on the site, restorable with media.restore.
asset.missingbooleanrequiredtrue when the original no longer exists; places that use it show nothing (excluded from media.list by default unless includeMissing=true).
asset.versionintegerrequiredIncreases each time media.replace uploads a new version.
asset.urlstring | nullrequiredDisplay URL for showing the file to the user. It changes with each version, so never store it; null for protected files.
asset.thumbnailUrlstring | nullrequiredThumbnail or poster URL for public files; null for protected files (their thumbnails are only served to signed-in members).
asset.fileNamestring | nullrequiredasset.mimeTypestringrequiredasset.bytesinteger | nullrequirednull while the size of an older file is still being calculated.
asset.billableBytesintegerrequiredBytes counted toward the site's storage quota (images have a 256 KB minimum; active derived files such as watermarked versions are included).
asset.widthinteger | nullrequiredasset.heightinteger | nullrequiredasset.durationSecondsnumber | nullrequiredasset.pageCountinteger | nullrequiredasset.sourcestringrequiredWhere it was uploaded (page module, blog, product, media library…).
asset.inUsebooleanrequiredUsed by site content, products, deliverables, emails or a published snapshot.
asset.focusobject | nullrequiredDefault focal point of an image (0-1 on the original): image fields without their own framing crop around it. null when unset or centered.
asset.focus.xnumberrequiredasset.focus.ynumberrequiredasset.edgeColorstring | nullrequiredSolid border color (#rrggbb) when all four edges of the image share a uniform opaque color (for example a white-background product photo). null for transparent or multi-colored images.
asset.altTextstring | nullrequiredDefault alt text, used when the place that shows the image has no alt of its own.
asset.watermarkExemptbooleanrequiredtrue when the owner excluded this image from the site watermark.
asset.createdAtstringrequiredusagesobject[]requiredPlaces that now show the new version.
usages[].kindstringrequiredpageSection, pageShare, siteContent, brand, favicon, watermark, post, blogTaxonomy, product, productContent, deliverable, productCategory, collectionItem, floatingWidgets, messageTemplate or campaign.
usages[].labelstringrequiredWhere it is used, in words you can repeat to the user (page › section, post title, product name…).
awaitingPublishbooleanrequiredPublished pages still show the older version until the site is published again.
aspectRatioChangedbooleanrequired
Errors
FORBIDDEN403ASSET_NOT_FOUND404 · also for files in the trashKIND_MISMATCH422REPLACE_NOT_AVAILABLE422VALIDATION400INVALID_IMAGE_DATA400INVALID_FILE_DATA400UNSUPPORTED_IMAGE_TYPE422UNSUPPORTED_FILE_TYPE422FILE_TOO_LARGE413FETCH_FAILED422STORAGE_LIMIT_EXCEEDED403SERVICE_UNAVAILABLE503UPLOAD_FAILED502
MCP tool media_replace
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media/{assetId}/replace" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/photos/hero-2026.jpg"}'{
"asset": {
"assetId": "asset_example01",
"kind": "image",
"visibility": "public",
"tags": [],
"processing": null,
"status": "active",
"missing": false,
"version": 2,
"url": "https://media.kanorio.com/example/sunny-bakery-hero.jpg",
"thumbnailUrl": null,
"fileName": "sunny-bakery-hero.jpg",
"mimeType": "image/jpeg",
"bytes": 482133,
"streaming": "none",
"billableBytes": 482133,
"width": 2400,
"height": 1600,
"durationSeconds": null,
"pageCount": null,
"source": "IMAGE_CONTENT",
"inUse": true,
"focus": null,
"edgeColor": null,
"altText": null,
"watermarkExempt": false,
"createdAt": "2026-10-01T02:30:00.000Z"
},
"usages": [
{
"kind": "pageSection",
"label": "Home › Our story"
}
],
"awaitingPublish": true,
"aspectRatioChanged": false
}Move a file to the trash. It stops showing on the site right away (places that use it show nothing), still counts toward storage, and is deleted permanently after 30 days unless restored with media.restore. The response lists where it was used so you can tell the user what will look empty. Protected files that a deliverable uses cannot be trashed; remove them from the deliverable first (unused protected files can be trashed). Errors: FORBIDDEN, NOT_FOUND, PRODUCT_FILE_PROTECTED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
assetIdstring · pathrequiredAsset id from media.list.
Response 200
assetIdstringrequiredusagesobject[]requiredPlaces on the site that now show nothing; empty when the file was unused.
usages[].kindstringrequiredpageSection, pageShare, siteContent, brand, favicon, watermark, post, blogTaxonomy, product, productContent, deliverable, productCategory, collectionItem, floatingWidgets, messageTemplate or campaign.
usages[].labelstringrequiredWhere it is used, in words you can repeat to the user (page › section, post title, product name…).
deleteAfterstringrequiredISO time after which the file is deleted permanently.
Errors
FORBIDDEN403NOT_FOUND404PRODUCT_FILE_PROTECTED409
MCP tool media_trash
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media/{assetId}/trash" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"assetId": "asset_example01",
"usages": [
{
"kind": "pageSection",
"label": "Home › Our story"
}
],
"deleteAfter": "2026-11-05T08:00:00.000Z"
}Restore a media file
/sites/{id}/media/{assetId}/restoreRestore a file from the trash; it shows on the site again right away. An older version left by replacing a file comes back as a separate file with a new assetId (returned); old content keeps showing the current version. Errors: FORBIDDEN, NOT_FOUND, PRODUCT_FILE_PROTECTED, RESTORE_FAILED.
Parameters
idstring · pathrequiredWebsite id. Get it from sites.list or from sites.create.
assetIdstring · pathrequiredAsset id from media.list.
Response 200
assetIdstringrequired
Errors
FORBIDDEN403NOT_FOUND404PRODUCT_FILE_PROTECTED409RESTORE_FAILED422
MCP tool media_restore
curl -X POST "https://app.kanorio.com/api/v1/sites/{id}/media/{assetId}/restore" \
-H "Authorization: Bearer $KANORIO_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'{
"assetId": "asset_example01"
}