+ description.set("The Sent DM API enables programmatic SMS and WhatsApp messaging with contact\nmanagement, template-based messaging, compliance (10DLC brand/campaign\nregistration), and webhook delivery tracking.\n\n## Authentication\n\nAll requests require the `x-api-key` header with your API key (e.g.\n`sk_live_...` for production, `sk_test_...` for sandbox). Your account is\nidentified automatically from the key.\n\n## Profile Scoping\n\nOrganization API keys can act on behalf of a child profile by passing the\noptional `x-profile-id` header with the profile's UUID. When present, all\noperations are scoped to that profile — contacts, messages, templates, and other\nresources are read and written as if the profile's own API key were used.\n\n- Only **organization** API keys may use this header. Profile API keys will\n receive `403`.\n- The profile must belong to the calling organization, otherwise `404` is\n returned.\n- When profile-scoped, the `X-Profile-Id` response header echoes the profile\n UUID.\n- Rate limits remain on the organization's pool regardless of profile scoping.\n\n## Idempotency\n\nPOST, PUT, and PATCH requests accept an optional `Idempotency-Key` header. When\nprovided, the API guarantees at-most-once execution: duplicate requests with the\nsame key return the original response. Keys are scoped per customer and expire\nafter 24 hours.\n\n## Sandbox Mode\n\nAll mutation endpoints (POST, PUT, DELETE) accept an optional `sandbox` field in\nthe request body. When set to `true`, the API validates the request and returns\na realistic fake response **without executing any side effects** — no database\nwrites, no messages sent, no external API calls.\n\nSandbox mode is useful for:\n\n- **Integration testing** — verify your request payloads and response parsing\n without affecting real data\n- **CI/CD pipelines** — run end-to-end tests against the live API without\n consuming resources\n- **Client development** — build and test your integration before going live\n\n```json\n{\n \"sandbox\": true,\n \"phone_number\": \"+1234567890\"\n}\n```\n\nSandbox mode responses include the `X-Sandbox: true` header and return the same\nschema as real responses with sample data. Authentication and validation still\nrun normally — only the service-layer execution is skipped.\n\n## Rate Limiting\n\nAPI requests are rate-limited per customer. Standard endpoints allow **200\nrequests/minute**; sensitive endpoints (e.g. secret rotation) allow **10\nrequests/minute**. When limits are exceeded, the API returns\n`429 Too Many Requests`.\n\nRate limit headers on `429` responses: | Header | Description |\n|--------|-------------| | `Retry-After` | Seconds until you can retry | |\n`X-RateLimit-Limit` | Maximum requests allowed in the window | |\n`X-RateLimit-Remaining` | Requests remaining (always `0` on 429) | |\n`X-RateLimit-Reset` | Unix timestamp when the window resets |\n\n## Common Response Headers\n\nAll responses include these headers: | Header | Description |\n|--------|-------------| | `X-Request-Id` | Unique request identifier for\nsupport and debugging | | `X-Response-Time` | Server processing time (e.g.\n`12ms`) | | `X-API-Version` | API version (always `v3`) | | `X-Profile-Id` |\nEchoed profile UUID when the request is profile-scoped via `x-profile-id` |\n\nIdempotent replay responses also include: | Header | Description |\n|--------|-------------| | `Idempotent-Replayed` | `true` if this is a cached\nreplay | | `X-Original-Request-Id` | Request ID of the original request |\n\n## Errors\n\nAll errors follow a consistent JSON envelope:\n\n```json\n{\n \"success\": false,\n \"error\": {\n \"code\": \"RESOURCE_001\",\n \"message\": \"Contact not found\",\n \"doc_url\": \"https://docs.sent.dm/errors/RESOURCE_001\"\n },\n \"meta\": { \"request_id\": \"req_...\", \"timestamp\": \"...\", \"version\": \"v3\" }\n}\n```\n\n### Error Code Reference\n\n| Code | Description |\n| ------------------ | ---------------------------------------------- |\n| **Authentication** | |\n| `AUTH_001` | User is not authenticated |\n| `AUTH_002` | Invalid or expired API key |\n| `AUTH_004` | Insufficient permissions for this operation |\n| **Validation** | |\n| `VALIDATION_001` | Request validation failed |\n| `VALIDATION_002` | Invalid phone number format |\n| `VALIDATION_003` | Invalid GUID format |\n| `VALIDATION_004` | Required field is missing |\n| `VALIDATION_005` | Field value out of valid range |\n| `VALIDATION_006` | Invalid enum value |\n| `VALIDATION_007` | Invalid Idempotency-Key format |\n| **Resource** | |\n| `RESOURCE_001` | Contact not found |\n| `RESOURCE_002` | Template not found |\n| `RESOURCE_003` | Message not found |\n| `RESOURCE_004` | Customer not found |\n| `RESOURCE_005` | Organization not found |\n| `RESOURCE_006` | User not found |\n| `RESOURCE_007` | Resource already exists (duplicate) |\n| `RESOURCE_008` | Webhook not found |\n| **Business Logic** | |\n| `BUSINESS_001` | Cannot modify inherited contact |\n| `BUSINESS_002` | Rate limit exceeded |\n| `BUSINESS_003` | Insufficient account balance |\n| `BUSINESS_004` | Contact has opted out of messaging |\n| `BUSINESS_005` | Template not approved for sending |\n| `BUSINESS_006` | Message cannot be modified in current state |\n| `BUSINESS_007` | Channel not available for this contact |\n| `BUSINESS_008` | Operation would exceed quota |\n| **Conflict** | |\n| `CONFLICT_001` | Concurrent idempotent request in progress |\n| **Service** | |\n| `SERVICE_001` | Cache service temporarily unavailable |\n| **Internal** | |\n| `INTERNAL_001` | Unexpected internal server error |\n| `INTERNAL_002` | Database operation failed |\n| `INTERNAL_003` | External service error (SMS/WhatsApp provider) |\n| `INTERNAL_004` | Timeout waiting for operation |\n| `INTERNAL_005` | Service temporarily unavailable |")
0 commit comments