upload_media_batch

Upload up to 10 photos in one request.

POST/v1/media/upload-batch
REST onlyScope: writeBetaSince 2026-09-30
Markdown

multipart/form-data with one form field per file and a manifest field: a JSON array with one item per file, naming the form field (file) and the same fields as upload_media ({ file, ownerType, ownerId, caption, credit, alt, ... } or { file, target: "blob" }). Every file counts against the upload limit and succeeds or fails on its own; the response lists each result. The status is 201 when at least one file was stored and 400 when every file failed, with the same body. At most 10 files and 19 MB per request.

curl -X POST "https://sceniq.earth/api/v1/media/upload-batch" \
  -H "Authorization: Bearer $SCENIQ_API_KEY" \
  -F 'manifest=[{"file":"overlook","ownerType":"spot","ownerId":"k1709e2vj4sk8q6x0d3m5r7t9w1y3b5c","caption":"Grand Prismatic Spring from the overlook, mid morning","credit":"Mara Lindgren","alt":"A deep blue pool ringed with orange and yellow, steam drifting over the boardwalk below","shows":{"kind":"spot"},"takenAt":{"lat":44.5199,"lon":-110.8406,"precision":"gps"}},{"file":"boardwalk","ownerType":"spot","ownerId":"k1709e2vj4sk8q6x0d3m5r7t9w1y3b5c","caption":"Grand Prismatic Spring and Midway Geyser Basin from above","credit":"Brocken Inaglory","licence":"CC BY-SA 3.0","sourcePageUrl":"https://commons.wikimedia.org/wiki/File:Grand_Prismatic_Spring_and_Midway_Geyser_Basin_from_above.jpg","creditUrl":"http://commons.wikimedia.org/wiki/User:Brocken_Inaglory"},{"file":"yellowstone-cover","target":"blob"}]' \
  -F "overlook=@overlook.jpg" \
  -F "boardwalk=@boardwalk.jpg" \
  -F "yellowstone-cover=@yellowstone-cover.jpg"

Form fields

  • manifestarray of valuesrequired

    JSON array, one item per file: { file (the form field name), ownerType, ownerId, and any upload_media field }, or { file, target: "blob" }.

  • <file field>stringrequired

    One form field per file, named as the manifest item's file.

Returns

201 Created with BatchUpload: The results of upload_media_batch.

  • uploadednumberrequired

    Files stored.

  • failednumberrequired

    Files refused.

  • resultsarray of objectsrequired

    One result per manifest item, in order.

    Show 23 fields
    • filestring or nullrequired

      The form field name from the manifest.

    • okbooleanrequired

      Whether this file was stored.

    • mediaIdstring · photo idoptional

      The new photo's id (media uploads).

    • storageIdstring · stored file idoptional

      The stored file's id (target blob).

    • urlstring or nulloptional

      The stored photo.

    • thumbUrlstring or nulloptional

      The thumbnail.

    • ownerTypestringoptional

      What the photo belongs to.

      spotproductcollectionlisting
    • ownerIdstringoptional

      The owner row's id.

    • captionstring or nulloptional

      Caption.

    • creditstring or nulloptional

      Photographer credit.

    • ordernumberoptional

      Position in the owner's gallery.

    • contentTypestringoptional

      The stored file's type.

    • widthnumberoptional

      Stored width in pixels.

    • heightnumberoptional

      Stored height in pixels.

    • bytesnumberoptional

      Stored size in bytes.

    • thumbBytesnumber or nulloptional

      The thumbnail's size in bytes.

    • originalBytesnumberoptional

      The uploaded file's size in bytes.

    • optimizedbooleanoptional

      false when the original was kept.

    • metadataStrippedbooleanoptional

      true when metadata was removed.

    • capturedOnstring or nulloptional

      Capture date.

    • usestringoptional

      Where a storageId goes.

    • warningsarray of stringsoptional

      Notes about things the write accepted but you should look at (a plain http link). Present only when there are any.

    • errorobjectoptional

      Why the file was refused.

      Show 2 fields
      • codestringrequired

        The error code (see Errors).

      • messagestringrequired

        What went wrong.

Response 201
{
  "uploaded": 3,
  "failed": 0,
  "results": [
    {
      "file": "overlook",
      "ok": true,
      "mediaId": "kg71s3d5f7g9h1j3k5k7z9x1c3v5b7n9",
      "url": "https://quiet-heron-512.convex.cloud/api/storage/grand-prismatic-overlook.webp",
      "thumbUrl": "https://quiet-heron-512.convex.cloud/api/storage/grand-prismatic-overlook-640.webp",
      "ownerType": "spot",
      "ownerId": "k1709e2vj4sk8q6x0d3m5r7t9w1y3b5c",
      "caption": "Grand Prismatic Spring from the overlook, mid morning",
      "credit": "Mara Lindgren",
      "width": 2048,
      "height": 1365,
      "order": 1024,
      "contentType": "image/webp",
      "bytes": 684312,
      "thumbBytes": 61240,
      "originalBytes": 5412880,
      "optimized": true,
      "metadataStripped": true,
      "capturedOn": "2026-09-14"
    },
    {
      "file": "boardwalk",
      "ok": true,
      "mediaId": "kg7p2p4j6v8y0t2r4e6w8q0a2s4d6f8g",
      "url": "https://quiet-heron-512.convex.cloud/api/storage/grand-prismatic-aerial.webp",
      "thumbUrl": "https://quiet-heron-512.convex.cloud/api/storage/grand-prismatic-aerial-640.webp",
      "ownerType": "spot",
      "ownerId": "k1709e2vj4sk8q6x0d3m5r7t9w1y3b5c",
      "caption": "Grand Prismatic Spring and Midway Geyser Basin from above",
      "credit": "Brocken Inaglory",
      "width": 2048,
      "height": 1365,
      "order": 2048,
      "contentType": "image/webp",
      "bytes": 597004,
      "thumbBytes": 55872,
      "originalBytes": 2811264,
      "optimized": true,
      "metadataStripped": false,
      "capturedOn": null,
      "warnings": [
        "creditUrl uses http, not https: http://commons.wikimedia.org/wiki/User:Brocken_Inaglory"
      ]
    },
    {
      "file": "yellowstone-cover",
      "ok": true,
      "storageId": "kg2a8c4e0g6j2k8m4p0q6s2v8w4y0a6c",
      "contentType": "image/webp",
      "width": 2048,
      "height": 1152,
      "bytes": 402118,
      "originalBytes": 3170912,
      "optimized": true,
      "metadataStripped": true,
      "use": "Pass storageId as coverStorageId (update_chapter) or photoStorageId (set_chapter_intro sections)"
    }
  ]
}

Errors

Every error is { error: { code, message } }. Act on the code.

  • 400invalid_argumentA required field is missing, a field is unknown, or a value does not match its type (the Convex validator's path and value are in the message).
  • 400invalid_requestA writer refused a value: a map link on a host that is not allowed, a title that is too long, a date that does not exist, too many items in a list. The message is the writer's own sentence.
  • 400unsupported_imageAn upload's bytes are not a JPEG, PNG or WebP file, or their dimensions cannot be read. The bytes are checked, not the Content-Type header.
  • 400image_too_largeAn upload is more than 6,000 px on a side.
  • 401unauthenticatedThe request carries no key. Send it as Authorization: Bearer sk_sceniq_... (or X-Api-Key).
  • 401invalid_api_keyThe key is unknown, revoked or expired.
  • 403api_scope_requiredA read-only key called a write operation, or a key without the publish option called publish_changes.
  • 403forbiddenThe key belongs to a creator account that is no longer active.
  • 413payload_too_largeA JSON body over 2 MB, an image over 6 MB, a batch upload over 19 MB, or an MCP message over 2 MB.
  • 429rate_limitedOver a limit: requests per key per minute, per creator per hour, uploads per key per minute, or check_links per guide per hour.
  • 500internal_errorAn unexpected failure. The message is hidden on purpose.