---
name: deepgram-django
description: "Provisions Deepgram speech-to-text with speaker diarization, through short-lived tokens for a Django app with `npx extraorbital add deepgram` (no signup, no card) and wires it into Django with `httpx`. Use when a Django project needs Speech-to-text (Deepgram), transcription, speech recognition, voice, diarization or audio, or when DEEPGRAM_BASE_URL is missing or unset in .env."
---

# Speech-to-text (Deepgram) in Django

<!-- Generated from ExtraOrbital's service registry by `pnpm skills:build` in platform/app. Edit the generator, not this file. -->

ExtraOrbital provisions Deepgram speech-to-text with speaker diarization, through short-lived tokens in one command and writes the credentials into the project's env file. For anything this recipe does not cover, use the `extraorbital` skill or https://extraorbital.dev/llms.txt.

## 1. Provision

```bash
npx extraorbital add deepgram
```

Run it from the project root. It is idempotent (a second run returns the same resource) and writes to `.env.local` if the project has one, otherwise `.env`, keeping that file out of git. Add `--json` to parse the result; credential values are redacted there, and you should never print them either.

| Variable | |
| --- | --- |
| `DEEPGRAM_BASE_URL` | Deepgram's API origin |
| `DEEPGRAM_TOKEN_URL` | Exchanges the refresh credential for a 30-second token |
| `DEEPGRAM_REFRESH_TOKEN` | Stable credential: backend only |

Free on the Prototype plan: Uses your shared $5 credit; no separate free allowance.

## 2. Install the client

```bash
pip install python-dotenv httpx
```

## 3. Connect

Django reads no env file by itself: `settings.py` loads it with `python-dotenv`, below `BASE_DIR`.

**`yourproject/settings.py`**

```python
import os

from dotenv import load_dotenv

# ExtraOrbital writes .env.local when the project has one, else .env.
# load_dotenv never overrides a variable already set, so the first file wins.
load_dotenv(BASE_DIR / ".env.local")
load_dotenv(BASE_DIR / ".env")

DEEPGRAM_BASE_URL = os.environ["DEEPGRAM_BASE_URL"]
DEEPGRAM_TOKEN_URL = os.environ["DEEPGRAM_TOKEN_URL"]
DEEPGRAM_REFRESH_TOKEN = os.environ["DEEPGRAM_REFRESH_TOKEN"]
```

**`yourapp/speech.py`**

```python
import httpx

from django.conf import settings


def deepgram_token() -> str:
    """A 30-second token: ask for one right before each request or socket."""
    with httpx.Client() as client:
        grant = client.post(
            settings.DEEPGRAM_TOKEN_URL,
            headers={"Authorization": f"Bearer {settings.DEEPGRAM_REFRESH_TOKEN}"},
        )
        grant.raise_for_status()
        return grant.json()["access_token"]


def transcribe_url(url: str) -> dict:
    token = deepgram_token()
    with httpx.Client(timeout=120) as client:
        response = client.post(
            f"{settings.DEEPGRAM_BASE_URL}/v1/listen",
            params={"model": "nova-3", "diarize": "true", "smart_format": "true"},
            headers={"Authorization": f"Bearer {token}"},
            json={"url": url},
        )
        response.raise_for_status()
        return response.json()
```

## 4. Use it

**`yourapp/views.py`**

```python
from django.http import JsonResponse

from yourapp.speech import transcribe_url

# yourapp/urls.py: path("api/deepgram/", views.check_deepgram)


def check_deepgram(request):
    result = transcribe_url("https://dpgr.am/spacewalk.wav")
    return JsonResponse({"transcript": result["results"]["channels"][0]["alternatives"][0]["transcript"]})
```

## 5. Verify

```bash
npx extraorbital list        # the resource is listed as active
python manage.py runserver
curl http://localhost:8000/api/deepgram/
```

A JSON answer means the credentials, the client and the route all work. Delete the route afterwards if the app does not need it.

## Pitfalls

- From the catalog: Exchange the refresh credential for a 30-second token, then connect directly to Deepgram. Transcription and speaker diarization spend the same shared credit as other services. Usage is polled every minute, subject to provider reporting delays. Exhausted credit stops new tokens; a top-up restores access with the same refresh credential. Existing streams are not disconnected.
- Grants are limited to one per resource every five seconds: reuse a token for requests that start within its 30 seconds instead of minting one per call.
- A browser or mobile client may receive a 30-second token to stream audio straight to Deepgram over WebSocket; it must never receive `DEEPGRAM_REFRESH_TOKEN`.
- Out of credit, the token endpoint answers 402; streams already open are not cut.
- `yourapp` and `yourproject` stand for the project's own app and settings package.
- Read credentials once in `settings.py` and import `django.conf.settings` elsewhere; `os.environ[...]` fails at startup when one is missing, which is the point.
- Exit code 3 means the account is past its free allowance: show the human the link the command printed and wait. Do not retry or work around it.

Docs: https://extraorbital.dev/docs/resources/deepgram.md
