Skip to main content

Authenticating Requests

Every request to the Dailybin API needs a tenant-scoped JWT. You get one automatically when you sign in to the dashboard, but you can also create dedicated tokens for individual agents.

Pass your token in the Authorization header as a Bearer token.

Method Example
Bearer header Authorization: Bearer <jwt>

Understanding scopes

Tokens are scoped so you can give each agent only the permissions it needs. A weather agent that only posts content needs ingest:write; a monitoring script that checks delivery status needs digest:read.

Scope Grants
ingest:write Submit content via /v1/mail
digest:read List upcoming digests, preview, list sends
digest:retry Retry failed digest sends
token:manage Create and revoke agent tokens

Adding a Section

To add content to your next daily digest, POST a JSON object with a section name and some markdown content. Dailybin groups entries by section, deduplicates them per day, and rolls everything into a single email on schedule.

If two agents post the same content to the same section on the same day, only one copy makes it into the digest. You can safely fire-and-forget from multiple sources without worrying about duplicates.

Scope: ingest:write

Request body

Field Type Required Description
section string yes Section heading (1-64 chars)
content string yes Markdown content (1-20KB)
source string no Agent name / identifier (1-80 chars)

Response (202)

response.json
{
  "accepted": true,
  "duplicate": false,
  "digestDate": "2026-02-15",
  "submissionId": "sub_01abc..."
}

Example

Here's a weather agent posting its morning report. The section name becomes the heading in the digest email:

terminal
$ curl -X POST https://api.dailybin.dev/v1/mail \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "section": "weather",
    "content": "Sunny, 72°F. Great day ahead.",
    "source": "weather-agent"
  }'

Managing Tokens

The dashboard gives you a default token that works for most setups. If you need finer control — separate tokens per agent, limited scopes, or expiration dates — you can create and revoke tokens through the API.

Requires token:manage scope.

Creating a token

Give the token a name you'll recognize later and the scopes it needs. The response includes the signed JWT — store it somewhere safe, because you won't see it again.

POST /v1/admin/tokens

Field Type Required Description
name string yes Human-readable token name (1-80 chars)
scopes string[] yes Array of scope strings
expiresAt datetime no ISO 8601 expiration time
response.json (201)
{
  "id": "tok_01abc...",
  "name": "weather-agent",
  "jti": "jti_...",
  "scopes": ["ingest:write"],
  "expiresAt": null,
  "createdAt": "2026-02-15T12:00:00Z",
  "token": "eyJhbGciOi..."
}

Revoking a token

If a token is compromised or an agent is retired, revoke it. The token becomes immediately unusable — any in-flight requests using it will fail with a 401.

POST /v1/admin/tokens/:tokenId/revoke

response.json (200)
{
  "revoked": true,
  "tokenId": "tok_01abc...",
  "revokedAt": "2026-02-15T14:30:00Z"
}

Viewing Digests

To see what's queued up for your next email, list your upcoming digests. Each entry shows the scheduled date, current status, and how many send attempts have been made.

Scope: digest:read

GET /v1/admin/digests/upcoming

response.json
{
  "items": [
    {
      "id": "dig_01abc...",
      "digestDate": "2026-02-15",
      "scheduledAt": "2026-02-15T14:00:00Z",
      "status": "pending",
      "attemptCount": 0
    }
  ]
}

Previewing a Digest

Before a digest is sent, you can preview exactly what it will look like. The response includes both the raw markdown and the rendered HTML, so you can inspect the content or pipe it into your own tooling.

Scope: digest:read

GET /v1/admin/digests/:digestId/preview

response.json
{
  "id": "dig_01abc...",
  "digestDate": "2026-02-15",
  "status": "pending",
  "markdownBody": "## Weather\nSunny, 72°F...",
  "htmlBody": "<h2>Weather</h2><p>Sunny, 72°F...</p>"
}

Retrying a Failed Send

If a digest fails to send (usually a transient email provider issue), you can retry it. Dailybin will re-render the content and attempt delivery again. The digest must be in a failed state — you'll get a 409 if you try to retry a digest that's already sent or still pending.

Scope: digest:retry

POST /v1/admin/digests/:digestId/retry

response.json
{
  "accepted": true,
  "digestId": "dig_01abc..."
}

Checking Send History

To confirm your digests are being delivered, pull your recent send history. Each entry shows whether the attempt succeeded or failed, the provider message ID for tracking, and any error messages.

Scope: digest:read

GET /v1/admin/sends

response.json
{
  "items": [
    {
      "digestId": "dig_01abc...",
      "attemptNo": 1,
      "provider": "ses",
      "status": "success",
      "providerMessageId": "01abc...",
      "errorMessage": null,
      "attemptedAt": "2026-02-15T14:00:05Z"
    }
  ]
}

Connecting via MCP

If your AI agent supports the Model Context Protocol, you can skip the REST API entirely. Dailybin exposes an MCP server at /mcp using Streamable HTTP transport. Point your MCP client at it and your agent can post sections, send emails, and check digest status using native tool calls.

Setting up Claude Desktop

Add this block to your Claude Desktop config. Replace the token with your own:

claude_desktop_config.json
{
  "mcpServers": {
    "dailybin": {
      "url": "https://api.dailybin.dev/mcp",
      "headers": {
        "Authorization": "Bearer <your-token>"
      }
    }
  }
}

Setting up Claude Code

If you're using Claude Code, you can add Dailybin as an MCP server from the CLI. Run this once and it's available in every session:

terminal
$ claude mcp add dailybin https://api.dailybin.dev/mcp --transport http --scope user --header "Authorization: Bearer <your-token>"

Handling Errors

When something goes wrong, Dailybin returns a consistent error envelope with a machine-readable code and a human-readable message. You can match on the code in your agent's error handling logic.

error response
{
  "error": {
    "code": "INVALID_PAYLOAD",
    "message": "section is required"
  }
}

Error codes

Code HTTP When you'll see it
UNAUTHORIZED 401 Token is missing, expired, or malformed
FORBIDDEN 403 Token doesn't have the required scope, or was revoked
INVALID_PAYLOAD 400 Request body is missing fields or has invalid values
NOT_FOUND 404 The digest or token you referenced doesn't exist
CONFLICT 409 You tried to retry a digest that isn't in a failed state
RATE_LIMITED 429 You've exceeded 60 requests per minute for this token
INTERNAL_ERROR 500 Something broke on our end — safe to retry