On this page
File upload API
POST https://api.meowload.net/openapi/v1/uploads provides a common upload flow for audio, images and video. It returns an uploadId and, when needed, a signed URL for one PUT of the complete file.
Authenticate the API request with Authorization: Bearer <your API key>. Requesting an upload requires a positive API credit balance but does not consume credits. Business tasks check their own balance and are charged separately; see billing rules.
Workflow
- Compute the exact byte size and SHA-256 of the original file, then POST
/uploads. - If
uploadis present, send the complete raw file with PUT to its URL, preserving every returned header. Do not attach your MeowLoad API key. Wait for a successful 2xx response. - If
uploadis null, verified reusable content is already available for you; skip PUT. - Pass
uploadIddirectly to a supported task API. For transcription, usemediaType: "audio"and POST/transcriptionswith{uploadId, language?, speakerLabels?}.
There is no public complete endpoint, upload-status GET, multipart/chunk protocol, renewal, file-list or deletion endpoint. The task performs confirmation and media validation in the background; validation failures are reported through that task.
Audio, image and video uploads share this request, but the business API must accept the actual media. Currently transcription accepts only audio upload IDs; separate image/video task APIs are not introduced by this guide.
Limits
mediaType | Accepted content and limits |
|---|---|
audio | Decodable audio, up to 1 GiB (1,073,741,824 bytes) |
image | JPEG, PNG or WebP; up to 30 MiB (31,457,280 bytes) and 100 million pixels |
video | MP4; up to 500 MiB (524,288,000 bytes) and 600 seconds |
The declaration, filename and Content-Type do not establish the actual media type. Real bytes, size, digest and metadata are verified. A validated file may still fail a business task's model or input requirements.
A pure-audio M4A/MP4 container is audio. Attached cover art does not count as a real video track. A file with real video tracks cannot be submitted to transcription by calling it audio; extract the audio first, or use transcription's audio/video URL input.
Request
curl https://api.meowload.net/openapi/v1/uploads \
-H 'Authorization: Bearer <your API key>' \
-H 'Content-Type: application/json' \
-d '{"mediaType":"audio","fileName":"meeting.mp3","sizeBytes":12345678,"sha256":"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"}'The size and digest above are illustrative; compute them from your actual file.
| Field | Required | Meaning |
|---|---|---|
mediaType | Yes | audio, image or video |
fileName | No | Optional display filename, not a format guarantee |
sizeBytes | Yes | Exact positive integer byte size, within the selected type's limit |
sha256 | Yes | SHA-256 of all original bytes, encoded as 64 hexadecimal characters |
The application JSON body is limited to 8 KiB. Do not send file bytes in JSON, a storage path, model, price, internal asset ID or caller-chosen request ID. The upload request itself has no public idempotency parameter.
Response and PUT
HTTP 200:
{
"uploadId": "0123456789abcdefghijklmn",
"upload": {
"method": "PUT",
"url": "https://uploads.example.com/signed-object?signature=EXAMPLE",
"headers": {"Content-Type":"application/octet-stream"},
"expiresAt": "2030-01-01T01:00:00.000Z"
}
}Copy the complete returned URL and all its headers, including any checksum header. The headers shown here are examples, not a fixed header list. Send raw bytes, not multipart form data:
curl --request PUT '<upload.url>' \
--header 'Content-Type: application/octet-stream' \
--header '<each additional returned header>: <its exact value>' \
--data-binary @meeting.mp3The URL is valid for up to one hour, limited by its current upload session. upload.expiresAt describes this signed URL, not how long a verified file can be used. If the upload is interrupted, resend the same complete file while the URL is valid; there is no chunk resume. If it expires, request another upload. Never send different content to the old URL.
A successful PUT establishes delivery, not media validity. After its 2xx response, create the task directly; no ETag parsing, complete request or upload-status polling is required.
Verified-content reuse returns:
{"uploadId":"0123456789abcdefghijklmn","upload":null}Only this explicit null allows PUT to be skipped. It does not mean the task itself has completed.
Reuse and availability
One valid upload ID can be used by multiple tasks with different options, subject to user ownership, current source availability and each business's media rules. A new key owned by the same user can use the upload; another user's key cannot.
Each task POST creates a separate task and may incur its own charge, even with the same upload ID. The ID is a file identity, not a task idempotency key. Do not automatically retry task POST after a lost response. Save a returned task ID and retry only its free GET.
No additional “24 hours after upload ID creation” business cutoff is imposed. The original upload session and current source authorization still apply, and an upload ID is not a permanent storage guarantee. If the source is no longer usable, request a new upload. Existing saved transcription text remains queryable independently of source-file retention.
Node.js: stream the hash and upload the file
Node.js 24 provides a file-backed Blob, so this example does not load a 1 GiB file into memory. Keep the file unchanged between hashing and PUT. It submits the transcription once; continue polling as described in the transcription guide.
import { createHash } from "node:crypto"
import { createReadStream, openAsBlob } from "node:fs"
const base = "https://api.meowload.net/openapi/v1"
const auth = { Authorization: "Bearer <your API key>", "Content-Type": "application/json" }
const path = "./meeting.mp3"
const file = await openAsBlob(path)
const hash = createHash("sha256")
for await (const chunk of createReadStream(path)) hash.update(chunk)
const application = await fetch(`${base}/uploads`, {
method: "POST",
headers: auth,
body: JSON.stringify({ mediaType: "audio", fileName: "meeting.mp3", sizeBytes: file.size, sha256: hash.digest("hex") }),
})
if (!application.ok) throw new Error(`Upload request failed: ${application.status}`)
const { uploadId, upload } = await application.json()
if (upload !== null) {
const put = await fetch(upload.url, { method: upload.method, headers: upload.headers, body: file })
if (!put.ok) throw new Error(`PUT failed: ${put.status}`)
}
const created = await fetch(`${base}/transcriptions`, {
method: "POST", headers: auth,
body: JSON.stringify({ uploadId, language: "auto", speakerLabels: false }),
})
if (!created.ok) throw new Error(`Create failed: ${created.status}`)
console.log("Save this task ID:", (await created.json()).id)API request errors use {message, code?, detail?} with the appropriate non-2xx status. Real file-validation failures are delivered through the task, before any execution charge. Parameters and response fields are also in the API reference.