Skip to content
Pidgely Docs

Get started

Authentication

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

View as Markdown

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 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: any valid key can submit mail there.

Domain-restricted keys

Pass domain_id when you create a key 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>).

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
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
<?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
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
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
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 a new key and deploy it.
  2. 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, suppression list, domains and contacts. New accounts start in a sandbox on the Free plan; see Rate limits & quotas for what that means.