Appearance
Email providers
Generated from
DialerDigital/corecommit0fc18008b3c8— specopenapi/dialer-v1.yamlv0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.
Per-tenant/region email provider registry (SMTP / log).
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/email/providers | Email provider registry |
POST | /v1/email/providers | Register an email provider |
GET | /v1/email/providers/{id} | Fetch one email provider |
PATCH | /v1/email/providers/{id} | Update an email provider (scope immutable) |
POST | /v1/email/providers/{id}/retire | Retire an email provider (rows never delete) |
Email provider registry
http
GET /v1/email/providersResponses
200
Own rows + inherited empresa-wide rows.
| Field | Type | Required | Description |
|---|---|---|---|
email_providers | array of EmailProvider | yes | |
email_providers[].id | string (uuid) | no | |
email_providers[].account_id | string (uuid) | no | |
email_providers[].tenant_id | string (uuid) | null | no | null = empresa-wide provider inherited by every sede. |
email_providers[].scope | empresa · sede | no | Derived from tenant_id. |
email_providers[].adapter | smtp · log | no | |
email_providers[].region | us · mx · eu | no | |
email_providers[].name | string | no | |
email_providers[].from_address | string | no | |
email_providers[].host | string | no | |
email_providers[].port | integer | no | |
email_providers[].username | string | no | |
email_providers[].has_secret | boolean | no | The SMTP secret NEVER rides the wire — this is the only signal a credential is stored. |
email_providers[].has_webhook_secret | boolean | no | The event-signing key NEVER rides the wire — this is the only signal one is stored. |
email_providers[].status | active · disabled · retired | no | |
email_providers[].created_at | string (date-time) | no | |
email_providers[].updated_at | string (date-time) | no |
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Register an email provider
http
POST /v1/email/providersSame conventions as /v1/sms/providers: scope: "empresa" creates an account-wide row and needs an account_admin (a member or a machine key gets 403); account_id/tenant_id are injected from the session. smtp needs host (a DNS name or IPv4 literal that passes the SSRF guard, re-checked at every send), port 465 or 587, username and secret. secret is WRITE-ONLY: responses carry has_secret/has_webhook_secret, never the secrets. The log adapter is refused unless the deployment allows it. There is no fallback provider: a tenant with no active row does not send. Retire-only — no DELETE route exists.
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
adapter | smtp · log | yes | |
region | us · mx · eu | yes | |
name | string | yes | |
from_address | string (email) | yes | |
host | string | no | |
port | 465 · 587 | no | |
username | string | no | |
secret | string | no | Write-only. SMTP password. |
webhook_secret | string | no | Write-only. HMAC key of the provider's signed events; responses carry has_webhook_secret. |
status | active · disabled · retired | no | |
scope | sede · empresa | no | Request-only field; responses derive it from tenant_id. |
json
{
"adapter": "smtp",
"region": "us",
"name": "example-email-provider",
"from_address": "[email protected]",
"host": "smtp.example.com",
"port": 587,
"username": "example-user",
"secret": "example-smtp-password-not-a-real-one"
}Responses
201
Registered provider (has_secret, never the secret).
| Field | Type | Required | Description |
|---|---|---|---|
email_provider | object (EmailProvider) | yes | |
email_provider.id | string (uuid) | no | |
email_provider.account_id | string (uuid) | no | |
email_provider.tenant_id | string (uuid) | null | no | null = empresa-wide provider inherited by every sede. |
email_provider.scope | empresa · sede | no | Derived from tenant_id. |
email_provider.adapter | smtp · log | no | |
email_provider.region | us · mx · eu | no | |
email_provider.name | string | no | |
email_provider.from_address | string | no | |
email_provider.host | string | no | |
email_provider.port | integer | no | |
email_provider.username | string | no | |
email_provider.has_secret | boolean | no | The SMTP secret NEVER rides the wire — this is the only signal a credential is stored. |
email_provider.has_webhook_secret | boolean | no | The event-signing key NEVER rides the wire — this is the only signal one is stored. |
email_provider.status | active · disabled · retired | no | |
email_provider.created_at | string (date-time) | no | |
email_provider.updated_at | string (date-time) | no |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Fetch one email provider
http
GET /v1/email/providers/{id}Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The provider.
| Field | Type | Required | Description |
|---|---|---|---|
email_provider | object (EmailProvider) | yes | |
email_provider.id | string (uuid) | no | |
email_provider.account_id | string (uuid) | no | |
email_provider.tenant_id | string (uuid) | null | no | null = empresa-wide provider inherited by every sede. |
email_provider.scope | empresa · sede | no | Derived from tenant_id. |
email_provider.adapter | smtp · log | no | |
email_provider.region | us · mx · eu | no | |
email_provider.name | string | no | |
email_provider.from_address | string | no | |
email_provider.host | string | no | |
email_provider.port | integer | no | |
email_provider.username | string | no | |
email_provider.has_secret | boolean | no | The SMTP secret NEVER rides the wire — this is the only signal a credential is stored. |
email_provider.has_webhook_secret | boolean | no | The event-signing key NEVER rides the wire — this is the only signal one is stored. |
email_provider.status | active · disabled · retired | no | |
email_provider.created_at | string (date-time) | no | |
email_provider.updated_at | string (date-time) | no |
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Update an email provider (scope immutable)
http
PATCH /v1/email/providers/{id}Editing an empresa-wide row takes an account_admin session, as creating it does. A retired row is terminal. scope is not accepted here: it is 400.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
adapter | smtp · log | no | |
region | us · mx · eu | no | |
name | string | no | |
from_address | string (email) | no | |
host | string | no | |
port | 465 · 587 | no | |
username | string | no | |
secret | string | no | Write-only. |
webhook_secret | string,null | no | Write-only. A new value rotates the key; null or an empty string clears it, so the row stops accepting events. |
status | active · disabled · retired | no |
json
{
"name": "postman-email-renamed",
"status": "disabled"
}Responses
200
Updated provider.
| Field | Type | Required | Description |
|---|---|---|---|
email_provider | object (EmailProvider) | yes | |
email_provider.id | string (uuid) | no | |
email_provider.account_id | string (uuid) | no | |
email_provider.tenant_id | string (uuid) | null | no | null = empresa-wide provider inherited by every sede. |
email_provider.scope | empresa · sede | no | Derived from tenant_id. |
email_provider.adapter | smtp · log | no | |
email_provider.region | us · mx · eu | no | |
email_provider.name | string | no | |
email_provider.from_address | string | no | |
email_provider.host | string | no | |
email_provider.port | integer | no | |
email_provider.username | string | no | |
email_provider.has_secret | boolean | no | The SMTP secret NEVER rides the wire — this is the only signal a credential is stored. |
email_provider.has_webhook_secret | boolean | no | The event-signing key NEVER rides the wire — this is the only signal one is stored. |
email_provider.status | active · disabled · retired | no | |
email_provider.created_at | string (date-time) | no | |
email_provider.updated_at | string (date-time) | no |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Retire an email provider (rows never delete)
http
POST /v1/email/providers/{id}/retireRetiring an empresa-wide row takes an account_admin session.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The retired provider.
| Field | Type | Required | Description |
|---|---|---|---|
email_provider | object (EmailProvider) | yes | |
email_provider.id | string (uuid) | no | |
email_provider.account_id | string (uuid) | no | |
email_provider.tenant_id | string (uuid) | null | no | null = empresa-wide provider inherited by every sede. |
email_provider.scope | empresa · sede | no | Derived from tenant_id. |
email_provider.adapter | smtp · log | no | |
email_provider.region | us · mx · eu | no | |
email_provider.name | string | no | |
email_provider.from_address | string | no | |
email_provider.host | string | no | |
email_provider.port | integer | no | |
email_provider.username | string | no | |
email_provider.has_secret | boolean | no | The SMTP secret NEVER rides the wire — this is the only signal a credential is stored. |
email_provider.has_webhook_secret | boolean | no | The event-signing key NEVER rides the wire — this is the only signal one is stored. |
email_provider.status | active · disabled · retired | no | |
email_provider.created_at | string (date-time) | no | |
email_provider.updated_at | string (date-time) | no |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}403 — Forbidden
The tenant is suspended. A dashboard session (ddu_) of a tenant whose login policy (ADR 227) is in mode enforce answers 403 with the code login_policy_denied on a request from an address or at an hour the policy does not allow — the same body for both; the session is not revoked and a later request inside the policy goes through. A widget token (ddw_) of a seat is held to the same policy on every request (core#2406, flag widget_login_honors_policy). A synthetic (demo) tenant answers 403 with the code synthetic_tenant on every route that would reach a third party — manual dial, external transfer or conference leg, SMS send (core#2251, ADR #280 addendum). No payment changes it.
json
{
"error": {
"code": "forbidden",
"message": "tenant is suspended"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}