--- title: "Twitter Media Upload API: Attach Images for $0.0008" description: "Upload an image as base64, get a media_id back and attach it to a tweet. $0.0008 a call, one JSON request, and none of the chunked INIT and FINALIZE steps." canonical: https://www.twitterapis.com/twitter-media-upload-api generator: md-live-route generated: 2026-11-06T08:56:28.409Z --- # Twitter Media Upload API: Attach Images for $0.0008 1. [Home](https://www.twitterapis.com/) 2. / Twitter Media Upload API ATTACH IMAGES # Twitter Media Upload API: Attach Images for $0.0008 Updated July 2026 How does the Twitter media upload API work? You send an image as base64 in a JSON body to `POST /twitter/media/upload` and get back a `media_id`, then pass that ID to the tweet create call. On TwitterAPIs that is one request at $0.0008, with no INIT, APPEND and FINALIZE sequence. The upload runs as the X account you registered, and one tweet carries up to four images. Flat $0.0008 per upload. Publishing the tweet that carries the image is a separate call, priced on the post page. Per our own API records, 125 endpoints share this one Bearer key and one prepaid balance. Start with the [quickstart](https://www.twitterapis.com/quickstart), compare [every price](https://www.twitterapis.com/pricing), or connect an agent through the [MCP server](https://www.twitterapis.com/mcp). [Start Free](https://www.twitterapis.com/signup)[Read the docs](https://docs.twitterapis.com/docs/reference/write-actions/media-upload) By the numbers ## One request instead of a sequence The upload is the step that usually makes a posting integration take a day rather than an hour. - An image upload on TwitterAPIs is one JSON request at $0.0008. [(TwitterAPIs pricing, 2026)](https://www.twitterapis.com/pricing) - X's chunked upload flow is the required path for video and large media on the official API. [(X API docs, 2026)](https://docs.x.com/x-api/media/quickstart/media-upload-chunked) - A plain or media post on the official X API bills $0.015 per request since April 20, 2026. [(X Developer Community, 2026)](https://devcommunity.x.com/t/x-api-pricing-update-owned-reads-now-0-001-other-changes-effective-april-20-2026/263025) - Per our billing data, paying customers made 484,424 tweet-returning read calls over October 22 to 28, 2026 (UTC), on the same keys and the same per-call billing as this endpoint. [(TwitterAPIs measured yield, 2026)](https://www.twitterapis.com/twitter-api-pricing) ## The two media endpoints and what each one does Uploading is one call and checking is another. Most image uploads only need the first, because the `media_id` is ready to attach as soon as it comes back. The status route exists for media that X processes in the background, so you can confirm readiness instead of guessing with a sleep. ### POST /twitter/media/upload Upload a base64-encoded image as the authenticated account and receive a media\_id you can attach when creating a tweet. Runs as your own logged-in account. Billed at $0.0008 per call. | Parameter | Where | Required | Purpose | | --- | --- | --- | --- | | media\_data | body | Yes | Base64-encoded image bytes, sent in the JSON request body. | ### GET /twitter/media/status Return the processing state X reports for a media\_id from Upload Media, so a caller can check it before attaching the id to a tweet. Uploads are image only, and X usually reports no state for an image, so state is usually null. Runs as your own logged-in account. Billed at $0.0008 per call. | Parameter | Where | Required | Purpose | | --- | --- | --- | --- | | media\_id | query | Yes | Numeric media ID (as a string) returned by media/upload. | ## Sample responses from the media endpoints Both responses are small on purpose. The upload hands you one identifier, and the status check tells you whether that identifier is ready. The tabs below show the examples published in our OpenAPI document, so they match what the API reference shows. Upload Media Media Status Copy ``` { "ok": true, "media_id": "1899999999999999999" } ``` ``` { "media_id": "1899999999999999999", "state": null, "check_after_secs": null, "progress_percent": null } ``` | Field | What it tells you | | --- | --- | | media\_id | The identifier you pass to media\_ids when you create the tweet. Keep it as a string, it is 19 digits long. | | state | Where processing stands. Attach the ID once this reads succeeded. | | check\_after\_secs | How long to wait before asking again, so you poll at the pace X suggests. | | progress\_percent | How far along processing is, useful when a user is watching a progress bar. | ## Quickstart: upload an image and attach it to a tweet An upload belongs to an account, so this needs the same one-time session as any other write. After that the flow is two calls: upload the bytes, then publish a tweet that names the ID you got back. Encode the file as base64 first. 1. Create a key at [signup](https://www.twitterapis.com/signup) and register the X account with `POST /twitter/customer/session`, which is free. 2. Base64-encode the image. In Python that is `base64.b64encode(open(path, "rb").read()).decode()`. 3. Send the request below with the encoded string in `media_data` and read `media_id` from the response. 4. Pass that ID in `media_ids` on [the tweet create call](https://www.twitterapis.com/twitter-post-api) to publish the post. cURL Python JavaScript Copy ``` curl -X POST "https://api.twitterapis.com/twitter/media/upload" \ -H "Authorization: Bearer $TWITTERAPIS_KEY" \ -H "Content-Type: application/json" \ -d '{"media_data": "iVBORw0KGgoAAAANSUhEUgAA..."}' ``` ``` import os import requests resp = requests.post( "https://api.twitterapis.com/twitter/media/upload", headers={"Authorization": f"Bearer {os.environ['TWITTERAPIS_KEY']}"}, json={"media_data": "iVBORw0KGgoAAAANSUhEUgAA..."}, ) resp.raise_for_status() data = resp.json() print(data) ``` ``` const resp = await fetch("https://api.twitterapis.com/twitter/media/upload", { method: "POST", headers: { Authorization: `Bearer ${process.env.TWITTERAPIS_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ media_data: "iVBORw0KGgoAAAANSUhEUgAA..." }), }); const data = await resp.json(); console.log(data); ``` ## What an image post costs per 1,000 Each image is one upload call, and each tweet is one create call, so the total depends only on how many images a post carries. There is no storage charge and no per-megabyte line. The status check bills like any other read, and an image upload rarely needs it. | Action | Per post | Per 1,000 posts | | --- | --- | --- | | Upload one image | $0.0008 | $0.80 | | Check processing status | $0.0008 | $0.80 | | Upload one image and publish the tweet | $0.0024 | $2.40 | | Upload four images and publish the tweet | $0.0048 | $4.80 | Calculated from the biller's per-call rows, one call per image at a fill of exactly 1. The signup credit of $0.50 covers about 208 image posts. See [all endpoint prices](https://www.twitterapis.com/pricing). ## Twitter media upload options compared The price of the upload itself matters less than the work around it. What separates these options is how many requests a single image takes, which credentials you must hold, and how the bill behaves once the post goes out. Rates are each vendor's published figures. | Option | Image post cost | Requests per image | Credentials | | --- | --- | --- | --- | | TwitterAPIs | $0.0024 | 1 to upload, 1 to post | Bearer key plus one registered session | | Official X API | $0.015 per post | 1 for a simple image, several when chunked | Developer app and OAuth user tokens | | Outstand | $0.007 per post after the included 3,000 | Handled by their publish call | API key plus your own X app, $19 per month | | Browser automation | Proxy and compute costs | A file input and a page load | A logged-in browser profile | ## What chunked upload is, and when you avoid it The first-party media flow was built for large files. You open an upload with INIT, send the bytes in pieces with APPEND, close it with FINALIZE, and then poll until processing ends. That design makes sense for a long video on a bad connection, and it is a lot of state to carry when all you want is one PNG on a post. Our route takes the image in a single JSON field and returns the ID in the same response. There is no multipart body to assemble and no session state to keep between requests, which also means a serverless function can do it in one invocation. ## Image input: what to send and what to expect The documented input is a base64-encoded image, so plan this endpoint around images. Base64 grows a file by about a third, which is worth remembering when you size request bodies at a proxy or an API gateway. Encode the raw file bytes and leave out any `data:image/png;base64,` prefix, because the field holds the encoded bytes and nothing else. - Four to a tweet. A tweet takes up to four image IDs, passed together in `media_ids`. - Attach soon after upload. Treat a `media_id` as short-lived and post it in the same job rather than keeping a library of IDs for later. - Alt text. The [media read endpoint](https://www.twitterapis.com/twitter-media-api) returns `ext_alt_text` on images that have it, which is useful when you audit an account's accessibility. ## Errors you will see on an upload A 400 usually means the body was not valid base64 or the field name was wrong, so check `media_data` before you look anywhere else. A 403 points at the account or the session: re-check the session with `GET /twitter/customer/session/status` and confirm the account can post by hand. A 402 means the balance ran out, and a 429 means you should back off and retry. A failed upload is safe to retry, unlike a tweet. Nothing is published until you pass the ID to the create call, so an orphaned `media_id` costs you one call and has no effect on the account. The full error contract is on the [REST API guide](https://www.twitterapis.com/twitter-rest-api), and our answer on [posting a tweet with an image](https://www.twitterapis.com/answers/how-to-post-a-tweet-with-an-image-api) walks through the two calls end to end. ## Upload and post in one script The whole flow fits in a short script, which is the point of a one-request upload. Read the file, encode it, send it, and pass the returned ID to the create call. The example below does exactly that with no retry logic, so you can see the two calls clearly before you add error handling around them. For several images, run the upload once per file and collect the IDs in a list, then send them together as a JSON array or a comma-separated string in `media_ids`. Upload in parallel if the images are large, since each upload is independent, and post only after every ID has come back. If one upload fails, retry that file alone and leave the others in place. Python Copy ``` import base64 import os import requests BASE = "https://api.twitterapis.com/twitter" HEADERS = {"Authorization": f"Bearer {os.environ['TWITTERAPIS_KEY']}"} with open("chart.png", "rb") as handle: encoded = base64.b64encode(handle.read()).decode() upload = requests.post( f"{BASE}/media/upload", headers=HEADERS, json={"media_data": encoded}, timeout=60, ) upload.raise_for_status() media_id = upload.json()["media_id"] post = requests.post( f"{BASE}/tweet/create", headers=HEADERS, json={"text": "This week's numbers.", "media_ids": media_id}, timeout=30, ) post.raise_for_status() print(post.json()["url"]) ``` ## Jobs that post images automatically Most automated image posts come from something that already produces a file on a schedule. The upload is the small last step at the end of a pipeline that renders a chart, a screenshot or a generated image, and the useful question is how to keep that step boring. These are the patterns that come up most, each of which works with one key and one registered session. Running this for several brands is the same code with a different X session on each call. Our answer on [using multiple X accounts with one key](https://www.twitterapis.com/answers/how-to-use-multiple-x-accounts-with-one-api-key) covers that, and the [API key page](https://www.twitterapis.com/twitter-api-key) explains how keys are issued. Budget by counting images and posts separately, using the table above, because the two are billed as separate calls. - Scheduled reports. Render a weekly chart, upload it and post it with a one-line summary. The [analytics API](https://www.twitterapis.com/twitter-analytics-api) can supply the numbers behind the chart. - Release announcements. Post a screenshot from your release pipeline, and let the [post API](https://www.twitterapis.com/twitter-post-api) carry the changelog link at no extra charge. - Generated images. An agent can create an image and publish it through the same two calls, with a person approving the result. - Replies with images. Set `reply_to` on the create call. The upload step does not change, and the [engagement API](https://www.twitterapis.com/twitter-engagement-api) covers the other actions a support bot needs. ## Sizing requests and keeping them reliable Base64 turns every three bytes into four characters, so a 3 MB image arrives as about 4 MB of JSON. Check that your HTTP client, proxy and any serverless platform accept a body of that size, because a gateway limit shows up as a confusing failure well before the request reaches us. Compress screenshots and charts before upload, since they shrink a lot and post just as well. Set a longer client timeout on the upload than on a normal read, and keep the two calls in the same job so a `media_id` does not sit unused. The [rate limits page](https://www.twitterapis.com/twitter-api-rate-limits) has the per-key ceiling, and the [quickstart](https://www.twitterapis.com/quickstart) covers the shared auth header if this is your first call to the API. ## Why the upload is a separate step from the post Keeping the file apart from the tweet feels like extra work on the first call, and it pays off in the failure cases. A post is the irreversible public action, while an upload has no visible effect. Doing the upload first means a bad file, an oversized body or a network drop fails before anything reaches the timeline, so you never publish a post that promised an image it does not carry. It also lets you validate before you commit. Run the upload, confirm the `media_id` came back, and only then send the text. If a human approves posts in your product, do the upload after approval rather than before it, so the ID is fresh when the post goes out. The same order suits a [reply bot](https://www.twitterapis.com/answers/how-to-build-a-twitter-auto-reply-bot-api), where the image is built from the message being answered. ## Upload images from an AI agent Both endpoints are tools in the MCP server, so an agent that generated an image can upload it and post it without writing any HTTP code. Use the [MCP page](https://www.twitterapis.com/mcp) to connect Claude, Cursor or another client with the same key. - `twitter_media_upload` calls POST /twitter/media/upload - `twitter_media_status` calls GET /twitter/media/status Install the MCP server Copy ``` npx -y @twitterapis/mcp@latest ``` ## Twitter media upload questions ### How do I upload an image to Twitter through an API? Base64-encode the file and send it as media\_data to POST /twitter/media/upload with your Bearer key. You get back a media\_id for $0.0008, which you then pass to POST /twitter/tweet/create in media\_ids. ### Can I upload a video with this endpoint? The documented input is a base64-encoded image, so build around images. If a video workflow matters to you, check the API reference before you commit to it, because the media\_status route is the only part of this pair that describes asynchronous processing. ### Does uploading an image publish anything? No. An upload only stores the image and returns an ID. Nothing appears on the account until you create a tweet that references that ID. ### Which account does the upload run as? The X account behind the session you registered with POST /twitter/customer/session. The media\_id is tied to that account, so post it from the same session. ### Do I have to use INIT, APPEND and FINALIZE? No. Those are steps in X's chunked flow. Our endpoint takes the whole image in one JSON request and returns the media\_id directly. ### How many images can a tweet have? Up to four. Upload each one separately, collect the IDs, and pass them together in the media\_ids field when you create the tweet. ### Do I need the status endpoint for images? Usually not. Check GET /twitter/media/status only if an attach fails because the media is not ready, or if you are handling something X processes in the background. It returns state, check\_after\_secs and progress\_percent. ### Post your first image in two calls Create a key, register one X session for free, upload the image and publish the tweet. [Start free](https://www.twitterapis.com/signup)[See every price](https://www.twitterapis.com/pricing) ## Next read Continue exploring related pages: [ Twitter API use cases 14 use cases from sentiment analysis to lead generation. ](https://www.twitterapis.com/twitter-api-usecases)[ Twitter API answers Short, sourced answers to the most common Twitter and X API questions. ](https://www.twitterapis.com/answers)[ Twitter timeline API Pull an account's full posting history over one REST endpoint. ](https://www.twitterapis.com/twitter-timeline-api)[ Twitter list API Read list members and list timelines with the same Bearer key. ](https://www.twitterapis.com/twitter-list-api) [ TwitterAPIs ](https://www.twitterapis.com/) The cheapest pay-as-you-go Twitter and X API. $0.0008 per call, which works out to $0.04 per 1,000 tweets on a full 20-tweet page. No subscriptions and no developer account. ## Product / API - [Pricing](https://www.twitterapis.com/pricing) - [Cost Calculator](https://www.twitterapis.com/twitter-api-cost-calculator) - [Rate Limits](https://www.twitterapis.com/twitter-api-rate-limits) - [MCP Server](https://www.twitterapis.com/mcp) - [Integrations](https://www.twitterapis.com/integrations) - [Language Clients](https://www.twitterapis.com/sdk) - [Changelog](https://www.twitterapis.com/changelog) - [Status](https://www.twitterapis.com/status) ## Developers - [Documentation](https://docs.twitterapis.com/docs) - [API Reference](https://docs.twitterapis.com/docs/reference/search/tweet-advanced-search) - [User Info](https://docs.twitterapis.com/docs/reference/user-reads/user-info) - [User Tweets](https://docs.twitterapis.com/docs/reference/user-reads/user-tweets) - [Advanced Search](https://docs.twitterapis.com/docs/reference/search/tweet-advanced-search) - [Verified Followers](https://docs.twitterapis.com/docs/reference/follower-graph/user-verified-followers) ## Resources / Compare - [Answers](https://www.twitterapis.com/answers) - [Reviews](https://www.twitterapis.com/reviews) - [Free Tools](https://www.twitterapis.com/tools) - [Twitter ID Finder](https://www.twitterapis.com/tools/twitter-id-finder) - [Get a Twitter API Key](https://www.twitterapis.com/twitter-api-key) - [Official X API Comparison](https://www.twitterapis.com/twitter-api-pricing) - [Twitter API Use Cases](https://www.twitterapis.com/twitter-api-usecases) - [Twitter API Alternatives](https://www.twitterapis.com/twitter-api-alternatives) - [Twitter Unofficial API](https://www.twitterapis.com/twitter-unofficial-api) - [Twitter Free API](https://www.twitterapis.com/twitter-free-api) - [TwitterAPIs vs twitterapi.io](https://www.twitterapis.com/twitterapis-vs-twitterapi-io) - [TwitterAPIs vs GetXAPI](https://www.twitterapis.com/twitterapis-vs-getxapi) - [TwitterAPIs vs TweetAPI](https://www.twitterapis.com/twitterapis-vs-tweetapi) - [TwitterAPIs vs TwexAPI](https://www.twitterapis.com/twitterapis-vs-twexapi) - [TwitterAPIs vs RapidAPI](https://www.twitterapis.com/twitterapis-vs-rapidapi) ## Legal - [About](https://www.twitterapis.com/about) - [Security](https://www.twitterapis.com/security) - [Trust](https://www.twitterapis.com/privacy-and-data-handling) - [Terms of Service](https://www.twitterapis.com/terms-of-service) - [Affiliates](https://www.twitterapis.com/affiliates) - [Contact](https://www.twitterapis.com/contact) - [Jobs](https://www.twitterapis.com/jobs) © 2026 TwitterAPIs. All rights reserved. TwitterAPIs is an independent third-party API for developers and researchers. Not affiliated with, endorsed by, or sponsored by X Corp. All systems operational