# YYDS Mail API > Public integration summary for developers and AI assistants. > Base URL: https://api-c1.0m0.app/v1 ## Public Scope This llms.txt follows the same public integration surface shown in /docs: - Quick Start - Temporary Email - Messages - Webhooks - AI / LLM - Error Handling Additional public query endpoints: - GET /v1/domains - GET /v1/plans - GET /v1/pricing - GET /v1/domain-reward/config - GET /v1/stats ## Authentication - Bearer JWT: Authorization: Bearer - API Key: X-API-Key: AC-xxxxxx - Temp Token: Authorization: Bearer ## Notes - Temp tokens are only valid for temporary inbox flows such as /v1/accounts/me, /v1/accounts/{id}, /v1/messages*, and /v1/sources/{id} - Anonymous temp inbox creation is only available through the official YYDS Mail homepage bridge - Signed-in owner endpoints under /v1/me/domains*, /v1/me/wildcard-rules*, and /v1/me/dns-service-connections* require Bearer JWT; X-API-Key cannot manage those resources - Signed-in domain management uses those owner endpoints for DNS status refresh, DNS verification, wildcard rule creation/pause, DNS automation settings, and provider-backed DNS sync - DNS automation is optional. Without a service connection, the standard custom-domain flow remains manual TXT + MX setup - The first DNS automation connector is Cloudflare. Configure it from the signed-in domain management page, then create a token with Zone:Read + DNS:Edit and include the domains you want to manage in the token scope - Current live rate-limit summary before verified-domain bonus: VIP=25 req/s, Plus=35 req/s, Max=55 req/s, Neo=120 req/s - Burst multiplier: 3 ## Quick Start 1. Sign in at /login 2. Create an API key from the dashboard 3. Call the API with X-API-Key or Bearer JWT ## Temporary Email POST /v1/accounts — Create a temporary inbox POST /v1/accounts/wildcard — Force wildcard child-domain creation Rules for POST /v1/accounts: - Pass domain to create under a real domain - Pass subdomain together with domain to create under a wildcard child domain - localPart is the preferred request field; the legacy address field is still accepted - POST /v1/accounts keeps fixed-domain behavior when subdomain is omitted - POST /v1/accounts/wildcard forces wildcard mode and will use the key default or a random child domain when possible Example with normal domain (POST /v1/accounts): { "localPart": "my-prefix", "domain": "public.example.com" } Example with automatic child domain (POST /v1/accounts/wildcard, subdomain omitted; the server allocates a random child domain): Request: { "localPart": "my-prefix", "domain": "public.example.com" } Response data (excerpt): { "address": "my-prefix@a3f9c2.public.example.com", "mode": "wildcard", "domain": "a3f9c2.public.example.com", "subdomain": "a3f9c2" } Example with fixed child domain: { "localPart": "my-prefix", "domain": "public.example.com", "subdomain": "team-a" } POST /v1/token — Refresh the temp token for the same active inbox GET /v1/accounts/me — Get the current temp inbox profile GET /v1/inboxes/{id} — Get inbox detail by ID (canonical) GET /v1/accounts/{id} — DEPRECATED alias of GET /v1/inboxes/{id} (responses carry Deprecation + Link headers) DELETE /v1/accounts/{id} — Deactivate a temporary inbox Naming convergence: /v1/inboxes/{id} is the canonical inbox resource. The legacy aliases (GET /v1/accounts/{id}; POST /v1/inboxes, /v1/mailboxes, /v1/emails as creation aliases of POST /v1/accounts) keep working for a 12-month transition window but are deprecated — prefer the canonical paths in new integrations. ## Messages GET /v1/inboxes/{id}/messages — List messages by inbox ID (canonical; same filters/response as below) GET /v1/messages?address=xxx — List messages for an inbox (address-keyed) GET /v1/messages/next — Take the next unread message (see below) POST /v1/messages/mark-read — Mark the current mailbox as read GET /v1/messages/{id}?address=xxx — Get full message detail PATCH /v1/messages/{id}?address=xxx — Update message state (seen true/false, starred) DELETE /v1/messages/{id}?address=xxx — Delete a message GET /v1/sources/{id}?address=xxx — Get raw message source wrapped in JSON Verification codes (server-extracted): - Message detail responses include "verificationCode" when the server detects an OTP-style code in the mail (multi-language keywords, 4-8 char digit/alphanumeric, anti-false-positive scoring). Absent field = no code found. - List responses include the field only for recent unread messages that look like verification mail (bounded probing). GET /v1/messages/next — the one-call verification-code loop: - Returns the OLDEST unread message in scope and atomically marks it read. Response data: { "message": , "inboxAddress": "..." }. - Query: address= (optional; restrict to one owned inbox — omit to take from all your inboxes), wait=<0-30> (optional; long-poll seconds — the server holds the request until a message arrives or the window expires). - 204 No Content when no unread message exists (after the wait window). - Auth: temp token (its own inbox), API key with write permission, or Bearer JWT. Concurrent callers never receive the same message. - Typical loop: trigger the email → GET /v1/messages/next?address=me@example.com&wait=30 → read data.message.verificationCode. One call, no list/mark-read bookkeeping. GET /v1/messages optional filters (response gains nextCursor when any is used): - seen=true|false — read-state filter (seen=false = only unread) - since= — only messages at/after this timestamp (inclusive) - q= — case-insensitive substring match on subject and from - after_id= — cursor: return messages strictly after this id (newest-first order); supersedes offset - nextCursor in the response data is the last returned id when more matches remain, empty string otherwise; requests without these params keep the legacy response shape unchanged PATCH /v1/messages/{id} body: - {"seen": true} or empty body — mark read (legacy behaviour) - {"seen": false} — mark unread - {"starred": true|false} — toggle the per-user star; starred-only PATCH leaves seen untouched ## API Key Lifecycle (Bearer JWT only) PATCH /v1/me/api-keys/{id} — Update name / domainScope(+allowedDomainIds) / expiresAt (null clears) / enabled POST /v1/me/api-keys/{id}/rotate — Generate a new secret (shown once); the old key keeps working until graceUntil (default 24h), then stops permanently. Key id and usage history are preserved. ## Webhooks GET /v1/me/webhooks — List webhook subscriptions POST /v1/me/webhooks — Create a webhook (server generates a signing secret; shown once in the response) PATCH /v1/me/webhooks/{id} — Update a webhook DELETE /v1/me/webhooks/{id} — Delete a webhook POST /v1/me/webhooks/{id}/test — Send a signed test event (recorded in the delivery log) POST /v1/me/webhooks/{id}/rotate-secret — Rotate the webhook signing secret (old secret invalidated; new one shown once) GET /v1/me/webhooks/{id}/deliveries — List recent deliveries with per-attempt status / retry schedule Webhook delivery contract: - Event: message.received fires when new mail arrives in any of your inboxes (source="local") or in a linked external IMAP account (source="external", with accountId + accountEmail). Payload contains headers only (event, source, messageId, mailbox, from, to, subject, date, size, hasAttachments) — fetch the body via GET /v1/messages/{id}?address=. - Event: messages.bulk_received is the anti-storm aggregate for external accounts: when one sync round ingests more than 50 new messages, per-message events are replaced by a single aggregate { event, source:"external", mailbox, count, accountId, accountEmail }. It is delivered to subscribers of either messages.bulk_received or message.received. - Signature: X-YYDS-Signature: sha256=HEX(HMAC-SHA256(secret, ".")). Also sent: X-YYDS-Event, X-YYDS-Delivery (delivery id), X-YYDS-Timestamp (unix seconds; reject stale timestamps to prevent replay). - Delivery: HTTP POST, 10s timeout, redirects not followed. Respond 2xx to acknowledge. - Retries: exponential backoff 1m / 5m / 30m / 2h / 6h (6 attempts total). 20 consecutive failures auto-disable the webhook. ## AI / LLM GET /v1/llms.txt — This AI-friendly public API summary ## Rate Limiting Every rate-limited response (success and 429) carries: - X-RateLimit-Limit: bucket capacity (burst) for your tier - X-RateLimit-Remaining: requests left in the current bucket - X-RateLimit-Reset: unix seconds when the bucket is fully refilled Buckets are token buckets: capacity = rps × burst multiplier (see above); tokens refill continuously at your tier's req/s. Daily/weekly/monthly API-call quotas are enforced separately for API keys and return 429 with Retry-After: 3600 when exhausted. ## Machine-Readable Contract (OpenAPI) GET /v1/openapi.yaml — Full OpenAPI 3.1 specification (single source of truth; importable into Postman / Swagger UI) GET /v1/openapi.json — Same specification as JSON Spec URL: https://api-c1.0m0.app/v1/openapi.yaml ## Error Handling All errors follow the same envelope: { "success": false, "error": "...", "errorCode": "..." } Prefer errorCode over localized error text in stable integrations. GET /v1/error-codes — Full machine-readable errorCode catalog (code -> en/zh message) 429 responses include Retry-After. ## Intentionally Excluded Admin, balance, domain-governance, and other signed-in console routes are intentionally excluded from this public llms surface. Signed-in domain onboarding, wildcard rule management, and DNS automation stay inside the dashboard rather than the public integration contract.