# Quickstart: REST API (/quickstart/api)



Your first video from a terminal in four calls. Base URL `https://app.riffads.com/api/v1`.

You need: a RiffAds workspace on a paid plan, `curl` and `jq`.

<Callout type="warn" title="Server side only">
  No CORS. Never call the API from a browser.
</Callout>

<Steps>
  <Step>
    ### Make a key [#make-a-key]

    Owners and admins create keys at [app.riffads.com/api-keys](https://app.riffads.com/api-keys). Tick **Generate**, or submits fail with `403 insufficient_scope`. The key starts with `sk_live_` and is shown once.

    ```bash title="Terminal"
    export RIFFADS_API_KEY="sk_live_..."
    ```
  </Step>

  <Step>
    ### Estimate [#estimate]

    Text-to-video with `veo_31`: one prompt, one generation. The estimate returns the number you need for `max_credits`.

    <Endpoint method="POST" path="/estimates" />

    ```bash title="Terminal"
    curl -s -X POST https://app.riffads.com/api/v1/estimates \
      -H "Authorization: Bearer $RIFFADS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "capability_id": "veo_31",
        "config": { "prompt": "A matte black water bottle on wet slate, morning light" }
      }' | jq '.credits'
    ```
  </Step>

  <Step>
    ### Submit [#submit]

    <Endpoint method="POST" path="/generations" />

    ```bash title="Terminal"
    curl -s -X POST https://app.riffads.com/api/v1/generations \
      -H "Authorization: Bearer $RIFFADS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "capability_id": "veo_31",
        "config": { "prompt": "A matte black water bottle on wet slate, morning light" },
        "max_credits": 440
      }' | jq -r '.generation_id'
    ```

    <Callout type="warn" title="max_credits must be above the estimate">
      `max_credits` is the spend cap for this job. Send at least `ceil(estimate * 1.1)`. The bare estimate is refused with `402 max_credits_exceeded`. The `440` above is only an example.
    </Callout>

    * Success is `201` with a `generation_id` (`gen_...`). &#x2A;*Save it.** No endpoint lists your generations.
    * Body is strict: `maxCredits` or any unknown key is a `400`.
  </Step>

  <Step>
    ### Wait, then download [#wait-then-download]

    <Endpoint method="GET" path="/generations/{id}/wait" />

    Each call blocks up to 20 seconds. Call again while `still_running` is `true`.

    ```bash title="Terminal"
    GEN=gen_...   # from the submit
    while :; do
      BODY=$(curl -s "https://app.riffads.com/api/v1/generations/$GEN/wait" \
        -H "Authorization: Bearer $RIFFADS_API_KEY")
      [ "$(echo "$BODY" | jq -r '.still_running')" = "false" ] && break
    done

    echo "$BODY" | jq -r '.generation.status'
    echo "$BODY" | jq -r '.generation.outputs[0].url' | xargs curl -L -o ad.mp4
    ```

    * `completed`: `outputs[0].url` is a signed link that **expires in 600 seconds**. Download it, don't store it. Expired? Call wait again for a fresh one.
    * `failed`: no file. Read `generation.error`.
    * Branch on `still_running`, not on `status`.
  </Step>
</Steps>

## If it fails [#if-it-fails]

| Code                   | Fix                                                   |
| ---------------------- | ----------------------------------------------------- |
| `max_credits_exceeded` | Send at least `ceil(estimate * 1.1)`.                 |
| `insufficient_scope`   | Make a key with Generate ticked.                      |
| `invalid_config`       | Read `GET /capabilities/veo_31` and fix `config`.     |
| `submission_in_flight` | One submit in flight per key. Wait for the first job. |
| `insufficient_credits` | Top up in the app.                                    |
| `spend_limit_exceeded` | A workspace cap blocked it. Ask an owner.             |
| `moderation_blocked`   | Change the wording. Don't resend the same text.       |

Every error has `code`, `message` and `retryable`. All codes: [Errors](/reference/errors).

## Next [#next]

<Cards>
  <Card title="Talking actor ads" href="/guides/talking-actor" description="An actor speaks your script." />

  <Card title="Capabilities" href="/capabilities" description="Every id you can submit." />

  <Card title="Generations API" href="/api/generations" description="Every field on submit and read." />
</Cards>
