Upload a media file to the caller's library
const url = 'https://api.harperharbor.com/open/v1/image/upload';const form = new FormData();form.append('file', 'file');form.append('roleId', '2489E9AD-2EE2-8E00-8EC9-32D5F69181C0');form.append('folderIds', 'example');form.append('relativePath', 'example');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.harperharbor.com/open/v1/image/upload \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: multipart/form-data' \ --form file=@file \ --form roleId=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0 \ --form folderIds=example \ --form relativePath=exampleUpload one image, video, audio file or font. Harbor supports JPG, PNG, GIF, WebP, MP4, WebM, MP3, WAV, OGG, WOFF and WOFF2, up to 10 MiB per file. File-byte signatures and the declared media category must agree. Ownership and storage quota checks apply; stored media is immediately usable without content review. Responses do not include moderationState. Harbor assigns a stable libraryPrefix at https://
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
The file. Accepted types, detected from the bytes: image/jpeg, image/png, image/webp (incl. animated), image/gif, image/svg+xml, video/mp4, video/webm, audio/mpeg (ID3 or raw MPEG frame sync), audio/wav, audio/ogg, font/woff2, font/woff, application/wasm. text/javascript and application/json have no byte signature: they are accepted only when the part’s Content-Type or the file extension (.js, .mjs, .json) declares them, and the bytes must be UTF-8 text (JSON must parse as a whole). QuickTime video (.mov) is refused with invalid_param and detail.reason quicktime; upload MP4 instead. Max 104857600 bytes.
Optional card to attribute the file to (own card or any public card).
Optional; repeat the field for each folder id to bind the new item to.
Harbor extension when capabilities.relativePaths is true: author-provided relative file path, e.g. my-card/expressions/happy.png. Overrides fileName; the first selected folder name is prepended. Same-path uploads replace content. Other providers may not support this field.
Responses
Section titled “Responses”Stored item.
object
object
Library item id.
Public CDN URL of the stored file.
Detected MIME type (e.g. image/png, audio/wav, font/woff2).
0 for non-images.
0 for non-images.
Sanitized file name the object is stored under.
True when an existing item with the same file name was overwritten.
Example
{ "code": 0, "data": { "kind": "image" }}invalid_param (no file part / unreadable; also returned instead of 413 when a chunked/untruthful-Content-Length body exceeds 100 MiB while the multipart form is being read), invalid_file_type (unsupported bytes or undecodable image), quota_image_exceeded, quota_bytes_exceeded.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}unauthorized.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}file_too_large — request or file larger than 100 MiB.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}generic_error — storage or database failure.
object
Error code (snake_case) or, for legacy handlers, a free-text/localized message.
Human-readable sentence. Present only on middleware errors and /me, /me/wallet.
Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.
Only on a first-turn context_capacity_exceeded: the smallest context tier that fits the whole card with room to talk. Save it as context in player settings and resend. Absent when no tier fits.
The token budget of required_context_tier for the selected model, for display.
Only on a first-turn context_capacity_exceeded: whether saving trimConstantLore=true lets the card play at the current tier. Absent on mid-conversation capacity errors.
Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.
Only on insufficient_credits: the credits the wallet could set aside for it.
Examplegenerated
{ "error": "example", "message": "example", "retryable": true, "required_context_tier": 1, "required_context_tokens": 1, "constant_lore_trim_fits": true, "credits_needed": 1, "credits_available": 1}