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

# Descript API

> Go from raw media to a finished, shareable video without opening the Descript app.

Descript is an AI-powered video and audio editor. With the Descript API, you can import media and
tell Underlord, Descript's AI editing assistant, what to automate in plain language: remove filler
words, improve audio, add captions, or turn a long recording into short clips. Review the results in
the Descript editor or publish directly through the API.

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/developers/get-started/quickstart">
    Create a token, import media, and prompt Underlord for edits.
  </Card>

  <Card title="API endpoints" icon="code" href="/developers/api-reference/overview">
    Import media, edit projects with AI, and query jobs and projects.
  </Card>

  <Card title="Using the CLI" icon="terminal" href="/developers/guides/cli">
    Drive the same API from your terminal.
  </Card>

  <Card title="Descript MCP" icon="plug" href="/developers/mcp/overview">
    Import, edit, and publish from Claude, ChatGPT, Codex, or Cursor.
  </Card>
</CardGroup>

## Make your first request

Every request goes to `https://descriptapi.com/v1` and carries a personal API token in the
`Authorization` header. Tokens are scoped to a specific Drive and inherit your permissions on that
Drive.

Generating a client? Download the [OpenAPI spec](/developers/openapi.json).

<CodeGroup>
  ```bash cURL theme={null}
  curl https://descriptapi.com/v1/status \
    -H "Authorization: Bearer $DESCRIPT_API_TOKEN"
  ```

  ```js Node theme={null}
  const res = await fetch('https://descriptapi.com/v1/status', {
      headers: { Authorization: `Bearer ${process.env.DESCRIPT_API_TOKEN}` },
  });
  const status = await res.json();
  ```

  ```python Python theme={null}
  import os
  import requests

  res = requests.get(
      "https://descriptapi.com/v1/status",
      headers={"Authorization": f"Bearer {os.environ['DESCRIPT_API_TOKEN']}"},
  )
  status = res.json()
  ```
</CodeGroup>

<Note>
  Don't have a token yet? [Create one in
  Settings](/developers/get-started/quickstart#create-an-api-token) — it takes three clicks.
</Note>

## Import media and edit it

Import, agent edit, and publish return a `job_id` immediately and do the work in the background.

<Tabs>
  <Tab title="Import from URL">
    Pass a public or presigned URL, and Descript fetches the file itself. Try it with the demo video
    below.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://descriptapi.com/v1/jobs/import/project_media \
        -H "Authorization: Bearer $DESCRIPT_API_TOKEN" \
        -H "Content-Type: application/json" \
        -d '{
          "project_name": "My First Video",
          "add_media": {
            "demo.mp4": { "url": "https://test-files.descriptapi.com/demo-video.mp4" }
          },
          "add_compositions": [
            { "name": "Demo Video", "clips": [{ "media": "demo.mp4" }] }
          ]
        }'
      ```

      ```js Node theme={null}
      const res = await fetch('https://descriptapi.com/v1/jobs/import/project_media', {
          method: 'POST',
          headers: {
              Authorization: `Bearer ${process.env.DESCRIPT_API_TOKEN}`,
              'Content-Type': 'application/json',
          },
          body: JSON.stringify({
              project_name: 'My First Video',
              add_media: {
                  'demo.mp4': { url: 'https://test-files.descriptapi.com/demo-video.mp4' },
              },
              add_compositions: [{ name: 'Demo Video', clips: [{ media: 'demo.mp4' }] }],
          }),
      });
      const { job_id } = await res.json();
      ```

      ```python Python theme={null}
      import os
      import requests

      res = requests.post(
          "https://descriptapi.com/v1/jobs/import/project_media",
          headers={"Authorization": f"Bearer {os.environ['DESCRIPT_API_TOKEN']}"},
          json={
              "project_name": "My First Video",
              "add_media": {
                  "demo.mp4": {"url": "https://test-files.descriptapi.com/demo-video.mp4"}
              },
              "add_compositions": [
                  {"name": "Demo Video", "clips": [{"media": "demo.mp4"}]}
              ],
          },
      )
      job_id = res.json()["job_id"]
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Direct file upload">
    Send a file from your own machine in two requests: ask for a signed upload URL, then PUT the bytes
    to it.

    <Steps>
      <Step title="Request an upload URL">
        Describe the file with `content_type` and `file_size` instead of a `url`. The response includes a
        signed `upload_url` for each file, valid for 3 hours.

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST https://descriptapi.com/v1/jobs/import/project_media \
            -H "Authorization: Bearer $DESCRIPT_API_TOKEN" \
            -H "Content-Type: application/json" \
            -d '{
              "project_name": "My First Video",
              "add_media": {
                "recording.mp4": { "content_type": "video/mp4", "file_size": 52428800 }
              },
              "add_compositions": [
                { "name": "Main", "clips": [{ "media": "recording.mp4" }] }
              ]
            }'
          ```

          ```js Node theme={null}
          import { readFile, stat } from 'node:fs/promises';

          const { size } = await stat('recording.mp4');

          const res = await fetch('https://descriptapi.com/v1/jobs/import/project_media', {
              method: 'POST',
              headers: {
                  Authorization: `Bearer ${process.env.DESCRIPT_API_TOKEN}`,
                  'Content-Type': 'application/json',
              },
              body: JSON.stringify({
                  project_name: 'My First Video',
                  add_media: {
                      'recording.mp4': { content_type: 'video/mp4', file_size: size },
                  },
                  add_compositions: [{ name: 'Main', clips: [{ media: 'recording.mp4' }] }],
              }),
          });
          const { job_id, upload_urls } = await res.json();
          ```

          ```python Python theme={null}
          import os
          import requests

          res = requests.post(
              "https://descriptapi.com/v1/jobs/import/project_media",
              headers={"Authorization": f"Bearer {os.environ['DESCRIPT_API_TOKEN']}"},
              json={
                  "project_name": "My First Video",
                  "add_media": {
                      "recording.mp4": {
                          "content_type": "video/mp4",
                          "file_size": os.path.getsize("recording.mp4"),
                      }
                  },
                  "add_compositions": [
                      {"name": "Main", "clips": [{"media": "recording.mp4"}]}
                  ],
              },
          )
          job_id = res.json()["job_id"]
          upload_urls = res.json()["upload_urls"]
          ```
        </CodeGroup>
      </Step>

      <Step title="Upload the file">
        PUT the raw bytes to the signed URL. The import job picks up the upload and starts processing.

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X PUT "$UPLOAD_URL" \
            -H "Content-Type: application/octet-stream" \
            --data-binary @recording.mp4
          ```

          ```js Node theme={null}
          await fetch(upload_urls['recording.mp4'].upload_url, {
              method: 'PUT',
              headers: { 'Content-Type': 'application/octet-stream' },
              body: await readFile('recording.mp4'),
          });
          ```

          ```python Python theme={null}
          with open("recording.mp4", "rb") as f:
              requests.put(
                  upload_urls["recording.mp4"]["upload_url"],
                  headers={"Content-Type": "application/octet-stream"},
                  data=f,
              )
          ```
        </CodeGroup>
      </Step>
    </Steps>
  </Tab>
</Tabs>

Either way, poll [Get job status](/developers/api-reference/jobs/get-job) with the `job_id`, or pass
a `callback_url` and Descript will POST the same payload when the job finishes. For mixing both in
one request, see [Direct file upload](/developers/guides/direct-upload).

<Columns cols={2}>
  <Card title="Partner integrations" icon="handshake" href="/developers/partners/edit-in-descript">
    Edit in Descript and Export from Descript.
  </Card>

  <Card title="Rate limits" icon="gauge-high" href="/developers/guides/rate-limiting">
    Read the headers and back off correctly on 429.
  </Card>
</Columns>


## Related topics

- [Descript API](/api-and-mcp/api.md)
- [Call the API directly](/api-and-mcp/other-endpoints.md)
- [Descript on Raycast](/account-and-app-settings/descript-on-raycast.md)
- [Connect Descript to Zapier](/api-and-mcp/zapier.md)
- [Descript MCP overview](/api-and-mcp/mcp.md)
