---
title: Quickstart
description: Go from an API key to a scheduled post with curl.
sidebar:
  icon: rocket
---

This walks through the REST API end to end: list accounts, upload a video, and schedule it. If you use Claude, Cursor, or ChatGPT, the [MCP server](/mcp) does the same thing without a key.

Every request goes to `https://www.brainyshorts.com`. Don't use `api.brainyshorts.com`.

1. **Get a key and connect an account**

    Sign in at [brainyshorts.com](https://www.brainyshorts.com), connect a social account, and create a key at [Dashboard → API keys](https://www.brainyshorts.com/dashboard/api-keys).

    ```bash
    export BRAINY_API_KEY="ba_sk_..."
    export BRAINY="https://www.brainyshorts.com/api/cli"
    ```

2. **List connected accounts**

    ```bash
    curl -s "$BRAINY/accounts" -H "Authorization: Bearer $BRAINY_API_KEY"
    ```

    ```json
    {
      "accounts": [
        { "id": "3f0c...", "platform": "instagram", "account_handle": "@example", "token_valid": true, "created_at": "..." }
      ]
    }
    ```

    Keep the `id` and `platform` of each account you want to post to.

3. **Reserve an upload**

    BrainyShorts checks the file's hashes before it accepts it, so compute them first.

    ```bash
    FILE=clip.mp4
    SIZE=$(wc -c < "$FILE" | tr -d ' ')
    SHA256=$(shasum -a 256 "$FILE" | cut -d' ' -f1)
    MD5=$(openssl md5 -r "$FILE" | cut -d' ' -f1)

    curl -s -X POST "$BRAINY/media/uploads" \
      -H "Authorization: Bearer $BRAINY_API_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"content_type\":\"video/mp4\",\"content_length\":$SIZE,\"content_sha256\":\"$SHA256\",\"content_md5\":\"$MD5\"}" \
      > reservation.json
    ```

    `content_type` is one of `image/jpeg`, `image/png`, `image/webp`, or `video/mp4`. The response holds an `asset_id`, a presigned `upload_url`, and the exact `upload_headers` to send.

    If the response says `"already_uploaded": true`, you uploaded this exact file before. Skip the next two steps and use `media.public_url`.

4. **Upload the bytes**

    Send every header from `upload_headers` exactly as returned. The URL is signed against them, so a missing or changed header fails.

    ```bash
    HEADERS=()
    while IFS= read -r h; do HEADERS+=(-H "$h"); done \
      < <(jq -r '.upload_headers | to_entries[] | "\(.key): \(.value)"' reservation.json)

    curl -s -X PUT "$(jq -r .upload_url reservation.json)" "${HEADERS[@]}" --data-binary @"$FILE"
    ```

5. **Complete the upload**

    ```bash
    curl -s -X PATCH "$BRAINY/media/uploads" \
      -H "Authorization: Bearer $BRAINY_API_KEY" \
      -H "Content-Type: application/json" \
      -d "{\"asset_id\":\"$(jq -r .asset_id reservation.json)\"}" \
      > media.json
    ```

    The response has a `public_url` on `storage.brainyshorts.com`, a domain every supported platform accepts.

6. **Schedule the post**

    ```bash
    curl -s -X POST "$BRAINY/posts" \
      -H "Authorization: Bearer $BRAINY_API_KEY" \
      -H "Content-Type: application/json" \
      -d @- <<JSON
    {
      "title": "Launch teaser",
      "caption": "Something new is coming.",
      "media_type": "video",
      "media_urls": ["$(jq -r .public_url media.json)"],
      "targets": [{ "social_account_id": "3f0c...", "platform": "instagram" }],
      "action": "schedule",
      "scheduled_at": "2026-10-01T17:00:00Z"
    }
    JSON
    ```

    The response is `{ "post": {...}, "social_posts": [...] }`, with one `social_posts` entry per target. BrainyShorts publishes it at `scheduled_at`.

:::warning
Always send `action`. When `action` is missing, the REST API publishes immediately, or schedules if `scheduled_at` is present. Use `"action": "draft"` when you want a person to review the post first.
:::

## Next steps

- [REST API](/api) covers updates, deletes, errors, and articles.
- [Bulk scheduling](/bulk-scheduling) schedules hundreds of posts safely.
- [Platforms](/platforms) lists each platform's rules.
