OpenAPI reference · Media
Add your own image to the media library
/mediaRegister an image the customer supplied as a media-library asset of the project, so it can be attached to a post.
This is the producer the library was missing on this surface.
POST /v1/uploads accepts image/*, but its handle is consumed
by POST /v1/sources and POST /v1/knowledge — neither of
which produces media — and PATCH /v1/posts/{postId}/media
takes either a library img_… id or a stored file key, never an
upload handle. Every other library writer is first-party (the
dashboard, the LinkedIn sync, POST /v1/media/generations).
Three ways in, exactly one of them per call:
sourceUrl— a publichttps://address of the image; Scripe downloads it server-side (up to 25 MB), so the bytes never pass through the request. The address must open without signing in and serve the image file itself. Only https on port 443 to a public address is fetched: IP literals, private, loopback, link-local and metadata addresses are refused, every redirect hop (at most three) is judged the same way, and the type is read from the bytes (PNG, JPEG, GIF, WebP, AVIF), never from the server'sContent-Type. A refusal is a 422 naming why; a file over the cap is a 413.content_base64— the bytes inline, for SMALL files only (≤ ~3 MB decoded ≈ 4 MB as base64). Larger request bodies are rejected at the platform edge (~4.5 MB on the wire) with a bare HTTP 413 that carries no error envelope, so pre-check the file size instead of retrying inline. An optionalsha256of the decoded bytes is verified after decode.uploadId— anupl_…handle fromPOST /v1/uploads, after the bytes have been PUT to its signed URL. Preferred for anything larger than the inline cap; the signed PUT takes the full 25 MB image cap.
The stored object is copied into the project's
image-library/ prefix with a file extension, which is what the
publish step needs in order to tell an image from a document.
The response is the same MediaAsset object GET /v1/media
lists.
The asset is returned status: PROCESSING and is attachable
immediately — attach resolves the stored file, not the status.
Cloudflare re-hosting and vision tagging run asynchronously;
GET /v1/media (READY only) lists it once they finish.
Sending the same bytes, or the same upload handle, twice returns
the asset already created rather than a duplicate row. Re-sending
the bytes of an asset that was deleted (MCP delete_media_asset)
restores it under a freshly minted storage key, so an object an
existing post already points at is never overwritten; the rare
case where that restore cannot be recorded answers 409.
Images only: SVG is refused (it is a script-bearing document and
LinkedIn rejects it), and a non-image content type is refused
naming the endpoint that takes it. Requires media:write.
Authorization
AuthorizationstringrequiredBearer token in the Authorization header.
Pass
Authorization: Bearer scripe_sk_live_<...>(orscripe_sk_test_<...>for test keys) on every request. Keys are scoped to a single workspace and can be revoked from the Scripe dashboard.The same header also accepts an OAuth 2.1 access token (
scripe_oat_*); both credentials share one scope vocabulary and every operation below documents the scope it requires. An API key can hold every scope named on this surface exceptwebhooks:manage, which is grantable to OAuth tokens only today — the webhook-endpoint operations answer403 scope_missingto every API key. Operations that name no scope accept any valid token of the workspace.
Header parameters
Scripe-Api-VersionstringPin the API version. Format
YYYY-MM-DD. Omit to receive the currently rolling default. Unknown versions return400 version_unsupported.
Request bodyapplication/json
projectIdstringProject whose library receives the image. Optional for an OAuth principal with a default project pinned at consent time.
sourceUrlstringPublic
https://address of the image, fetched server-side (see the operation description for what is refused). The file name defaults to the URL's last path segment. Mutually exclusive withcontent_base64anduploadId;sha256is refused beside it.content_base64stringBase64-encoded image bytes, ≤ ~3 MB decoded (larger request bodies are rejected at the platform edge as a bare HTTP 413 — use the
uploadIdpath instead). Mutually exclusive withuploadIdandsourceUrl.sha256stringOptional hex SHA-256 of the DECODED image bytes (inline path only; refused beside
uploadId). Strongly recommended when the caller can compute it — base64 relayed through model output corrupts silently, and a digest mismatch is rejected naming both digests instead of storing the wrong bytes.uploadIdstringupl_…handle fromPOST /v1/uploads, after the bytes have been PUT to its signed URL. Mutually exclusive withcontent_base64. A handle whose object does not exist is a 422, not a 404 — the handle is valid, the upload never happened.fileNamestringOriginal filename. On the inline path it is also how the content type is inferred when
mimeTypeis omitted.mimeTypestringImage MIME type for the inline path. Ignored on the
uploadIdpath, where the stored object's own content type (bound into the signature at mint time) is authoritative.altstringAlt text, carried onto the post at attach time.
titlestring
Responses
- 200
The created (or already-existing) asset.
- 400
Malformed request (bad cursor, bad limit, etc.).
- 401
Missing, malformed, expired, or revoked API key.
- 403
Plan not eligible, scope missing, or workspace mismatch.
- 404
Resource not found in this workspace.
- 409
A deleted asset could not be restored: its record of superseded storage keys is full (too many delete/restore cycles) or unreadable. Both keep the earlier copies reachable for erasure, so the restore is refused rather than dropping them. See
conflict. - 413
Request body exceeds the size cap.
- 422
Body shape was JSON but failed validation (
unprocessable). - 429
Sliding-window rate limit exceeded.
Example request
curl --request POST \
--url 'https://api.scripe.io/v1/media' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"projectId": "proj_a1b2c3d4e5f6g7h8",
"sourceUrl": "https://images.example.com/keynote-stage.jpg",
"content_base64": "string",
"sha256": "string",
"uploadId": "upl_org_2aSH--30e8e643de8b4f01",
"fileName": "keynote-stage.jpg",
"mimeType": "image/png",
"alt": "string",
"title": "string"
}'Example response (200)
{
"data": {
"id": "img_a1b2c3d4e5f6g7h8",
"projectId": "string",
"source": "UPLOAD",
"status": "string",
"title": "string",
"fileName": "string",
"mimeType": "string",
"width": 0,
"height": 0,
"alt": "string",
"tags": [
"string"
],
"aiCaption": "string",
"reusability": "string",
"generatedAssetKind": "string",
"provenance": {
"provider": "string",
"authorName": "string",
"permalink": "string"
},
"displayUrl": "string",
"createdAt": "string"
}
}