# Authentication

> Authenticate with API keys sent as Bearer tokens. Choose full_access or sending_access, and restrict sending keys to a single domain.

Source: https://docs.pidgely.com/authentication/

Every request to the API carries an API key in the `Authorization` header:

```http
POST /emails HTTP/1.1
Host: api.pidgely.com
Authorization: Bearer pdg_k3x7m2qa4bnp6c5d_xyy4u7c2r3w5t6v7m8n9p2q3r4s5t6u7
Content-Type: application/json
```

The scheme must be exactly `Bearer ` (capital B, one space). A request without it gets `401 missing_api_key`; an unknown or revoked key gets `403 invalid_api_key`.

## Key format

Keys look like `pdg_<prefix>_<secret>`: a public prefix that identifies the key and a secret part. Pidgely stores only an HMAC of the secret, so the full key is shown **once**, when you create it. If you lose it, create a new key and revoke the old one.

## Permissions

| Permission | Can call | Use it for |
|---|---|---|
| `full_access` (default) | Every endpoint | Back-office scripts, infrastructure as code, the [MCP server](https://docs.pidgely.com/mcp/) when you trust the agent |
| `sending_access` | `POST /emails`, `POST /emails/batch` and `GET /emails/{id}` only | Application servers that only send |

A `sending_access` key that calls any other endpoint gets:

```json
{
  "statusCode": 401,
  "name": "restricted_api_key",
  "message": "This API key is restricted to only send emails."
}
```

The same applies to [SMTP](https://docs.pidgely.com/guides/smtp/): any valid key can submit mail there.

## Domain-restricted keys

Pass `domain_id` when you [create a key](https://docs.pidgely.com/api-reference/api-keys/create/) to restrict it to one verified domain. Such a key is always `sending_access`, and sending from any other domain fails with `403 validation_error` (`this API key cannot send from <domain>`).

```js tab="Node.js"
import { Pidgely } from 'pidgely';

const pidgely = new Pidgely(process.env.PIDGELY_API_KEY);

const { data, error } = await pidgely.apiKeys.create({
  name: 'Web app (acme.com)',
  permission: 'sending_access',
  domainId: '0199d7f0-1c2b-7e4d-a5f6-0b9c8d7e6f51',
});
console.log(data ?? error);
```

```python tab="Python"
import os
import pidgely

pidgely.api_key = os.environ["PIDGELY_API_KEY"]

result = pidgely.ApiKeys.create({
    "name": "Web app (acme.com)",
    "permission": "sending_access",
    "domain_id": "0199d7f0-1c2b-7e4d-a5f6-0b9c8d7e6f51",
})
print(result)
```

```php tab="PHP"
<?php

require __DIR__ . '/vendor/autoload.php';

$pidgely = Pidgely::client(getenv('PIDGELY_API_KEY'));

$result = $pidgely->apiKeys->create([
    'name' => 'Web app (acme.com)',
    'permission' => 'sending_access',
    'domain_id' => '0199d7f0-1c2b-7e4d-a5f6-0b9c8d7e6f51',
]);
var_dump($result);
```

```go tab="Go"
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/pidgely/pidgely-go"
)

func main() {
	ctx := context.Background()
	client := pidgely.NewClient(os.Getenv("PIDGELY_API_KEY"))

	result, err := client.ApiKeys.Create(ctx, &pidgely.CreateApiKeyRequest{
		Name:       "Web app (acme.com)",
		Permission: "sending_access",
		DomainId:   "0199d7f0-1c2b-7e4d-a5f6-0b9c8d7e6f51",
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(result)
}
```

```ruby tab="Ruby"
require "pidgely"

Pidgely.api_key = ENV["PIDGELY_API_KEY"]

result = Pidgely::ApiKeys.create({
  name: "Web app (acme.com)",
  permission: "sending_access",
  domain_id: "0199d7f0-1c2b-7e4d-a5f6-0b9c8d7e6f51"
})
puts result
```

```bash tab="cURL"
curl -X POST 'https://api.pidgely.com/api-keys' \
  -H "Authorization: Bearer $PIDGELY_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Web app (acme.com)",
  "permission": "sending_access",
  "domain_id": "0199d7f0-1c2b-7e4d-a5f6-0b9c8d7e6f51"
}'
```

Give each service its own restricted key: a leaked key can then only send from one domain and can't read your contacts, change DNS settings or create new keys.

## Keep keys on the server

The API does not answer CORS requests, so it can't be called from a browser, and it shouldn't be: anyone could read a key embedded in front-end code. Call Pidgely from your backend, a serverless function or a job runner, and load the key from an environment variable or a secret manager.

## Rotating and revoking keys

1. [Create](https://docs.pidgely.com/api-reference/api-keys/create/) a new key and deploy it.
2. [Delete](https://docs.pidgely.com/api-reference/api-keys/delete/) the old key.

Revocation takes effect within about 30 seconds, as authentication results are cached briefly. `GET /api-keys` shows `last_used_at` for each key, which helps to spot keys that are no longer used.

## Accounts and limits

All keys of an account share the same [rate limit and quotas](https://docs.pidgely.com/rate-limits/), suppression list, domains and contacts. New accounts start in a sandbox on the Free plan; see [Rate limits & quotas](https://docs.pidgely.com/rate-limits/#plans) for what that means.
