Skip to content

Start a browser direct-to-storage upload

POST
/open/v1/image/uploadIntent
curl --request POST \
--url https://api.harperharbor.com/open/v1/image/uploadIntent \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "byteSize": 1, "contentType": "example" }'

Alternative to /image/upload for browsers: the bytes go straight to object storage through a short-lived signed URL instead of through the provider’s API proxy, which cuts an upload off after a fixed time. Checks the per-account item and byte quotas against the declared size, then returns uploadUrl (HTTP PUT the raw file there with Content-Type set to the returned contentType; that header and the exact byteSize are part of the signature, so any other type or size is refused by storage) and uploadId (an opaque, account-bound, expiring credential to pass to /image/uploadComplete). Same 100 MiB per-file limit as /image/upload. Success body is the legacy wrapper {code:0,data:{...}}.

Media typeapplication/json
object
byteSize
required

Exact size of the file in bytes; uploadComplete rejects the upload if the stored object differs.

integer
contentType

Media type of the file you will upload, for example image/png, text/javascript or application/json. Defaults to image/png. Image, video, audio, font and WASM uploads are still identified from their bytes at uploadComplete; JavaScript and JSON have no byte signature, so they are accepted only when declared here. video/quicktime is refused (upload MP4).

string
Examplegenerated
{
"byteSize": 1,
"contentType": "example"
}

Signed upload target.

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

Opaque credential for /image/uploadComplete.

string
uploadUrl
required

HTTP PUT the raw file body here before expiresAt.

string format: uri
expiresAt
required

Unix seconds; both the URL and the credential stop working after this.

integer
maxBytes
required

Per-file limit in bytes.

integer
contentType
required

Send exactly this Content-Type on the PUT (always application/octet-stream); the real type is sniffed from the bytes at uploadComplete.

string
Example
{
"code": 0
}

invalid_param (missing or non-positive byteSize, or an unsupported contentType; for video/quicktime the error carries detail.reason quicktime); 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
}

file_too_large (over 104857600 bytes).

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
}