Media

Upload files, list the media library, and attach media to a post.

Media endpoints use the posts scope, because media only exists to end up on a post.

Upload

POST /v1/media/upload

Send multipart/form-data with the files under the files field:

curl -X POST https://api.fopost.com/v1/media/upload \
  -H "X-API-Key: $FOPOST_API_KEY" \
  -F "[email protected]" \
  -F "workspaceId=7d2b8c11-4e5a-4a8f-b0d9-3c5e6f7a8b90"
LimitValue
Files per request5
Size per fileYour account's limit, at most 100 MB on this route
Typesjpeg, png, gif, webp, mp4, mov, webm, pdf, txt, csv

The declared type is verified against the actual file contents, so renaming an executable to .png does not get it through.

This route carries the whole request through the API's edge, which refuses any request body over 100 MB before it reaches FoPost. A file that large on this route gets the edge's own 413 page, not a FoPost error, so keep multipart under 100 MB per file and send anything bigger through direct upload, which has no such cap. Within 100 MB, a file over your account's limit answers 413 with file_too_large and max_file_bytes.

Pass workspaceId to also save the files to that workspace's media library, where they are reusable and count against the account's storage allowance. Leave it out for a one-off attachment.

Limits

GET /v1/media/limits?workspace_id=7d2b8c11-4e5a-4a8f-b0d9-3c5e6f7a8b90

The largest file your account can upload grows with the number of connected accounts on the billing account:

Connected accountsLargest file
Up to 2100 MB
Up to 7400 MB
Up to 152 GB
16 and more5 GB

The Large Uploads add-on (Settings → Billing) lifts any account to 5 GB for a flat monthly fee. The endpoint answers the ceiling in force, what set it, the multipart ceiling, and whether direct upload is available:

{
  "max_file_bytes": 419430400,
  "source": "accounts",
  "connected_accounts": 5,
  "large_uploads_enabled": false,
  "next_tier": { "up_to_accounts": 15, "max_file_bytes": 2147483648 },
  "multipart_max_file_bytes": 104857600,
  "max_files_per_upload": 5,
  "direct_upload_available": true,
  "allowed_types": ["image/jpeg", "image/png", "..."]
}

Read it before an upload to pick the route and to show the person the real number. source is accounts, add_on, or bypass.

Direct upload

For anything over 100 MB, or a client that should not proxy bytes through your server, upload straight to storage through a signed URL. The bytes never pass through the API, so the only ceiling is your account's limit, up to 5 GB in one PUT. It is three calls.

1. Sign the upload.

POST /v1/media/presign
{
  "workspaceId": "7d2b8c11-4e5a-4a8f-b0d9-3c5e6f7a8b90",
  "filename": "launch.mp4",
  "mimeType": "video/mp4",
  "size": 18350080
}

folderId, brandRole, brandVoiceId, personConsent, and origin are accepted here too, with the same meaning as on upload, so the finished item lands exactly where a multipart upload would.

{
  "upload_id": "b1a3…",
  "upload_url": "https://…",
  "method": "PUT",
  "headers": { "Content-Type": "video/mp4" },
  "expires_at": "2026-09-19T14:00:00.000Z",
  "max_file_bytes": 419430400
}

The same types apply as for upload, size is checked against your account's limit (413 file_too_large when over), and the storage allowance is checked here too. size is part of the signature, so the PUT is refused if the byte count differs.

2. PUT the bytes.

curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/mp4" --data-binary @launch.mp4

Send exactly the returned headers and no API key. The URL accepts one PUT and stops working after an hour.

3. Complete it.

POST /v1/media/presign/{upload_id}/complete

Completion verifies the object where it sits and promotes it into the library with the same checks as an upload: allowed type, content matching the declared type, size, storage allowance. It answers with the same item shape as upload, including preview_url. A refused object is deleted; 409 means the upload was already completed and 410 that it expired. An upload that is never completed is discarded after 24 hours and never appears in the library.

The SDKs wrap all three calls in one helper (media.uploadDirect or the language equivalent).

List the library

GET /v1/media

Returns the workspace's library items with their type, name, size, and URL.

Delete

DELETE /v1/media/{id}

Removes the item from the library and answers 204 with no body. Posts that already published with it are unaffected, since the network holds its own copy.

Attaching to a post

Two ways. A post can carry its files in the same request: send POST /v1/posts (or PUT /v1/posts/{id}) as multipart/form-data with the JSON body in a post field and the files under files. They are stored in the workspace library and attached to the first content block. The multipart ceiling of 100 MB per file applies; see publishing.

Or upload first and take what upload returns into a content block:

{
  "content": [
    {
      "text": "Doors open Monday.",
      "media": [
        {
          "type": "image",
          "name": "launch.png",
          "url": "https://...",
          "alt": "A storefront with the lights coming on"
        }
      ]
    }
  ]
}

type is image, video, or gif. alt is optional and worth sending: it is what screen readers announce, and several networks surface it.

Each network sets its own limits on count, dimensions, aspect ratio, and duration. FoPost resizes and re-encodes for the target where it can, and preflight tells you when it cannot.

Storage

The library counts against your account's free storage allowance; past it, storage is metered per GB per month with a card on file. A stored URL is an identity, not public access: the bucket is private, and the dashboard renders media through a per-request check rather than a link anyone can pass around.

Next

Related documentation
  • API Overview

    Base URL, envelopes, pagination, and errors for the FoPost REST API.

  • Authentication

    API keys, scopes, and workspace binding.

  • Publishing

    Create a post, target accounts, publish it, and read the per-account result.

  • Scheduling

    Schedule a post, repeat it, and import a batch from a spreadsheet.

  • Validation

    Check content, text length, and media against platform rules before a post exists.

  • Blog

    A keyword plan, articles written in the background, a review window, and a target they publish to.

  • Blogs and Articles

    Read and manage the articles and products that already live on a connected site.

  • Accounts

    List connected social accounts, check their health, and refresh credentials.

Was this helpful?

On this page