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

# Developer quickstart

> Create an API key, make your first platform API call, send an email with a sending key and look up its delivery status.

export const DemoVideo = ({id, title, embedUrl, src, poster, duration}) => {
  if (embedUrl) {
    return <figure className="brd-media" data-media-id={id}>
        <iframe className="brd-media-frame w-full aspect-video" src={embedUrl} title={title} allow="accelerometer; autoplay; clipboard-write; encrypted-media; picture-in-picture" allowFullScreen />
      </figure>;
  }
  if (src) {
    return <figure className="brd-media" data-media-id={id}>
        <video className="brd-media-frame w-full aspect-video" src={src} poster={poster} controls playsInline preload="metadata" />
      </figure>;
  }
  const label = duration ? `Video walkthrough coming soon · ${duration}` : "Video walkthrough coming soon";
  return <Placeholder id={id} kind="video" label={label} description={title} ratio="16 / 9" icon={<PlayIcon />} />;
};

export const Screenshot = ({id, src, srcDark, alt, caption}) => {
  if (!src) {
    return <Placeholder id={id} kind="screenshot" label="Screenshot coming soon" description={alt} icon={<ImageIcon />} />;
  }
  return <figure className="brd-media" data-media-id={id}>
      <img className="brd-media-frame block dark:hidden" src={src} alt={alt} />
      <img className="brd-media-frame hidden dark:block" src={srcDark || src} alt={alt} />
      {caption && <figcaption className="brd-media-caption">{caption}</figcaption>}
    </figure>;
};

In about ten minutes once your domain is verified, you'll have a working API key, a sent email and
its delivery status.

<Screenshot id="ss-developers-quickstart-hero" alt="The API Keys page listing one active key with its Key ID and scope count" />

This quickstart crosses two APIs on two hosts, with two different secrets. You'll collect both:

| Steps      | Host                                                    | Credential                                         |
| ---------- | ------------------------------------------------------- | -------------------------------------------------- |
| 1, 2 and 5 | `core-service.prod.brudcast.com`, the platform API      | A platform **API key**, sent as `X-API-Key`        |
| 3 and 4    | `mailing-service.prod.brudcast.com`, the email send API | A **sending key**, sent as `Authorization: Bearer` |

Both secrets are `bk_live_` followed by 64 hex characters, so you can't tell them apart by looking.
Keep them in `BRUDCAST_API_KEY` and `BRUDCAST_SENDING_KEY`, the names used throughout this page. See
[Authentication](/developers/authentication).

<Info>
  **Before you start**, you need a terminal with `curl`, or Node.js, Python or PHP.
</Info>

## Step 0: verify your sending domain

Every step below needs a sending domain of your own, added and verified. Verification waits on a DNS
change, which can take a few hours to spread, so start it before anything else.

1. [Add a sending domain](/channels/email/add-a-sending-domain). Brudcast generates five DNS records.
2. Publish them with your DNS provider. See [DNS records](/channels/email/dns-records) and
   [DNS by provider](/channels/email/dns-by-provider).
3. [Verify your domain](/channels/email/verify-your-domain). It shows **Verified** once the records
   resolve.

The **Brudcast trial address** has no sending keys and no SMTP credentials, so it can't be used with
the send API or SMTP. See [Email overview](/channels/email/overview).

<Accordion title="Watch the walkthrough" icon="circle-play">
  <DemoVideo id="V19" title="Developer quickstart: API key to first send" duration="3 min" />
</Accordion>

<Steps>
  <Step title="Create an API key">
    1. Go to **Developers > API Keys** and select **Create API Key**.
    2. Enter a **Key Name**, for example `Quickstart`.
    3. Under **Permissions**, set **Sending Domains** to **Read Sending Domains** and **Messages**
       to **Read Message Log**. Leave everything else at **No access**.
    4. Select **Create**.
    5. Copy the **Secret Key**. It starts with `bk_live_` and is shown only once.

    <Screenshot id="ss-developers-quickstart-01-create-key" alt="The Create API Key panel with Sending Domains and Messages set to read access" />

    Store the key in an environment variable rather than in your code:

    ```bash theme={"system"}
    export BRUDCAST_API_KEY="bk_live_your_api_key"
    ```
  </Step>

  <Step title="Make your first platform API call">
    List your sending domains. This proves the key works and gives you the domain to send from.

    <CodeGroup>
      ```bash cURL theme={"system"}
      curl https://core-service.prod.brudcast.com/api/v1/user/sending-domains \
        -H "X-API-Key: $BRUDCAST_API_KEY"
      ```

      ```javascript Node.js theme={"system"}
      const response = await fetch(
        "https://core-service.prod.brudcast.com/api/v1/user/sending-domains",
        { headers: { "X-API-Key": process.env.BRUDCAST_API_KEY } },
      );

      console.log(await response.json());
      ```

      ```python Python theme={"system"}
      import os
      import requests

      response = requests.get(
          "https://core-service.prod.brudcast.com/api/v1/user/sending-domains",
          headers={"X-API-Key": os.environ["BRUDCAST_API_KEY"]},
      )

      print(response.json())
      ```

      ```php PHP theme={"system"}
      <?php
      $ch = curl_init("https://core-service.prod.brudcast.com/api/v1/user/sending-domains");
      curl_setopt_array($ch, [
          CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("BRUDCAST_API_KEY")],
          CURLOPT_RETURNTRANSFER => true,
      ]);

      echo curl_exec($ch);
      ```
    </CodeGroup>

    Each domain in `data` has more fields than shown here, and the response also carries `meta`
    with pagination details:

    ```json theme={"system"}
    {
      "success": true,
      "message": "Sending domains fetched successfully",
      "data": [
        {
          "id": "0b9d7c1e-5f7a-4a57-9a0e-3c2f5d1e8a41",
          "name": "mail.example.com",
          "status": "verified"
        }
      ]
    }
    ```

    Find a domain whose `status` is `verified`. You'll send from an address on it.
  </Step>

  <Step title="Create a sending key">
    The email send API uses its own credential, bound to one domain.

    1. Go to **Channels > Email** and open the **Domains** tab.
    2. Select your domain, then open its **Sending Keys** tab.
    3. Select **Add Sending Key**, enter a **Description** and select **Create Key**.
    4. Copy the **API Secret Key**. It's shown only once.

    <Screenshot id="ss-developers-quickstart-03-sending-key" alt="The Sending Key Created dialog showing the API Key ID and the API Secret Key" />

    ```bash theme={"system"}
    export BRUDCAST_SENDING_KEY="your_sending_key"
    ```

    <Note>
      A new key shows `provisioning` in the **Status** column for a moment while the send API
      receives it. Wait until it shows `active`.
    </Note>
  </Step>

  <Step title="Send an email">
    Change the `from` address to one on your verified domain and the `to` address to an inbox you
    can check.

    <CodeGroup>
      ```bash cURL theme={"system"}
      curl -X POST https://mailing-service.prod.brudcast.com/api/v1/send \
        -H "Authorization: Bearer $BRUDCAST_SENDING_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "from": { "name": "Example", "address": "hello@mail.example.com" },
          "to": [{ "name": "Jane", "address": "jane@example.com" }],
          "subject": "My first Brudcast email",
          "text": "It works.",
          "html": "<p>It works.</p>"
        }'
      ```

      ```javascript Node.js theme={"system"}
      const response = await fetch("https://mailing-service.prod.brudcast.com/api/v1/send", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.BRUDCAST_SENDING_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          from: { name: "Example", address: "hello@mail.example.com" },
          to: [{ name: "Jane", address: "jane@example.com" }],
          subject: "My first Brudcast email",
          text: "It works.",
          html: "<p>It works.</p>",
        }),
      });

      console.log(response.status, await response.json());
      ```

      ```python Python theme={"system"}
      import os
      import requests

      response = requests.post(
          "https://mailing-service.prod.brudcast.com/api/v1/send",
          headers={"Authorization": f"Bearer {os.environ['BRUDCAST_SENDING_KEY']}"},
          json={
              "from": {"name": "Example", "address": "hello@mail.example.com"},
              "to": [{"name": "Jane", "address": "jane@example.com"}],
              "subject": "My first Brudcast email",
              "text": "It works.",
              "html": "<p>It works.</p>",
          },
      )

      print(response.status_code, response.json())
      ```

      ```php PHP theme={"system"}
      <?php
      $payload = [
          "from" => ["name" => "Example", "address" => "hello@mail.example.com"],
          "to" => [["name" => "Jane", "address" => "jane@example.com"]],
          "subject" => "My first Brudcast email",
          "text" => "It works.",
          "html" => "<p>It works.</p>",
      ];

      $ch = curl_init("https://mailing-service.prod.brudcast.com/api/v1/send");
      curl_setopt_array($ch, [
          CURLOPT_POST => true,
          CURLOPT_HTTPHEADER => [
              "Authorization: Bearer " . getenv("BRUDCAST_SENDING_KEY"),
              "Content-Type: application/json",
          ],
          CURLOPT_POSTFIELDS => json_encode($payload),
          CURLOPT_RETURNTRANSFER => true,
      ]);

      echo curl_exec($ch);
      ```
    </CodeGroup>

    A `202 Accepted` response means Brudcast queued the message:

    ```json theme={"system"}
    {
      "status": true,
      "message": "Email queued for delivery",
      "data": { "jobId": "3f1c2a9e-8b7d-4c6e-9a51-2d0e7f4b6c10" }
    }
    ```

    Keep the `jobId`. It identifies this message everywhere else. Take the value from your own
    response, not from the example above, and put it in an environment variable. The next step reads
    it:

    ```bash theme={"system"}
    export BRUDCAST_JOB_ID="your_job_id"
    ```
  </Step>

  <Step title="Check the message status">
    Look the message up in the message log with your API key from step 1.

    <CodeGroup>
      ```bash cURL theme={"system"}
      curl https://core-service.prod.brudcast.com/api/v1/user/messages/$BRUDCAST_JOB_ID \
        -H "X-API-Key: $BRUDCAST_API_KEY"
      ```

      ```javascript Node.js theme={"system"}
      const jobId = process.env.BRUDCAST_JOB_ID;
      const response = await fetch(
        `https://core-service.prod.brudcast.com/api/v1/user/messages/${jobId}`,
        { headers: { "X-API-Key": process.env.BRUDCAST_API_KEY } },
      );

      console.log(await response.json());
      ```

      ```python Python theme={"system"}
      import os
      import requests

      job_id = os.environ["BRUDCAST_JOB_ID"]
      response = requests.get(
          f"https://core-service.prod.brudcast.com/api/v1/user/messages/{job_id}",
          headers={"X-API-Key": os.environ["BRUDCAST_API_KEY"]},
      )

      print(response.json())
      ```

      ```php PHP theme={"system"}
      <?php
      $jobId = getenv("BRUDCAST_JOB_ID");
      $ch = curl_init("https://core-service.prod.brudcast.com/api/v1/user/messages/" . $jobId);
      curl_setopt_array($ch, [
          CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("BRUDCAST_API_KEY")],
          CURLOPT_RETURNTRANSFER => true,
      ]);

      echo curl_exec($ch);
      ```
    </CodeGroup>

    Once the receiving mail server has answered, each delivery attempt shows up with its outcome:

    ```json theme={"system"}
    {
      "success": true,
      "message": "Message fetched successfully",
      "data": {
        "jobId": "3f1c2a9e-8b7d-4c6e-9a51-2d0e7f4b6c10",
        "senderAddress": "hello@mail.example.com",
        "recipients": ["jane@example.com"],
        "deliveryAttempts": [
          {
            "recipient": "jane@example.com",
            "status": "delivered",
            "smtpCode": 250,
            "attemptedAt": "2026-09-11T09:30:12.000Z"
          }
        ],
        "engagementEvents": [],
        "retentionDays": 90
      }
    }
    ```

    A `404` right after sending is normal. The message enters the log only once the receiving
    server has responded, so wait a few seconds and try again. A `404` that never clears usually
    means `BRUDCAST_JOB_ID` holds something other than the `jobId` your own send returned. See
    [Message status](/developers/sending/message-status) for every field.
  </Step>
</Steps>

## What's next

Instead of polling the message log, have Brudcast call you when something happens. Create a
webhook endpoint and subscribe to `email.delivered`, `email.bounced` and the other email events.

<AccordionGroup>
  <Accordion title="“Unauthorized access” from the platform API" icon="circle-alert">
    **Why:** The key is missing, mistyped, revoked or expired, or your server's IP address isn't on
    the key's IP allowlist.

    **Fix:** Send the full key in `X-API-Key`, including the `bk_live_` prefix. Check the key's
    status and IP allowlist in **Developers > API Keys**.
  </Accordion>

  <Accordion title="“This API key is missing the domains:read scope required by this endpoint…”" icon="circle-alert">
    **Why:** The key was created without the permission this endpoint needs.

    **Fix:** Open the key in **Developers > API Keys** and edit its **Permissions**.
  </Accordion>

  <Accordion title="“Unauthorized” from the send API" icon="circle-alert">
    **Why:** The send API reads only the `Authorization` header. The key is missing, isn't prefixed
    with `Bearer `, or was deleted or regenerated.

    **Fix:** Send `Authorization: Bearer ` followed by the sending key. If you just created or
    regenerated the key, wait until its status is `active`.
  </Accordion>

  <Accordion title="“From address domain 'x' does not match sending key domain 'y'”" icon="circle-alert">
    **Why:** A sending key can only send from the domain it was created on.

    **Fix:** Use a `from` address on that domain, or create a sending key on the other domain.
  </Accordion>
</AccordionGroup>

## Related

<Columns cols={2}>
  <Card title="Email send API" icon="send" href="/developers/sending/http-api">
    Attachments, Cc and Bcc, custom headers and every error.
  </Card>

  <Card title="Sending keys" icon="key-round" href="/developers/sending-keys">
    The credential the send API needs, and how it differs from an API key.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/developers/webhooks/overview">
    Get delivery, bounce and engagement events pushed to you.
  </Card>

  <Card title="API keys" icon="key-round" href="/developers/api-keys">
    Scopes, expiry, IP allowlists and request logs.
  </Card>

  <Card title="Authentication" icon="shield" href="/developers/authentication">
    Which credential works on which surface.
  </Card>
</Columns>
