Media uploads
Upload bytes through OpenPost's storage-safe HTTP flow.
Media uploads usually use three requests. Your integration creates an upload session, sends bytes to its storage target, then completes the session. If OpenPost finds identical ready media in the workspace, it returns deduped: true and the returned media_id is ready to use without the upload and completion requests. The examples use Bash, curl, and jq.
1. Create a session
OPENPOST_BASE="https://app.openpo.st"
WORKSPACE_ID="ws_..."
SESSION_JSON="$(curl "$OPENPOST_BASE/api/v1/media/upload-session" \
-X POST \
-H "Authorization: Bearer $OPENPOST_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cover-2026-09-19" \
--data "$(jq -n \
--arg workspace_id "$WORKSPACE_ID" \
--arg filename "cover.png" \
--arg mime_type "image/png" \
--arg alt_text "Product import screen with the completed file list" \
'{workspace_id: $workspace_id, filename: $filename, mime_type: $mime_type, size: 48231, alt_text: $alt_text}')")"
MEDIA_ID="$(printf '%s' "$SESSION_JSON" | jq -r '.media_id')"
DEDUPED="$(printf '%s' "$SESSION_JSON" | jq -r '.deduped')"The response includes media_id, deduped, complete_url, and an upload object with a method, URL, and headers. The following steps only run when DEDUPED is false.
2. Send the bytes
Use the exact method and headers from upload. An absolute target is external storage, so do not send the OpenPost bearer token to it. A relative target is an authenticated OpenPost endpoint.
if [ "$DEDUPED" = "false" ]; then
UPLOAD_METHOD="$(printf '%s' "$SESSION_JSON" | jq -r '.upload.method')"
UPLOAD_URL="$(printf '%s' "$SESSION_JSON" | jq -r '.upload.url')"
UPLOAD_HEADERS=()
while IFS= read -r header; do
UPLOAD_HEADERS+=(-H "$header")
done < <(printf '%s' "$SESSION_JSON" | jq -r '.upload.headers | to_entries[] | "\(.key): \(.value)"')
case "$UPLOAD_URL" in
http://* | https://*)
UPLOAD_TARGET="$UPLOAD_URL"
UPLOAD_EXTERNAL=true
;;
/*)
UPLOAD_TARGET="$OPENPOST_BASE$UPLOAD_URL"
UPLOAD_EXTERNAL=false
;;
*)
printf 'Unsupported upload URL: %s\n' "$UPLOAD_URL" >&2
exit 1
;;
esac
if [ "$UPLOAD_EXTERNAL" = "true" ]; then
curl "$UPLOAD_TARGET" \
-X "$UPLOAD_METHOD" \
"${UPLOAD_HEADERS[@]}" \
--data-binary @cover.png
else
curl "$UPLOAD_TARGET" \
-X "$UPLOAD_METHOD" \
-H "Authorization: Bearer $OPENPOST_TOKEN" \
"${UPLOAD_HEADERS[@]}" \
--data-binary @cover.png
fi
fi3. Complete the session
Complete the session with the returned media_id, your workspace ID, and your OpenPost bearer token. Completion starts the normal validation and media-processing flow.
if [ "$DEDUPED" = "false" ]; then
RESULT_JSON="$(curl "$OPENPOST_BASE/api/v1/media/upload-session/$MEDIA_ID/complete" \
-X POST \
-H "Authorization: Bearer $OPENPOST_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"workspace_id\":\"$WORKSPACE_ID\"}")"
MEDIA_ID="$(printf '%s' "$RESULT_JSON" | jq -r '.id')"
fiUse the completion response's id in Publication or Rendition media fields. When the create-session response is deduplicated, use its media_id instead and skip steps 2 and 3.
The create upload session reference contains the exact request and response schemas and links to completion. JavaScript and TypeScript integrations can use media.upload() to run all three steps.
Fit an image to a destination
After uploading an image, call POST /api/v1/media/{id}/prepare-image with a connected account_id, its explicit output_profile from capability resolution, and any destination settings used for that resolution. The account and image must belong to the same workspace, and the caller needs editor access. A scoped API token needs media:write.
{
"account_id": "account_...",
"output_profile": "instagram.feed",
"alt_text": "The completed product import screen"
}OpenPost resolves the connected account's limits, then pads and downscales the image without cropping. It may re-encode the copy to fit accepted formats and byte limits. JPEG padding is white; PNG and WebP preserve transparent padding. Animated images are rejected to avoid losing frames. Inputs and image allocations are bounded; an image that cannot fit returns an error.
The response contains status: "ready" and a separate media object, including its ID, known width and height, and parent_media_id. Preparation finishes before the response. Attach the returned ID explicitly. Existing posts and the original asset are unchanged. Repeating the same preparation reuses a matching ready copy when available. This operation prepares an image; validate the complete post separately before publishing.