Skip to main content

Overview

The Ordinal API supports two ways to upload media:
  • URL upload — submit a publicly accessible URL and Ordinal downloads the file for you.
  • Local file upload — request signed credentials, then upload the file directly to the returned uploadUrl.
Both flows are asynchronous. You receive an upload job ID, poll until the status is ready, then use the returned assetId when creating posts.

Choosing a flow

Use caseEndpoint
The file is already hosted at a public URLPOST /uploads
The file lives on your machine, server, or in private storagePOST /uploads/prepare

Supported file types

TypeFormats
ImagesJPEG, PNG, GIF, WebP
VideosMP4, QuickTime (MOV)

File size limits

TypeMax size
Images10 MB
GIFs15 MB
Videos350 MB
Images cannot exceed 36 megapixels total resolution.

Upload statuses

StatusDescription
awaiting_uploadCredentials issued for a local file. Ordinal is waiting for the file to be POSTed.
pendingUpload is queued and waiting to be processed
processingFile is being downloaded and processed
readyUpload is complete. Use the assetId to attach to posts
failedUpload failed. Check the error field for details
expiredUpload expired before being used in a post

URL upload

Use this flow when the file is already hosted somewhere Ordinal can reach over the public internet.
1

Submit the file URL

POST to /uploads with the publicly accessible URL of the file.
2

Receive an upload job ID

The API returns an upload job ID with pending status.
3

Poll for status

Poll GET /uploads/{id} until the status changes to ready.
4

Use the asset ID

Once ready, use the returned assetId in the assetIds array when creating posts.

Create a URL upload

curl -X POST "https://app.tryordinal.com/api/v1/uploads" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/images/product-photo.jpg"}'

Response

{
  "id": "d4f8e2a1-3b7c-4e9d-8f2a-1c5b9e7d3a6f",
  "status": "pending",
  "createdAt": "2026-02-04T10:30:00.000Z"
}
The file URL must be publicly accessible. URLs that require authentication or sit behind a firewall will fail.

Local file upload

Use this flow when the file is on disk, in private storage, or otherwise not reachable from the public internet. Ordinal returns signed credentials that let your client upload the bytes directly to the returned uploadUrl.
1

Prepare the upload

POST to /uploads/prepare with the file’s filename, mimetype, and size in bytes.
2

Upload the file

POST the file to the returned uploadUrl as multipart/form-data with three form fields: params (the JSON string from the prepare response), signature (the signature from the prepare response), and file (the local file bytes). Copy params and signature verbatim — do not modify them. Ordinal is notified automatically when processing finishes.
3

Poll for status

Poll GET /uploads/{id} until the status changes to ready.
4

Use the asset ID

Once ready, use the returned assetId in the assetIds array when creating posts.

Prepare the upload

curl -X POST "https://app.tryordinal.com/api/v1/uploads/prepare" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "product-photo.jpg",
    "mimetype": "image/jpeg",
    "size": 245678
  }'

Response

{
  "id": "d4f8e2a1-3b7c-4e9d-8f2a-1c5b9e7d3a6f",
  "status": "awaiting_upload",
  "uploadUrl": "https://upload.example.com",
  "params": "{\"auth\":{\"key\":\"...\",\"expires\":\"2026-02-04T11:30:00.000Z\"},\"template_id\":\"...\",\"fields\":{\"uploadJobId\":\"...\",\"workspaceId\":\"...\",\"storagePath\":\"...\"}}",
  "signature": "sha384:abc123...",
  "expiresAt": "2026-02-04T11:30:00.000Z",
  "createdAt": "2026-02-04T10:30:00.000Z"
}
The params value is a signed JSON string. It includes auth (with key and expires), template_id, and fields (uploadJobId, workspaceId, storagePath). The credentials are short‑lived — upload the file before expiresAt.

Upload the file

POST the file to uploadUrl as multipart/form-data with three form fields. Use the params and signature values from the prepare response exactly as returned — do not parse, reformat, or regenerate them.
Form fieldValue
paramsThe params string from the prepare response
signatureThe signature string from the prepare response
fileThe local file bytes
curl -X POST "https://upload.example.com" \
  -F 'params={"auth":{"key":"...","expires":"2026-02-04T11:30:00.000Z"},"template_id":"...","fields":{"uploadJobId":"...","workspaceId":"...","storagePath":"..."}}' \
  -F 'signature=sha384:abc123...' \
  -F "file=@./video.mp4"
In practice, substitute uploadUrl, params, and signature with the values returned by /uploads/prepare. Here’s the same step in Node.js:
import fs from "node:fs";

const form = new FormData();
form.append("params", prepare.params);
form.append("signature", prepare.signature);
form.append("file", new Blob([fs.readFileSync("./video.mp4")]), "video.mp4");

await fetch(prepare.uploadUrl, { method: "POST", body: form });
You don’t need to do anything else after the upload completes — Ordinal is notified automatically and transitions the job to processing and then ready.

Checking upload status

Poll the upload endpoint to check when processing is complete:
curl -X GET "https://app.tryordinal.com/api/v1/uploads/d4f8e2a1-3b7c-4e9d-8f2a-1c5b9e7d3a6f" \
  -H "Authorization: Bearer YOUR_API_KEY"

Ready response

When the upload is ready, the response includes the assetId and file metadata:
{
  "id": "d4f8e2a1-3b7c-4e9d-8f2a-1c5b9e7d3a6f",
  "status": "ready",
  "assetId": "a7c2e4b9-5d1f-4a8e-9c3b-2f6d8e1a4b7c",
  "filename": "product-photo.jpg",
  "mimetype": "image/jpeg",
  "size": 245678,
  "width": 1920,
  "height": 1080,
  "duration": null,
  "expiresAt": "2026-02-11T10:30:00.000Z",
  "createdAt": "2026-02-04T10:30:00.000Z",
  "readyAt": "2026-02-04T10:30:15.000Z"
}

Failed response

If the upload fails, the response includes an error message:
{
  "id": "d4f8e2a1-3b7c-4e9d-8f2a-1c5b9e7d3a6f",
  "status": "failed",
  "error": "Failed to download file: HTTP 404",
  "createdAt": "2026-02-04T10:30:00.000Z"
}

Using assets in posts

Include the assetId in the assetIds array when creating a post:
{
  "title": "Product Launch",
  "publishAt": "2026-02-10T14:00:00.000Z",
  "status": "Scheduled",
  "linkedIn": {
    "profileId": "b3e7f1c9-2d4a-4f8b-a6c1-9e5d7b2f8a3c",
    "copy": "Check out our new product!",
    "assetIds": ["a7c2e4b9-5d1f-4a8e-9c3b-2f6d8e1a4b7c"]
  }
}

Platform attachment limits

PlatformMax attachments
X (Twitter)4
LinkedIn20

Important notes

Uploads expire within 24 hours if not used in a post. Once an upload expires, create a new upload job.
For videos, the duration field in the ready response contains the video length in seconds. For images, this field is null.