Meet the new Scripe, live on October 7.Register

OpenAPI reference · Media

Add your own image to the media library

POST/media

Register 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).

Two ways in, exactly one of them per call:

  • 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 optional sha256 of the decoded bytes is verified after decode.
  • uploadId — an upl_… handle from POST /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

  • Authorizationstringrequired

    Bearer token in the Authorization header.

    Pass Authorization: Bearer scripe_sk_live_<...> (or scripe_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 except webhooks:manage, which is grantable to OAuth tokens only today — the webhook-endpoint operations answer 403 scope_missing to every API key. Operations that name no scope accept any valid token of the workspace.

Header parameters

  • Scripe-Api-Versionstring

    Pin the API version. Format YYYY-MM-DD. Omit to receive the currently rolling default. Unknown versions return 400 version_unsupported.

Request bodyapplication/json

  • projectIdstring

    Project whose library receives the image. Optional for an OAuth principal with a default project pinned at consent time.

  • content_base64string

    Base64-encoded image bytes, ≤ ~3 MB decoded (larger request bodies are rejected at the platform edge as a bare HTTP 413 — use the uploadId path instead). Mutually exclusive with uploadId.

  • sha256string

    Optional 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.

  • uploadIdstring

    upl_… handle from POST /v1/uploads, after the bytes have been PUT to its signed URL. Mutually exclusive with content_base64. A handle whose object does not exist is a 422, not a 404 — the handle is valid, the upload never happened.

  • fileNamestring

    Original filename. On the inline path it is also how the content type is inferred when mimeType is omitted.

  • mimeTypestring

    Image MIME type for the inline path. Ignored on the uploadId path, where the stored object's own content type (bound into the signature at mint time) is authoritative.

  • altstring

    Alt 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.

  • 422

    Body shape was JSON but failed validation (unprocessable).

  • 429

    Sliding-window rate limit exceeded.

Example request

bash
curl --request POST \
  --url 'https://api.scripe.io/v1/media' \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{
  "projectId": "proj_a1b2c3d4e5f6g7h8",
  "content_base64": "string",
  "sha256": "string",
  "uploadId": "upl_org_2aSH--30e8e643de8b4f01",
  "fileName": "keynote-stage.jpg",
  "mimeType": "image/png",
  "alt": "string",
  "title": "string"
}'

Example response (200)

json
{
  "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"
  }
}