Skip to content

Register a file that was PUT to the signed URL

POST
/open/v1/image/uploadComplete
curl --request POST \
--url https://api.harperharbor.com/open/v1/image/uploadComplete \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "uploadId": "example", "roleId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "folderIds": [ "example" ], "fileName": "example", "relativePath": "example" }'

Complete an upload after validating its bytes, file type, dimensions and quota. Stored media is immediately usable without content review. The response matches /image/upload and does not include moderationState. Harbor assigns a stable libraryPrefix at https:///u/. Named uploads use /; each path segment is URL-encoded. The first selected folder name prefixes that path. Original names and subdirectories are preserved (Unicode NFC); empty/dot/traversal segments, backslashes, controls and overly long paths are rejected. The same path replaces content atomically while preserving URL, asset ID and existing folder/card references. Completed-upload retries do not restore older content. Existing unnamed URLs remain valid.

Media typeapplication/json
object
uploadId
required

From /image/uploadIntent.

string
roleId

Optional card to attribute the file to (own card or any public card).

string format: uuid
folderIds

Optional folder ids to bind the new item to.

Array<string>
fileName

Original file name. The object is stored as u/<numeric user id>/<fileName> (path, control characters and #%?&<>:"|* are stripped or replaced; the extension is aligned to the sniffed type). Uploading the same name again replaces the existing item in place: same item id, same URL. Omitted → a generated name.

string
relativePath

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.

string
<= 1024 characters
Examplegenerated
{
"uploadId": "example",
"roleId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
"folderIds": [
"example"
],
"fileName": "example",
"relativePath": "example"
}

Stored item.

Media typeapplication/json
object
code
required
integer
Allowed values: 0
data
required
object
imageId
required

Library item id.

integer format: int64
imageUrl
required

Public CDN URL of the stored file.

string format: uri
kind
required
string
Allowed values: image video audio font code data
mimeType
required

Detected MIME type (e.g. image/png, audio/wav, font/woff2).

string
byteSize
required
integer format: int64
pixelWidth
required

0 for non-images.

integer
pixelHeight
required

0 for non-images.

integer
fileName

Sanitized file name the object is stored under.

string
replaced

True when an existing item with the same file name was overwritten.

boolean
Example
{
"code": 0,
"data": {
"kind": "image"
}
}

invalid_param; upload_invalid (credential not issued to this account, or tampered); upload_expired (credential older than its lifetime; start over); upload_size_mismatch (stored object is not byteSize bytes); invalid_file_type; quota_image_exceeded; quota_bytes_exceeded.

Media typeapplication/json
object
error
required

Error code (snake_case) or, for legacy handlers, a free-text/localized message.

string
message

Human-readable sentence. Present only on middleware errors and /me, /me/wallet.

string
retryable

Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.

boolean
required_context_tier

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.

integer
>= 1 <= 5
required_context_tokens

The token budget of required_context_tier for the selected model, for display.

integer
constant_lore_trim_fits

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.

boolean
credits_needed

Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.

integer
credits_available

Only on insufficient_credits: the credits the wallet could set aside for it.

integer
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.

Media typeapplication/json
object
error
required

Error code (snake_case) or, for legacy handlers, a free-text/localized message.

string
message

Human-readable sentence. Present only on middleware errors and /me, /me/wallet.

string
retryable

Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.

boolean
required_context_tier

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.

integer
>= 1 <= 5
required_context_tokens

The token budget of required_context_tier for the selected model, for display.

integer
constant_lore_trim_fits

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.

boolean
credits_needed

Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.

integer
credits_available

Only on insufficient_credits: the credits the wallet could set aside for it.

integer
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
}

upload_missing (nothing was PUT to the signed URL, or it was already consumed).

Media typeapplication/json
object
error
required

Error code (snake_case) or, for legacy handlers, a free-text/localized message.

string
message

Human-readable sentence. Present only on middleware errors and /me, /me/wallet.

string
retryable

Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.

boolean
required_context_tier

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.

integer
>= 1 <= 5
required_context_tokens

The token budget of required_context_tier for the selected model, for display.

integer
constant_lore_trim_fits

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.

boolean
credits_needed

Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.

integer
credits_available

Only on insufficient_credits: the credits the wallet could set aside for it.

integer
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.

Media typeapplication/json
object
error
required

Error code (snake_case) or, for legacy handlers, a free-text/localized message.

string
message

Human-readable sentence. Present only on middleware errors and /me, /me/wallet.

string
retryable

Whether retrying may succeed. Present only on middleware errors, /me, /me/wallet and service/share routes.

boolean
required_context_tier

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.

integer
>= 1 <= 5
required_context_tokens

The token budget of required_context_tier for the selected model, for display.

integer
constant_lore_trim_fits

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.

boolean
credits_needed

Only on insufficient_credits: the credits this turn needs before it can start. Absent when the refusal came without an estimate.

integer
credits_available

Only on insufficient_credits: the credits the wallet could set aside for it.

integer
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
}