> ## Documentation Index
> Fetch the complete documentation index at: https://aisa.one/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload Media

> Upload an image, GIF, or video and get a media_id to attach to a tweet. Multipart/form-data only.

Upload a single image, GIF, or video and receive a `media_id` you can attach to a tweet via [`post_twitter`](/docs/api-reference/twitter/post_twitter-post-twitter), routed through the AIsa gateway at `https://api.aisa.one/apis/v1/twitter/upload_media`.

<Warning>
  This endpoint accepts **`multipart/form-data` only** — send the file as a form part. Sending a JSON body returns `400 "upload_media requires multipart/form-data with a single media file field"`.
</Warning>

## Prerequisites

* An **AIsa API key** (Bearer token for every request).
* A one-time **OAuth authorization** for the source user. Link your X account via [`POST /apis/v1/twitter/auth_twitter`](/docs/api-reference/twitter/post_twitter-auth-twitter).
* The X session must hold `media.write`, `tweet.read`, `users.read`.

## Two-step media flow

1. **Upload** — `POST /apis/v1/twitter/upload_media` with the file → returns `media_id`.
2. **Publish** — `POST /apis/v1/twitter/post_twitter` with `{ "media": { "media_ids": ["<media_id>"] } }`.

<Note>
  `post_twitter` also accepts inline file parts via `media_files` in a single multipart request, letting you skip the pre-upload step. Use `upload_media` when you want to reuse one asset across several tweets, or decouple a large upload from the publish call.
</Note>

## Example

```bash theme={null}
curl -X POST "https://api.aisa.one/apis/v1/twitter/upload_media" \
  -H "Authorization: Bearer $AISA_API_KEY" \
  -F "media_files=@./cat.png;type=image/png"
```

```json Response theme={null}
{ "code": 200, "msg": "Media uploaded successfully", "data": { "media_id": "2099415764780605440" } }
```

Then attach it:

```bash theme={null}
curl -X POST "https://api.aisa.one/apis/v1/twitter/post_twitter" \
  -H "Authorization: Bearer $AISA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Look at this cat","media":{"media_ids":["2099415764780605440"]}}'
```

## Notes

* The file field may be named `media_files`, `media`, or `file`.
* Images up to 8 MB use a single-shot upload; larger files and video are chunked automatically.
* Unattached media expires after \~24h.
* Media upload is a prerequisite step and is **not separately metered** — you are billed when you publish the tweet via `post_twitter`.

See [Error Codes](/docs/api-reference/errors) and [Rate Limits](/docs/api-reference/rate-limits) for more.

## Related

<CardGroup cols={3}>
  <Card title="Post a Tweet" icon="pen" href="/docs/api-reference/twitter/post_twitter-post-twitter">
    Attach the media\_id via `media.media_ids`.
  </Card>

  <Card title="Link an X Account" icon="key" href="/docs/api-reference/twitter/post_twitter-auth-twitter">
    Start the OAuth flow this endpoint requires.
  </Card>

  <Card title="Like a Tweet" icon="heart" href="/docs/api-reference/twitter/post_twitter-like-twitter">
    Another write endpoint using the same OAuth session.
  </Card>
</CardGroup>


## OpenAPI

````yaml openapi/twitter-actions.json POST /twitter/upload_media
openapi: 3.0.3
info:
  title: Twitter Actions API
  version: 1.0.0
  description: >-
    Write actions against X/Twitter, routed through the AIsa gateway. Modeled
    after the official X v2 API endpoints (e.g., POST /2/users/{id}/following).
servers:
  - url: https://api.aisa.one/apis/v1
security:
  - BearerAuth: []
paths:
  /twitter/upload_media:
    post:
      summary: Upload media (pre-upload for tweets)
      description: >-
        Upload a single image, GIF, or video and receive a `media_id` you can
        attach to a tweet via
        [`post_twitter`](/api-reference/twitter/post_twitter-post-twitter)
        (`media.media_ids`). Routed through the AIsa gateway at
        `https://api.aisa.one/apis/v1/twitter/upload_media`.


        **Content type — `multipart/form-data` only.** Send the file as a
        multipart form part. **Do not send a JSON body** — a JSON request is
        rejected with `400 "upload_media requires multipart/form-data with a
        single media file field"`.


        **Two-step media flow.**

        1. `POST /apis/v1/twitter/upload_media` with the file → returns
        `media_id`.

        2. `POST /apis/v1/twitter/post_twitter` with `{ "media": { "media_ids":
        ["<media_id>"] } }`.


        Alternatively, `post_twitter` accepts inline file parts via
        `media_files` in a single multipart request, skipping this pre-upload
        step.


        **Authentication.** Requires an OAuth session for the source user,
        attached to your AIsa API key. Link the account once via [`POST
        /apis/v1/twitter/auth_twitter`](/api-reference/twitter/post_twitter-auth-twitter).


        **Scopes.** The underlying X session must hold `media.write` (plus
        `tweet.read`, `users.read`).


        Media upload is a prerequisite step and is not separately metered — you
        are billed when you publish the tweet via `post_twitter`.
      operationId: uploadTwitterMedia
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - media_files
              properties:
                media_files:
                  type: string
                  format: binary
                  description: >-
                    The media file to upload (`image/*`, `image/gif`, or
                    `video/*`). The field may also be named `media` or `file`.
                    Images up to 8 MB use a single-shot upload; larger files and
                    video use chunked upload automatically.
                aisa_api_key:
                  type: string
                  description: >-
                    Optional. Normally unnecessary — the gateway resolves your
                    account from the `Authorization: Bearer` header.
            encoding:
              media_files:
                contentType: image/png, image/jpeg, image/gif, video/mp4
      responses:
        '200':
          description: >-
            Upload succeeded; returns the `media_id` to reference in
            `post_twitter`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      media_id:
                        type: string
                        description: >-
                          The uploaded media's ID. Pass it in `post_twitter`
                          `media.media_ids`. Unattached media expires after
                          ~24h.
              example:
                data:
                  media_id: '2099415764780605440'
        '400':
          description: >-
            Not `multipart/form-data`, missing file part, empty file, or
            unsupported content type. A JSON body returns `"upload_media
            requires multipart/form-data with a single media file field"`.
        '401':
          description: Missing or invalid AIsa API key.
        '403':
          description: >-
            OAuth session missing, expired, or lacking `media.write`.
            Re-authorize via `POST /apis/v1/twitter/auth_twitter`.
        '429':
          description: >-
            Rate limit hit — your AIsa key's RPM cap or the upstream X rate
            limit.
        '500':
          description: Internal error.
        '502':
          description: Upstream X API unreachable. Safe to retry with exponential backoff.
      security:
        - BearerAuth: []
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: AISA API Key
      description: >-
        Your AIsa API key. The authenticated source user (the account doing the
        follow) is determined by the OAuth session attached to your key.

````