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

# Other languages

> TypeScript has a first-class SDK; every other language calls the same plain REST API. cURL, Python, and Go examples.

TypeScript gets a first-class SDK. Everything else talks to the same plain REST API — no SDK required, just HTTP and JSON over a Bearer token.

Drin's API is ordinary REST: a JSON body, a Bearer key, and conventional status codes. If your language can make an HTTPS request, it can use Drin. The [TypeScript SDK](/sdk/typescript) is a thin wrapper over these exact endpoints — anything it does, you can do directly.

<Info>
  **The whole API in three facts.** Base URL is `https://api.drin.run`. Authenticate with `Authorization: Bearer $DRIN_API_KEY`. Send and read JSON. That's the entire integration surface.
</Info>

## Send an email

The same request in three stacks. Each posts to `/v1/emails` and gets back `{ "id": "…" }` on `202 Accepted`.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.drin.run/v1/emails \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "from": { "email": "hello@acme.com" },
      "to": [{ "email": "customer@example.com" }],
      "subject": "Welcome aboard",
      "html": "<h1>You'\''re in</h1>"
    }'
  ```

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

  resp = requests.post(
      "https://api.drin.run/v1/emails",
      headers={
          "Authorization": f"Bearer {os.environ['DRIN_API_KEY']}",
          "Content-Type": "application/json",
      },
      json={
          "from": {"email": "hello@acme.com"},
          "to": [{"email": "customer@example.com"}],
          "subject": "Welcome aboard",
          "html": "<h1>You're in</h1>",
      },
      timeout=30,
  )
  resp.raise_for_status()
  print(resp.json()["id"])
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"os"
  )

  func main() {
  	body, _ := json.Marshal(map[string]any{
  		"from":    map[string]string{"email": "hello@acme.com"},
  		"to":      []map[string]string{{"email": "customer@example.com"}},
  		"subject": "Welcome aboard",
  		"html":    "<h1>You're in</h1>",
  	})

  	req, _ := http.NewRequest("POST", "https://api.drin.run/v1/emails", bytes.NewReader(body))
  	req.Header.Set("Authorization", "Bearer "+os.Getenv("DRIN_API_KEY"))
  	req.Header.Set("Content-Type", "application/json")

  	res, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	defer res.Body.Close()

  	var out struct {
  		ID string `json:"id"`
  	}
  	json.NewDecoder(res.Body).Decode(&out)
  	fmt.Println(out.ID)
  }
  ```
</CodeGroup>

<Note>
  **Prefer Python?** A first-party `drin` package is on PyPI — `pip install drin`. Or keep calling the REST API directly with the `requests` snippet above; it stays valid forever, since it's just HTTP.
</Note>

## Naming the project

A **project-scoped** key already knows which project it belongs to — send with just the `Authorization` header. An **account-wide** key spans every project, so you name the sender per request with `X-Drin-Product`:

```bash cURL theme={null}
curl https://api.drin.run/v1/emails \
  -H "Authorization: Bearer $DRIN_API_KEY" \
  -H "X-Drin-Product: my-project" \
  -H "Content-Type: application/json" \
  -d '{ "from": {"email":"hi@acme.com"}, "to":[{"email":"a@b.com"}], "subject":"Hi", "html":"<p>…</p>" }'
```

`X-Drin-Product` is the canonical header; `X-Drin-Sender` is an accepted alias. See [Authentication](/authentication) for the full picture and [API keys](/api-keys) for scoping.

## Pagination

List endpoints are cursor-paged: each response carries `{ data, nextCursor }`. Pass `cursor=$nextCursor` until `nextCursor` comes back `null`. Here's the loop in shell with `jq`:

```bash theme={null}
cursor=""
while : ; do
  resp=$(curl -s "https://api.drin.run/v1/emails?limit=100&cursor=$cursor" \
    -H "Authorization: Bearer $DRIN_API_KEY")
  echo "$resp" | jq -c '.data[]'
  cursor=$(echo "$resp" | jq -r '.nextCursor // empty')
  [ -z "$cursor" ] && break
done
```

See [Pagination](/api-reference/pagination) for the shared envelope and the `limit` bounds (1–100).

## Errors & retries

Non-2xx responses share one JSON envelope — `{ "error": { "type", "message", "param?" } }` — and echo a request id in `X-Request-Id`. On `429`, honour the `Retry-After` header before retrying; retry `5xx` idempotently by sending an `Idempotency-Key`. The full table lives in [Errors](/errors).

## Beyond HTTP

<CardGroup cols={3}>
  <Card title="CLI" icon="terminal" href="/agents/cli">
    `npx @drin00/cli` — send and inspect from a terminal or CI, no code at all.
  </Card>

  <Card title="MCP server" icon="plug" href="/agents/mcp">
    `npx @drin00/mcp` — give an AI agent the full API as tools.
  </Card>

  <Card title="SMTP" icon="envelope" href="/sending">
    Point any existing library at Drin's SMTP gateway instead of REST.
  </Card>
</CardGroup>

<Tip>
  **Generate a client.** Prefer a generated client in your language? The full [OpenAPI spec](/api-reference/overview) drives this reference and can feed your generator of choice.
</Tip>
