# DailyDraft — Full API & Platform Reference > Comprehensive reference for AI agents managing DailyDraft newsletters. Covers authentication, every REST endpoint, newsletter lifecycle, domain setup, AI features, webhooks, error handling, and end-to-end workflows. --- ## Account Creation & Onboarding {#onboarding} AI agents that need to provision a fresh DailyDraft account must use **browser automation** for the signup and onboarding phases (Playwright, Puppeteer, Selenium, or equivalent). There are no REST endpoints for account or newsletter creation — the REST API operates on resources that already exist. After completing onboarding, all subsequent operations use the REST API. ### Phase 1 — Register a User Account Navigate to the signup page and submit the registration form: ``` GET https://www.dailydraft.ai/signup POST https://www.dailydraft.ai/signup Form fields: user[email_address] = "agent@example.com" user[password] = "SecurePassword123!" user[password_confirmation] = "SecurePassword123!" ``` Success: HTTP 302 → `/email_confirmation_pending` Flash message: "Please check your email to confirm your account." ### Phase 2 — Confirm Email DailyDraft sends a confirmation email to the registered address. Extract the confirmation link and navigate to it: ``` Confirmation link format: https://www.dailydraft.ai/email_confirmations/ GET https://www.dailydraft.ai/email_confirmations/ ``` Success: session created, HTTP 302 → `/newsletters` Flash: "Email confirmed! Welcome to DailyDraft!" If the token has expired, navigate to `/email_confirmation_required` and submit the resend form (POST `/email_confirmations/resend`). ### Phase 3 — Sign In (for existing accounts) ``` GET https://www.dailydraft.ai/session/new POST https://www.dailydraft.ai/session Form fields: email_address = "agent@example.com" password = "SecurePassword123!" ``` Success: HTTP 302 → `/newsletters` ### Phase 4 — Create a Newsletter ``` GET https://www.dailydraft.ai/newsletters/new POST https://www.dailydraft.ai/newsletters Form fields: newsletter[name] = "AI Stack Weekly" newsletter[tagline] = "Weekly AI tooling for builders" newsletter[keywords] = "artificial intelligence, Claude, Cursor, LLMs" newsletter[from_name] = "Todd Dickerson" newsletter[from_email] = "todd@example.com" ``` Success: HTTP 302 → `/newsletters/:id/setup_frequency` Flash: "Newsletter created! Let's set up your publishing schedule..." ### Phase 5 — Set Publishing Frequency ``` GET https://www.dailydraft.ai/newsletters/:id/setup_frequency POST https://www.dailydraft.ai/newsletters/:id/apply_frequency Form fields: frequency = "weekly" # Options: "daily" (Mon-Fri), "three_times" (Mon/Wed/Fri), # "weekly" (Tuesdays), "skip" (decide later) ``` Success: HTTP 302 → `/newsletters/:id/preference/edit` ### Phase 6 — Set Preferences ``` GET https://www.dailydraft.ai/newsletters/:id/preference/edit PATCH https://www.dailydraft.ai/newsletters/:id/preference Form fields: newsletter_preference[target_audience] = "B2B software developers who use AI tools" newsletter_preference[content_tone] = "professional" newsletter_preference[newsletter_length] = "medium" ``` Success: HTTP 302 → `/newsletters/:id` ### Phase 7 — Generate API Key ``` GET https://www.dailydraft.ai/newsletters/:id/api_keys/new POST https://www.dailydraft.ai/newsletters/:id/api_keys Form fields: api_key[name] = "ea-agent" api_key[permissions][] = "read" api_key[permissions][] = "write" # Also available: "delete" ``` Success: page displays the full API key **exactly once**. Capture it immediately before navigating away. Key format: `<8-char-prefix><64-char-hex>` (total: 72 chars) ### Phase 8 — Use the REST API All subsequent operations use the key captured in Phase 7. See sections below for full endpoint reference. ### Full Onboarding Checklist ``` ✅ 1. POST /signup → redirect to /email_confirmation_pending ✅ 2. GET /email_confirmations/:token → session created ✅ 3. POST /newsletters → redirect to /setup_frequency ✅ 4. POST /apply_frequency → redirect to /preference/edit ✅ 5. PATCH /preference → redirect to /newsletters/:id ✅ 6. POST /api_keys → capture full key (shown once) ✅ 7. API GET /newsletter → 200 with newsletter JSON ✅ 8. API POST /issues → 201 with issue id ✅ 9. API PATCH /issues/:id → status = "approved" ✅ 10. API POST /issues/:id/publish → 200 with web_url ✅ 11. API POST /subscribers/bulk_create → {created: N, errors: []} ✅ 12. API GET /newsletter/stats → total_subscribers > 0 ``` --- ## Authentication All REST API requests require a Bearer token in the `Authorization` header: ``` Authorization: Bearer YOUR_API_KEY ``` API keys are scoped to a single newsletter and carry one or more permissions: - `read` — GET requests only - `write` — create and update resources - `delete` — destroy resources Rate limit: **1000 requests per hour** per API key. Headers returned: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` Generate API keys at: `Settings → API Keys` in the newsletter dashboard. --- ## Base URL ``` https://www.dailydraft.ai/api/v1 ``` All responses are JSON. All timestamps are ISO 8601. --- ## Newsletter {#newsletter} ### GET /newsletter Returns the newsletter profile for the authenticated API key. ``` GET /api/v1/newsletter Authorization: Bearer ``` Response: ```json { "id": 42, "name": "AI Stack Weekly", "tagline": "The latest in AI tooling for developers", "subdomain": "ai-stack-weekly", "from_name": "Todd Dickerson", "from_email": "todd@aistack.com", "tier": "paid", "status": "active", "primary_color": "#1A1A2E", "created_at": "2025-10-01T00:00:00Z", "updated_at": "2026-06-01T12:00:00Z" } ``` ### PATCH /newsletter Update newsletter profile fields. Requires `write` permission. ``` PATCH /api/v1/newsletter Authorization: Bearer Content-Type: application/json { "newsletter": { "name": "AI Stack Weekly", "tagline": "Weekly AI tooling for builders", "from_name": "Todd Dickerson", "from_email": "todd@aistack.com", "primary_color": "#1A1A2E", "archive_title": "Issues Archive", "archive_description": "All past editions of AI Stack Weekly" } } ``` Updatable fields: `name`, `tagline`, `from_name`, `from_email`, `primary_color`, `archive_title`, `archive_description` ### GET /newsletter/stats Returns aggregate stats for the newsletter. ``` GET /api/v1/newsletter/stats Authorization: Bearer ``` Response: ```json { "newsletter_id": 42, "name": "AI Stack Weekly", "stats": { "total_subscribers": 1420, "total_issues": 38, "published_issues": 35, "total_articles": 214, "tier": "paid" } } ``` --- ## Issues {#issues} Issues are newsletter editions. Lifecycle: `draft` → `approved` → `sending` → `sent`. Can also be `scheduled` (AI-drafted awaiting review) or `published` (web-only, no email). ### Issue Statuses | Status | Meaning | |--------|---------| | `draft` | In progress, not yet reviewed | | `scheduled` | AI draft complete, awaiting editor review (or auto-send queued) | | `approved` | Editor approved, ready to send | | `sending` | Email send in progress | | `sent` | Email sent to subscribers | | `published` | Published to web archive only (no email) | ### GET /issues List issues. Paginated. Optional `status` filter. ``` GET /api/v1/issues?status=draft&page=1&per_page=25 Authorization: Bearer ``` Response: ```json { "issues": [ { "id": 101, "newsletter_id": 42, "subject": "AI Stack Weekly #38 — Cursor, Claude 4, and the death of boilerplate", "status": "draft", "scheduled_for": null, "sent_at": null, "articles_count": 6, "created_at": "2026-06-20T08:00:00Z", "updated_at": "2026-06-20T09:30:00Z" } ], "meta": { "page": 1, "per_page": 25, "has_more": false } } ``` ### GET /issues/:id Get a single issue including full content and articles. ``` GET /api/v1/issues/101 Authorization: Bearer ``` Response includes all list fields plus: ```json { "content_markdown": "## This week...\n\n...", "articles": [ { "id": 500, "title": "Cursor 0.50 ships with background agents", "url": "https://techcrunch.com/...", "position": 1, "summarized_article": "Cursor's new background agents mode lets developers..." } ] } ``` ### POST /issues Create a new issue. Requires `write` permission. ``` POST /api/v1/issues Authorization: Bearer Content-Type: application/json { "issue": { "subject": "AI Stack Weekly #39 — What we're building with agents", "status": "draft", "content_markdown": "## This week\n\nWe're deep in agent territory...", "scheduled_for": "2026-06-27T14:00:00Z" } } ``` Fields: - `subject` (required) — Email subject line - `status` (optional) — defaults to `draft` - `content_markdown` (optional) — Full issue body in Markdown - `scheduled_for` (optional) — ISO 8601 datetime to auto-send Response: `201 Created` with issue JSON. ### PATCH /issues/:id Update an existing issue. Requires `write` permission. ``` PATCH /api/v1/issues/101 Authorization: Bearer Content-Type: application/json { "issue": { "subject": "Updated subject line", "status": "approved", "content_markdown": "## Updated content..." } } ``` To approve an issue for sending: set `"status": "approved"`. ### DELETE /issues/:id Delete an issue. Requires `delete` permission. ``` DELETE /api/v1/issues/101 Authorization: Bearer ``` Returns `204 No Content`. ### POST /issues/:id/send_now Send an issue immediately to all active subscribers. Requires `write` permission. Newsletter must be on **paid tier**. Issue must have status `approved` or `scheduled`. ``` POST /api/v1/issues/101/send_now Authorization: Bearer ``` Response: ```json { "message": "Newsletter queued for sending", "issue": { ...issue_json... }, "subscribers": 1420 } ``` Errors: - `402 Payment Required` — newsletter is on free tier - `422 Unprocessable Entity` — issue not in correct status, or no subscribers ### POST /issues/:id/publish Publish an issue to the web archive without sending email. Requires `write` permission. Issue must have `content_markdown` or `combined_markdown`. ``` POST /api/v1/issues/101/publish Authorization: Bearer ``` Response: ```json { "message": "Issue published to web", "issue": { ...issue_json... }, "web_url": "https://ai-stack-weekly.dailydraft.ai/issues/ai-stack-weekly-38" } ``` --- ## Subscribers {#subscribers} ### GET /subscribers List subscribers. Optional `status` filter: `active`, `unsubscribed`. ``` GET /api/v1/subscribers?status=active&page=1&per_page=25 Authorization: Bearer ``` Response: ```json { "subscribers": [ { "id": 9001, "email": "user@example.com", "status": "active", "subscribed_at": "2026-01-15T10:00:00Z", "unsubscribed_at": null, "tags": ["beta", "power-user"], "created_at": "2026-01-15T10:00:00Z", "updated_at": "2026-01-15T10:00:00Z" } ], "meta": { "page": 1, "per_page": 25, "has_more": true } } ``` ### GET /subscribers/:id Get a single subscriber. ### POST /subscribers Add a subscriber. Requires `write` permission. Idempotent — returns `200 OK` if already exists. ``` POST /api/v1/subscribers Authorization: Bearer Content-Type: application/json { "email": "newuser@example.com" } ``` Returns `201 Created` for new subscribers, `200 OK` for existing. ### PATCH /subscribers/:id Update a subscriber (e.g., change status). Requires `write` permission. ``` PATCH /api/v1/subscribers/9001 Authorization: Bearer Content-Type: application/json { "subscriber": { "status": "unsubscribed" } } ``` ### DELETE /subscribers/:id Remove a subscriber. Requires `delete` permission. ### POST /subscribers/:id/add_tag Add a tag to a subscriber. Tag is normalized lowercase. Requires `write` permission. ``` POST /api/v1/subscribers/9001/add_tag Authorization: Bearer Content-Type: application/json { "tag": "vip" } ``` ### DELETE /subscribers/:id/remove_tag Remove a tag from a subscriber. Requires `write` permission. ``` DELETE /api/v1/subscribers/9001/remove_tag Authorization: Bearer Content-Type: application/json { "tag": "vip" } ``` ### POST /subscribers/bulk_create Import up to 1000 subscribers at once. Requires `write` permission. ``` POST /api/v1/subscribers/bulk_create Authorization: Bearer Content-Type: application/json { "subscribers": [ { "email": "user1@example.com" }, { "email": "user2@example.com" }, "user3@example.com" ] } ``` Response: ```json { "created": 2, "existing": 1, "errors": [] } ``` --- ## Webhook Subscriptions {#webhooks} Register URLs to receive event notifications. Events are delivered as POST requests with a JSON body and `X-DailyDraft-Signature` header for verification. ### GET /webhook_subscriptions List all webhook subscriptions. ### POST /webhook_subscriptions Create a webhook. Requires `write` permission. ``` POST /api/v1/webhook_subscriptions Authorization: Bearer Content-Type: application/json { "event": "issue.sent", "target_url": "https://your-app.com/hooks/dailydraft" } ``` Common events: `issue.sent`, `subscriber.created`, `subscriber.unsubscribed` Response: `201 Created` with `{ "id": 77 }` ### DELETE /webhook_subscriptions/:id Remove a webhook. Requires `write` permission. --- ## Custom Domains {#domains} Custom domains are managed through the web dashboard. The underlying system uses **Cloudflare for SaaS** with automated hostname registration and SSL provisioning. ### Setup Flow (via web dashboard) 1. Navigate to your newsletter → **Settings → Custom Domains** 2. Click **Add Custom Domain** 3. Enter your domain (e.g., `newsletter.yourdomain.com`) 4. DailyDraft registers it with Cloudflare and returns DNS instructions 5. Add the required DNS records at your domain registrar: **Type A / CNAME records required:** ``` CNAME newsletter.yourdomain.com → .dailydraft.ai ``` Or point to the Cloudflare fallback origin IP. DailyDraft will also show any required TXT verification records. 6. Click **Verify** — DailyDraft runs a live DNS check 7. SSL is auto-provisioned once DNS propagates (can take up to 24h) ### Domain Status Values | Status | Meaning | |--------|---------| | `pending` | Registered with Cloudflare, awaiting DNS setup | | `active` | DNS verified, SSL active — domain is live | | `failed` | DNS check failed or Cloudflare registration error | ### Troubleshooting - If stuck in `pending` for >1h after adding DNS: use **Retry Setup** to reset and re-register - **Check DNS** button shows live DNS resolution without modifying any records - If on Cloudflare registrar: ensure CNAME is set to "Proxied" (orange cloud) - Verification records (TXT) are only required once; can be removed after domain is `active` --- ## AI Features {#ai} AI features are available through the web dashboard. The API supports delivering AI-drafted content via the `content_markdown` field on issues. ### AI Personas Each newsletter has one or more **Personas** — AI writing profiles that control voice, tone, and style. The default persona is used when drafting issues. Manage personas at: newsletter dashboard → **Settings → Personas** ### AI Drafting (via web UI) In the issue editor: 1. Click **Draft with AI** — triggers the AI pipeline to curate articles and generate issue content 2. The issue moves to `scheduled` status when the draft is complete 3. Review and edit the content, then click **Approve** to mark it ready to send ### AI Settings Configure at: newsletter dashboard → **Settings → AI Settings** Key settings: - `keywords` — Topics to monitor (e.g., "artificial intelligence, Claude, Cursor") - `audience_description` — Who reads your newsletter (affects tone and angle) - `layout_prompt` — Structural instructions for the AI (number of sections, article types, etc.) - `active_persona` — Which persona generates the content ### Content Blocks (Snippets) Reusable content blocks (intros, CTAs, sponsor slots) available in the editor. Manage at: newsletter → **Content → Content Blocks** --- ## Error Handling {#errors} All errors return JSON with an `error` key. | HTTP Status | Meaning | |------------|---------| | `400 Bad Request` | Invalid JSON or missing required fields | | `401 Unauthorized` | Missing or invalid API key | | `402 Payment Required` | Feature requires paid tier | | `403 Forbidden` | API key lacks required permission | | `404 Not Found` | Resource does not exist | | `422 Unprocessable Entity` | Validation failed (see `errors` array) | | `429 Too Many Requests` | Rate limit exceeded (see `X-RateLimit-Reset`) | | `500 Internal Server Error` | Unexpected server error | Example 422 response: ```json { "errors": ["Subject can't be blank", "Status is not included in the list"] } ``` --- ## End-to-End Workflows {#workflows} ### Workflow 1: Create and send a newsletter issue ```bash # 1. Create draft issue curl -s -X POST https://www.dailydraft.ai/api/v1/issues \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "issue": { "subject": "My Newsletter #1 — Hello World", "content_markdown": "## Welcome\n\nThis is my first issue..." } }' # → { "id": 101, "status": "draft", ... } # 2. Approve the issue curl -s -X PATCH https://www.dailydraft.ai/api/v1/issues/101 \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"issue": {"status": "approved"}}' # 3. Send it curl -s -X POST https://www.dailydraft.ai/api/v1/issues/101/send_now \ -H "Authorization: Bearer $API_KEY" # → { "message": "Newsletter queued for sending", "subscribers": 142 } ``` ### Workflow 2: Import subscribers ```bash curl -s -X POST https://www.dailydraft.ai/api/v1/subscribers/bulk_create \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "subscribers": [ {"email": "alice@example.com"}, {"email": "bob@example.com"} ] }' # → { "created": 2, "existing": 0, "errors": [] } ``` ### Workflow 3: Publish issue to web only (no email) ```bash # Create with content curl -s -X POST https://www.dailydraft.ai/api/v1/issues \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"issue": {"subject": "Archive Post", "content_markdown": "## Content..."}}' # → { "id": 102, ... } # Publish to web curl -s -X POST https://www.dailydraft.ai/api/v1/issues/102/publish \ -H "Authorization: Bearer $API_KEY" # → { "web_url": "https://mynewsletter.dailydraft.ai/issues/archive-post" } ``` ### Workflow 4: Set up custom domain 1. Go to newsletter dashboard → Settings → Custom Domains → Add Custom Domain 2. Enter `newsletter.yourdomain.com` 3. Copy the CNAME and TXT records shown 4. Add records at your DNS provider 5. Click Verify — DNS check runs automatically 6. Domain goes `active` once DNS propagates and SSL provisions --- ## Pagination All list endpoints support `page` (default: 1) and `per_page` (default: 25, max: 100) parameters. Response includes `meta.has_more` to indicate additional pages. --- ## Health Check ``` GET https://www.dailydraft.ai/up → { "status": "ok" } ``` --- ## Notes for AI Agents ### Account & Onboarding - Signup, email confirmation, newsletter creation, frequency setup, and API key generation are all **browser flows** — use Playwright/Puppeteer/Selenium - The REST API key is shown exactly once at creation — capture it from the page before navigating away - Email confirmation tokens expire — if expired, POST to `/email_confirmations/resend` to get a new one - Free-tier newsletters (`tier: "free"`) cannot send email — `send_now` returns `402` - Comped and Pro users get `tier: "paid"` newsletters automatically at creation ### REST API - Always check `newsletter.tier` before calling `send_now` — free tier returns `402` - Issue must be `approved` or `scheduled` before `send_now` — PATCH status first - `bulk_create` is idempotent — safe to re-run with the same email list - `scheduled_for` enables time-based delivery; omit to send immediately via `send_now` - Custom domains require web dashboard interaction (no API endpoint yet) — provide DNS record instructions and monitor status - AI drafting (draft_with_ai) is a web-only feature — inject AI-generated content via `content_markdown` in the API - Rate limit resets every hour; `X-RateLimit-Reset` is a Unix timestamp ### Discovery - This document: `https://www.dailydraft.ai/llms-full.txt` - Brief index: `https://www.dailydraft.ai/llms.txt` - AgentSkills skill: `https://www.dailydraft.ai/skill.md` - Well-known path: `https://www.dailydraft.ai/.well-known/skills/default/skill.md` - Agent docs: `https://www.dailydraft.ai/docs/api/ai-agents` - Health check: `https://www.dailydraft.ai/up` → `{"status":"ok"}`