agent

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 4, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// MaxLabelLength bounds a single label's length to keep the GIN
	// index entries small and prevent multi-KB tags from being smuggled
	// into the array.
	MaxLabelLength = 64

	// MaxLabelsPerOp caps the per-request add/remove list size.
	// Modeled on Gmail's 100/100 cap. Bigger than AgentMail's
	// (unspecified) but defensive enough that one PATCH can't try
	// to set thousands of labels.
	MaxLabelsPerOp = 50

	// LabelSystemPrefix marks server-applied system labels. User
	// writes that try to set a label starting with this prefix are
	// rejected with 400 — the namespace is reserved so future system
	// labels (auto-tagged, hitl-approved, …) don't collide with user
	// tags.
	LabelSystemPrefix = "e2a:"
)

Label validation constants. Public so tests can reference them.

Variables

This section is empty.

Functions

func ValidateWebhookURL

func ValidateWebhookURL(rawURL string) error

ValidateWebhookImageURL — see ValidateWebhookURL.

ValidateWebhookURL checks that a webhook URL is safe to call (SSRF protection).

Types

type API

type API struct {
	// contains filtered or unexported fields
}

func NewAPI

func NewAPI(store *identity.Store, sender *outbound.Sender, smtpRelay *outbound.SMTPRelay, userAuth *auth.UserAuth, usage usage.UsageTracker, smtpDomain, fromDomain, sharedDomain, publicURL string, production bool) *API

func (*API) RegisterRoutes

func (a *API) RegisterRoutes(r *mux.Router)

func (*API) RegisterWSRoute

func (a *API) RegisterWSRoute(r *mux.Router, handle http.HandlerFunc)

RegisterWSRoute registers the WebSocket endpoint for local-mode agents.

func (*API) SetApprovalSigner

func (a *API) SetApprovalSigner(s *approvaltoken.Signer)

SetApprovalSigner wires in the magic-link signer after construction so callers (and tests) that don't need HITL magic-link endpoints don't have to know about it. When nil, handleApproveMagicLink / handleRejectMagicLink respond with 404.

func (*API) SetBillingHookURL added in v0.3.0

func (a *API) SetBillingHookURL(s string)

SetBillingHookURL wires in the URL of an external billing service's user-event endpoint. When the user deletes their account, the API HMAC-signs a JSON payload and POSTs it there so the billing service can cancel the corresponding Stripe subscription. When empty (the self-host default), no hook fires — appropriate for deployments without a billing service. The same internal_api_secret is reused for the signature.

func (*API) SetEnforcer added in v0.3.0

func (a *API) SetEnforcer(e limits.Enforcer)

SetEnforcer wires in the resource-limits enforcer. When nil (the default) every check passes — handlers behave as if every user has unlimited capacity. The cmd/e2a runtime always sets it; tests that don't care about limits omit it and continue to work as before.

func (*API) SetIdempotencyStore added in v0.3.0

func (a *API) SetIdempotencyStore(s *idempotency.Store)

SetIdempotencyStore enables Idempotency-Key processing on the outbound /send and /reply endpoints. When nil (the default) the header is silently ignored — keeps the agent package usable in environments that don't have postgres wired or want to disable the feature. The cmd/e2a runtime always sets it.

func (*API) SetInternalAPISecret added in v0.3.0

func (a *API) SetInternalAPISecret(s string)

SetInternalAPISecret wires in the shared HMAC secret used to authenticate the /api/internal/limits/invalidate endpoint. When empty (default), that endpoint returns 503 — self-host operators who don't run a billing provisioner never need to configure it.

func (*API) SetMetrics added in v0.3.0

func (a *API) SetMetrics(m telemetry.Metrics)

SetMetrics wires the slice 10 observability backend. Default is telemetry.NoOp; production passes telemetry.NewLog() or a real counter backend.

func (*API) SetNotifier

func (a *API) SetNotifier(n *hitlnotify.Notifier)

SetNotifier wires in the HITL notifier. When nil, holdForApproval still persists the pending message but doesn't fire the email — useful for tests that don't want the async SMTP traffic.

func (*API) SetOAuthProvider added in v0.3.0

func (a *API) SetOAuthProvider(p fosite.OAuth2Provider)

SetOAuthProvider wires in the fosite-backed OAuth provider. When nil, /api/oauth/* endpoints return 404 (matches the SetApprovalSigner / SetNotifier pattern of "optional capability, silently absent when not wired"). Operators who don't want OAuth enabled simply don't call this.

func (*API) SetOAuthStorage added in v0.3.0

func (a *API) SetOAuthStorage(s *oauth.Storage)

SetOAuthStorage wires in the OAuth storage handle separately from the provider. The consent handler needs Pool() to begin a pgx tx that spans the agent-create (identity pkg) and the auth-code insert (fosite → oauth pkg). Provider-only callers (e.g. /token) don't need it, but it's required for /consent to work; setting one without the other is a misconfiguration the consent handler surfaces as a 503.

func (*API) SetOutbox added in v0.3.0

func (a *API) SetOutbox(o webhookpub.Outbox)

SetOutbox wires the slice-4 transactional outbox. Used by the outbound /send handler for the email.sent event (post-side-effect: PublishBestEffortTx). Other trigger sites (HITL) continue to use the legacy publisher; they will migrate in a future slice if/when their handlers gain transactional plumbing.

func (*API) SetPoolForEvents added in v0.3.0

func (a *API) SetPoolForEvents(p *pgxpool.Pool)

SetPoolForEvents wires the raw pgxpool.Pool used by the events handlers. Kept separate from the store so a future refactor can route through a higher-level abstraction without changing the handler signatures.

func (*API) SetPublisher added in v0.3.0

func (a *API) SetPublisher(p webhookpub.Publisher)

SetPublisher wires the LEGACY in-process fan-out publisher. Kept during the slice-4→slice-11 rollout window. Safe to leave nil in tests; trigger sites no-op when it is.

func (*API) SetSubscriberStore added in v0.3.0

func (a *API) SetSubscriberStore(s *webhook.SubscriberStore)

SetSubscriberStore wires the subscriber-store dependency after NewAPI. Same optional-setter convention as SetEnforcer / etc.

func (*API) SetUsageStore added in v0.3.0

func (a *API) SetUsageStore(s *usage.Store)

SetUsageStore wires in the usage store used by handleGetMyLimits to surface the user's current counts (agents, domains, messages this month, storage bytes) alongside the resolved caps. Separate from the usage.UsageTracker (which is for recording events) so the dashboard read path can stay alive even when usage-tracking is otherwise off.

type AgentInfo

type AgentInfo struct {
	ID                   string    `json:"id" example:"ag_abc123"`
	Domain               string    `json:"domain" example:"agents.example.com"`
	Email                string    `json:"email" example:"my-bot@example.com"`
	Name                 string    `json:"name" example:"My Bot"`
	WebhookURL           string    `json:"webhook_url"`
	AgentMode            string    `json:"agent_mode" example:"cloud" enums:"cloud,local"`
	DomainVerified       bool      `json:"domain_verified"`
	CreatedAt            time.Time `json:"created_at"`
	HITLEnabled          bool      `json:"hitl_enabled"`
	HITLTTLSeconds       int       `json:"hitl_ttl_seconds" example:"604800"`
	HITLExpirationAction string    `json:"hitl_expiration_action" example:"reject" enums:"approve,reject"`

} // @name Agent

AgentInfo is the public API representation of an agent.

type ApprovePendingMessageRequest

type ApprovePendingMessageRequest struct {
	Subject     *string      `json:"subject,omitempty"`
	BodyText    *string      `json:"body_text,omitempty"`
	BodyHTML    *string      `json:"body_html,omitempty"`
	To          []string     `json:"to,omitempty"`
	CC          []string     `json:"cc,omitempty"`
	BCC         []string     `json:"bcc,omitempty"`
	Attachments []Attachment `json:"attachments,omitempty"`

} // @name ApprovePendingMessageRequest

ApprovePendingMessageRequest is the optional body for POST /api/v1/messages/{id}/approve. Any field present overrides the stored value before the message is sent; missing fields are left as the original draft. An empty body means approve-as-is.

type ApprovePendingMessageResponse

type ApprovePendingMessageResponse struct {
	Status            string `json:"status" example:"sent"`
	MessageID         string `json:"message_id"`
	ProviderMessageID string `json:"provider_message_id,omitempty"`
	Method            string `json:"method,omitempty" example:"smtp"`
	Edited            bool   `json:"edited,omitempty"`

} // @name ApprovePendingMessageResponse

ApprovePendingMessageResponse is the response when the reviewer approves a held message and the server successfully hands it to the upstream SMTP relay.

type Attachment

type Attachment = outbound.Attachment

Attachment is a base64-encoded file attachment.

type AuthHeaders

type AuthHeaders struct {
	Verified    string `json:"X-E2A-Auth-Verified" example:"true"`
	Sender      string `json:"X-E2A-Auth-Sender" example:"alice@example.com"`
	EntityType  string `json:"X-E2A-Auth-Entity-Type" example:"human" enums:"human,agent"`
	DomainCheck string `json:"X-E2A-Auth-Domain-Check" example:"spf=pass; dkim=none"`
	Delegation  string `json:"X-E2A-Auth-Delegation,omitempty" example:"agent=ag_abc123;human=usr_xyz789"`
	Signature   string `json:"X-E2A-Auth-Signature" example:"sha256=..."`
	Timestamp   string `json:"X-E2A-Auth-Timestamp" example:"2025-01-15T10:30:00Z"`

} // @name AuthHeaders

AuthHeaders documents the signed authentication headers included in webhook deliveries. The server signs Verified, Sender, EntityType, DomainCheck, Delegation, and Timestamp into a canonical string and produces an HMAC-SHA256 Signature. SDKs can verify the signature to confirm the payload was not tampered with.

type ConversationDetail added in v0.3.0

type ConversationDetail struct {
	ConversationSummary
	Participants []string         `json:"participants"`
	Labels       []string         `json:"labels"`
	Messages     []MessageSummary `json:"messages"`

} // @name ConversationDetail

ConversationDetail extends the summary with computed aggregates (participants union, label union) and the member messages, ordered chronologically (oldest first).

type ConversationSummary added in v0.3.0

type ConversationSummary struct {
	ConversationID string `json:"conversation_id" example:"conv_abc123"`
	LastMessageAt  string `json:"last_message_at" example:"2026-05-28T12:00:00Z"`
	FirstMessageAt string `json:"first_message_at" example:"2026-05-20T10:00:00Z"`
	MessageCount   int    `json:"message_count" example:"4"`
	InboundCount   int    `json:"inbound_count" example:"2"`
	OutboundCount  int    `json:"outbound_count" example:"2"`
	// HasUnread is true iff at least one INBOUND member of this
	// conversation is in inbox_status='unread'. Outbound rows do
	// NOT contribute — a thread containing only your sent messages
	// (or only HITL-pending outbound) returns false. This is the
	// agent's mailbox view, not the reviewer's HITL queue.
	HasUnread     bool   `json:"has_unread" example:"true"`
	LatestSubject string `json:"latest_subject" example:"Re: Quarterly report"`
	LatestSender  string `json:"latest_sender" example:"alice@example.com"`

} // @name ConversationSummary

ConversationSummary is one row in the conversations list. Aggregated counts + the "latest" preview fields render an inbox-style conversation list without a per-row drill-down.

type CreateSigningSecretRequest added in v0.3.0

type CreateSigningSecretRequest struct {
	// Optional human-readable label so users can tell secrets apart in
	// the dashboard (e.g. "prod", "staging", "rollover-2026-04").
	Name string `json:"name"`
}

CreateSigningSecretRequest is the body of POST /api/v1/users/me/signing-secrets.

type CreateSigningSecretResponse added in v0.3.0

type CreateSigningSecretResponse struct {
	ID           string `json:"id"`
	Name         string `json:"name"`
	Secret       string `json:"secret"`
	SecretPrefix string `json:"secret_prefix"`
	CreatedAt    string `json:"created_at"`

} // @name CreateSigningSecretResponse

CreateSigningSecretResponse is returned exactly once at creation. The plaintext Secret is the only chance the caller has to capture the value — subsequent reads only see SecretPrefix.

type CreateWebhookRequest added in v0.3.0

type CreateWebhookRequest struct {
	URL         string          `json:"url" example:"https://example.com/e2a/hook"`
	Events      []string        `json:"events" example:"email.received"`
	Filters     *WebhookFilters `json:"filters,omitempty"`
	Description string          `json:"description,omitempty" example:"main inbox handler"`

} // @name CreateWebhookRequest

CreateWebhookRequest is the POST /api/v1/webhooks body.

type DNSRecord

type DNSRecord struct {
	Host     string `json:"host" example:"@"`
	Value    string `json:"value" example:"mx.example.com"`
	Priority *int   `json:"priority,omitempty" example:"10"`

} // @name DNSRecord

DNSRecord is a single DNS record entry.

type DNSRecords

type DNSRecords struct {
	MX   DNSRecord `json:"mx"`
	TXT  DNSRecord `json:"txt"`
	DKIM DNSRecord `json:"dkim,omitempty"`

} // @name DNSRecords

DNSRecords contains the DNS records needed for domain verification. DKIM is populated for domains created after migration 014 (per-domain keypairs). Pre-migration rows leave DKIM at the zero value — clients can detect by checking Host == "".

type DeleteUserDataResult

type DeleteUserDataResult = identity.DeleteUserDataResult // @name DeleteUserDataResult

type DeliveryStatus added in v0.3.0

type DeliveryStatus struct {
	MatchedWebhooks int `json:"matched_webhooks"`
	Delivered       int `json:"delivered"`
	Pending         int `json:"pending"`
	Failed          int `json:"failed"`

} // @name DeliveryStatus

DeliveryStatus summarizes how many of the matched webhooks have received an event so far. Computed at read time by joining against webhook_subscriber_deliveries.

type DeploymentInfo

type DeploymentInfo struct {
	// SharedDomain is the mail domain backing slug-based agent registration
	// on this deployment (e.g. "agents.example.com"). Empty when the
	// operator hasn't configured one — in that case slug registration is
	// disabled and every agent must use a custom domain.
	SharedDomain string `json:"shared_domain" example:"agents.example.com"`

	// SlugRegistrationEnabled mirrors `shared_domain != ""` for clients
	// that prefer a boolean. Equivalent to checking SharedDomain directly.
	SlugRegistrationEnabled bool `json:"slug_registration_enabled" example:"true"`

	// PublicURL is the externally visible base URL of the API itself —
	// the same value the operator sets in `http.public_url`. Empty when
	// not configured.
	PublicURL string `json:"public_url,omitempty" example:"https://e2a.example.com"`

} // @name DeploymentInfo

DeploymentInfo is the response shape of GET /api/v1/info. It's how CLI/SDK clients discover deployment-specific values (shared domain, public URL) without each user having to configure them by hand.

type DomainInfo

type DomainInfo struct {
	Domain            string     `json:"domain" example:"yourdomain.com"`
	Verified          bool       `json:"verified"`
	VerificationToken string     `json:"verification_token" example:"e2a-verify=abc123"`
	DNSRecords        DNSRecords `json:"dns_records"`
	CreatedAt         time.Time  `json:"created_at"`
	VerifiedAt        *time.Time `json:"verified_at,omitempty"`
	// IsPrimary marks the user's default domain — at most one per
	// user, enforced server-side via SetDomainPrimary. The redesign's
	// Domains list renders this as a "Primary" chip.
	IsPrimary bool `json:"is_primary"`
	// LastCheckedAt is the timestamp of the most recent
	// /api/v1/domains/{domain}/verify probe (success or failure).
	// Distinct from VerifiedAt, which only updates on success.
	LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
	// AgentCount is populated by list endpoints. Single-domain endpoints
	// (register, verify) leave it zero — the count would require an
	// extra query that callers can derive from the agents list anyway.
	AgentCount int `json:"agent_count"`

} // @name Domain

DomainInfo is the public API representation of a domain.

type ForwardMessageRequest added in v0.3.0

type ForwardMessageRequest struct {
	To             []string     `json:"to" example:"alice@example.com"`
	CC             []string     `json:"cc,omitempty" example:"bob@example.com"`
	BCC            []string     `json:"bcc,omitempty" example:"carol@example.com"`
	Body           string       `json:"body,omitempty" example:"FYI — see below"`
	HTMLBody       string       `json:"html_body,omitempty" example:"<p>FYI — see below</p>"`
	ConversationID string       `json:"conversation_id,omitempty"`
	Attachments    []Attachment `json:"attachments,omitempty"`

} // @name ForwardMessageRequest

ForwardMessageRequest is the request body for forwarding a message. Body and html_body are the caller's optional comment to prepend; the server appends a quoted block with the original headers and body. A forward is treated as a new thread (no In-Reply-To/References) — pass conversation_id to bind it to an existing thread explicitly.

type ForwardRequest added in v0.3.0

type ForwardRequest struct {
	To             []string              `json:"to"`
	CC             []string              `json:"cc,omitempty"`
	BCC            []string              `json:"bcc,omitempty"`
	Body           string                `json:"body,omitempty"`
	HTMLBody       string                `json:"html_body,omitempty"`
	ConversationID string                `json:"conversation_id,omitempty"`
	Attachments    []outbound.Attachment `json:"attachments,omitempty"`
}

ForwardRequest is the JSON body for /api/v1/agents/{email}/messages/{id}/forward.

type LimitsCaps added in v0.3.0

type LimitsCaps struct {
	MaxAgents        int   `json:"max_agents"`
	MaxDomains       int   `json:"max_domains"`
	MaxMessagesMonth int   `json:"max_messages_month"`
	MaxStorageBytes  int64 `json:"max_storage_bytes"`

} // @name LimitsCaps

type LimitsInfo added in v0.3.0

type LimitsInfo struct {
	PlanCode   string      `json:"plan_code"`
	Limits     LimitsCaps  `json:"limits"`
	Usage      LimitsUsage `json:"usage"`
	UpgradeURL string      `json:"upgrade_url"`

} // @name LimitsInfo

LimitsInfo is the response body for GET /api/v1/users/me/limits. It bundles the user's resolved caps with their current usage so the dashboard renders the "you're using X of Y" surface in one round trip. plan_code and upgrade_url come straight from the limits row (opaque to OSS — written by whatever provisions the row); when no row exists, plan_code is the operator default and upgrade_url is empty.

type LimitsUsage added in v0.3.0

type LimitsUsage struct {
	Agents        int   `json:"agents"`
	Domains       int   `json:"domains"`
	MessagesMonth int   `json:"messages_month"`
	StorageBytes  int64 `json:"storage_bytes"`

} // @name LimitsUsage

type ListAgentsResponse

type ListAgentsResponse struct {
	Agents []AgentInfo `json:"agents"`

} // @name ListAgentsResponse

ListAgentsResponse wraps the agent list returned by GET /api/v1/agents.

type ListConversationsResponse added in v0.3.0

type ListConversationsResponse struct {
	Conversations []ConversationSummary `json:"conversations"`

} // @name ListConversationsResponse

ListConversationsResponse wraps the conversations list. Pagination is intentionally deferred — the response is hard-capped at 100 conversations server-side, which covers the inbox-style use case without a cursor.

type ListDomainsResponse

type ListDomainsResponse struct {
	Domains []DomainInfo `json:"domains"`

} // @name ListDomainsResponse

ListDomainsResponse wraps the domain list returned by GET /api/v1/domains.

type ListEventsResponse added in v0.3.0

type ListEventsResponse struct {
	Events    []WebhookEvent `json:"events"`
	NextToken string         `json:"next_token,omitempty"`

} // @name ListEventsResponse

ListEventsResponse wraps the events list.

type ListMessagesResponse

type ListMessagesResponse struct {
	Messages  []MessageSummary `json:"messages"`
	NextToken string           `json:"next_token,omitempty"`

} // @name ListMessagesResponse

ListMessagesResponse wraps the message list with pagination.

type ListPendingMessagesResponse

type ListPendingMessagesResponse struct {
	Messages []PendingMessageSummary `json:"messages"`

} // @name ListPendingMessagesResponse

ListPendingMessagesResponse wraps the array returned by GET /api/v1/messages.

type ListSigningSecretsResponse added in v0.3.0

type ListSigningSecretsResponse struct {
	Secrets []SigningSecretSummary `json:"secrets"`

} // @name ListSigningSecretsResponse

ListSigningSecretsResponse is the GET shape.

type ListWebhookDeliveriesResponse added in v0.3.0

type ListWebhookDeliveriesResponse struct {
	Deliveries []WebhookDeliveryResponse `json:"deliveries"`

} // @name ListWebhookDeliveriesResponse

ListWebhookDeliveriesResponse wraps the deliveries endpoint.

type ListWebhooksResponse added in v0.3.0

type ListWebhooksResponse struct {
	Webhooks []WebhookResponse `json:"webhooks"`

} // @name ListWebhooksResponse

ListWebhooksResponse wraps the list endpoint.

type MessageDetail

type MessageDetail struct {
	MessageID      string   `json:"message_id" example:"msg_abc123"`
	From           string   `json:"from" example:"alice@example.com"`
	To             []string `json:"to" example:"my-bot@example.com"`
	CC             []string `json:"cc,omitempty"`
	ReplyTo        []string `json:"reply_to,omitempty"`
	Recipient      string   `json:"recipient" example:"my-bot@example.com"`
	Subject        string   `json:"subject" example:"Hello"`
	ConversationID string   `json:"conversation_id,omitempty"`
	Status         string   `json:"status" example:"read"`
	// Labels are caller-applied string tags. See MessageSummary.Labels
	// for the validation rules. Empty array when no labels are set —
	// never null.
	Labels      []string          `json:"labels"`
	CreatedAt   string            `json:"created_at"`
	AuthHeaders map[string]string `json:"auth_headers"`
	RawMessage  string            `json:"raw_message"`

} // @name MessageDetail

MessageDetail is the full message content returned by GET /api/v1/agents/{email}/messages/{id}, which marks unread messages as read when fetched. To and CC are the parsed To: / Cc: headers from the original message; Recipient is this delivery's per-agent target. ReplyTo carries the parsed Reply-To: header so consumers can identify the intended reply mailbox for forwarded / notification mail (e.g. From: notifications@..., Reply-To: <real-user>).

type MessageSummary

type MessageSummary struct {
	MessageID      string   `json:"message_id" example:"msg_abc123"`
	Direction      string   `json:"direction" example:"inbound" enums:"inbound,outbound"`
	From           string   `json:"from" example:"alice@example.com"`
	To             []string `json:"to" example:"my-bot@example.com"`
	CC             []string `json:"cc,omitempty"`
	ReplyTo        []string `json:"reply_to,omitempty"`
	Recipient      string   `json:"recipient" example:"my-bot@example.com"`
	Subject        string   `json:"subject" example:"Hello"`
	ConversationID string   `json:"conversation_id,omitempty"`
	// Status carries the inbound inbox_status value (`unread` | `read`).
	// Empty string for outbound rows — clients filtering on Status must
	// gate on `Direction == "inbound"` first. The enum was removed from
	// the swag annotation deliberately so SDK generators don't emit a
	// `Literal["unread", "read"]` that breaks at runtime.
	Status        string `json:"status"`
	HITLStatus    string `json:"hitl_status,omitempty" example:"sent" enums:"pending_approval,sent,rejected,expired_approved,expired_rejected"`
	WebhookStatus string `json:"webhook_status,omitempty" example:"delivered" enums:"pending,delivered,failed"`
	WebhookError  string `json:"webhook_error,omitempty"`
	SizeBytes     int    `json:"size_bytes,omitempty" example:"4231"`
	// Labels are caller-applied string tags. Always lowercase, charset
	// `[a-z0-9:_-]+`, ≤ 64 chars each, ≤ 100 per message. The `e2a:`
	// prefix is reserved for server-applied system labels. Empty array
	// when no labels are set — never null.
	Labels    []string `json:"labels"`
	CreatedAt string   `json:"created_at" example:"2025-01-15T10:30:00Z"`

} // @name MessageSummary

MessageSummary is a lightweight message summary for the list endpoint. To and CC are the parsed To: / Cc: headers from the original message; Recipient is this delivery's per-agent target. ReplyTo is the parsed Reply-To: header — empty when the sender did not request a different reply mailbox (the server never falls back to From: silently).

This shape covers both inbound and outbound rows since the dashboard inbox queries `?direction=all`. The non-`omitempty` fields are present on every row; the others are direction-specific:

  • Status (inbound): "unread" | "read"; empty string for outbound rows.
  • HITLStatus (outbound): the outbound delivery state ("pending_approval", "sent", "rejected", "expired_*"); empty for inbound.
  • WebhookStatus (outbound): delivery state of the most recent webhook attempt ("delivered", "pending", "failed"); empty for inbound.
  • WebhookError (outbound): last webhook delivery error text when WebhookStatus is "failed"; empty otherwise.
  • SizeBytes: raw message length in bytes (best-effort, 0 when the row was migrated from a pre-sizing build).

type OAuthClientPublicMetadata added in v0.3.0

type OAuthClientPublicMetadata struct {
	ClientID         string   `json:"client_id"`
	ClientName       string   `json:"client_name"`
	RedirectURIs     []string `json:"redirect_uris"`
	Scopes           []string `json:"scopes"`
	ClientIDIssuedAt int64    `json:"client_id_issued_at"`
}

OAuthClientPublicMetadata is the subset of an oauth_clients row we surface to anonymous readers. The consent page (web/) fetches this so it can render the friendly client_name beside the requested scope. No secrets are present in this struct; secret_hash and internal bookkeeping fields are deliberately omitted.

type OAuthError added in v0.3.0

type OAuthError struct {
	Error            string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
}

OAuthError is the RFC 7591 §3.2.2 / RFC 6749 §5.2 JSON error body. Used for DCR-side errors and direct (non-redirected) authorize errors.

type OAuthMetadata added in v0.3.0

type OAuthMetadata struct {
	Issuer                                 string   `json:"issuer"`
	AuthorizationEndpoint                  string   `json:"authorization_endpoint"`
	TokenEndpoint                          string   `json:"token_endpoint"`
	RegistrationEndpoint                   string   `json:"registration_endpoint"`
	RevocationEndpoint                     string   `json:"revocation_endpoint"`
	ResponseTypesSupported                 []string `json:"response_types_supported"`
	ResponseModesSupported                 []string `json:"response_modes_supported"`
	GrantTypesSupported                    []string `json:"grant_types_supported"`
	CodeChallengeMethodsSupported          []string `json:"code_challenge_methods_supported"`
	TokenEndpointAuthMethodsSupported      []string `json:"token_endpoint_auth_methods_supported"`
	RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported"`
	ScopesSupported                        []string `json:"scopes_supported"`
	// RFC 9207 §3 — advertises that authorize responses carry `iss`
	// (we emit it manually in writeAuthorizeRedirect since fosite
	// v0.49 doesn't ship native RFC 9207 support).
	AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
}

OAuthMetadata is the RFC 8414 authorization-server metadata document served at /.well-known/oauth-authorization-server. Field names use snake_case per §2 of the RFC. Only the fields e2a actually advertises are present — omitting an OPTIONAL field (introspection_endpoint, jwks_uri, …) signals to clients that the feature isn't supported.

type OAuthRegisterRequest added in v0.3.0

type OAuthRegisterRequest struct {
	ClientName              string   `json:"client_name"`
	RedirectURIs            []string `json:"redirect_uris"`
	GrantTypes              []string `json:"grant_types,omitempty"`
	ResponseTypes           []string `json:"response_types,omitempty"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method,omitempty"`
	Scope                   string   `json:"scope,omitempty"`
}

OAuthRegisterRequest is the RFC 7591 §2 client metadata POSTed to /api/oauth/register. Unknown fields are tolerated (forward-compat with RFC 7591 extensions) but ignored.

type OAuthRegisterResponse added in v0.3.0

type OAuthRegisterResponse struct {
	ClientID                string   `json:"client_id"`
	ClientIDIssuedAt        int64    `json:"client_id_issued_at"`
	ClientName              string   `json:"client_name"`
	RedirectURIs            []string `json:"redirect_uris"`
	GrantTypes              []string `json:"grant_types"`
	ResponseTypes           []string `json:"response_types"`
	TokenEndpointAuthMethod string   `json:"token_endpoint_auth_method"`
	Scope                   string   `json:"scope"`
}

OAuthRegisterResponse is the RFC 7591 §3.2.1 success envelope. Echoes the metadata the server stored (after defaults applied) plus the assigned client_id and issuance timestamp. No client_secret — public clients only.

type PendingMessageDetail

type PendingMessageDetail struct {
	PendingMessageSummary
	EmailMessageID string       `json:"email_message_id,omitempty" example:"<orig@gmail.com>"`
	BodyText       string       `json:"body_text,omitempty"`
	BodyHTML       string       `json:"body_html,omitempty"`
	Attachments    []Attachment `json:"attachments,omitempty"`
	Edited         bool         `json:"edited,omitempty"`
	// InboundContext is attached when this is a reply — provides the
	// SPF/DKIM/DMARC provenance + sender/subject of the inbound message
	// being replied to so the review panel can render the context pane.
	InboundContext *PendingMessageInboundContext `json:"inbound,omitempty"`
	ReviewedAt     string                        `json:"reviewed_at,omitempty" example:"2025-01-15T10:35:00Z"`
	// ReviewedByUserID identifies the human reviewer for approved or
	// rejected messages. NULL on TTL-expired transitions (worker
	// auto-approve / auto-reject) where no human reviewed the message.
	ReviewedByUserID *string `json:"reviewed_by_user_id,omitempty" example:"usr_abc123"`
	// ReviewedByName is the JOIN'd display name from the reviewer's
	// users row. NULL when reviewed_by_user_id is null (worker) or when
	// the reviewer's user account has since been deleted (the FK has
	// ON DELETE SET NULL specifically so this doesn't poison the audit
	// trail).
	ReviewedByName    *string `json:"reviewed_by_name,omitempty" example:"Jamie"`
	RejectionReason   string  `json:"rejection_reason,omitempty"`
	ProviderMessageID string  `json:"provider_message_id,omitempty"`
	Method            string  `json:"method,omitempty" example:"smtp"`

} // @name PendingMessageDetail

PendingMessageDetail extends the summary with the stored body, attachments, and review metadata. Body columns are populated only while the row is in pending_approval; terminal rows return empty bodies since the server scrubs them on transition.

type PendingMessageInboundContext added in v0.3.0

type PendingMessageInboundContext struct {
	Sender    string `json:"sender" example:"alice@gmail.com"`
	Subject   string `json:"subject" example:"contract details"`
	CreatedAt string `json:"created_at" example:"2025-01-15T10:25:00Z"`
	// AuthHeaders carries the SPF/DKIM/DMARC validation results captured
	// at inbound time. Keys are conventionally "spf", "dkim", "dmarc"
	// each with values "pass" | "fail" | "neutral" | etc. The dashboard
	// renders these as found/missing chips on the provenance pane.
	AuthHeaders map[string]string `json:"auth_headers,omitempty"`

} // @name PendingMessageInboundContext

PendingMessageInboundContext is the inlined inbound-row preview attached to a reply's pending detail. Body is intentionally elided (the inbound's raw_message is RFC 5322 bytes; the review panel surfaces only the headers + auth_headers).

type PendingMessageSummary

type PendingMessageSummary struct {
	ID                string    `json:"id" example:"msg_abc123"`
	AgentID           string    `json:"agent_id" example:"my-bot@example.com"`
	Direction         string    `json:"direction" example:"outbound"`
	Subject           string    `json:"subject" example:"Re: contract details"`
	Type              string    `json:"type,omitempty" example:"send" enums:"send,reply,test,forward"`
	ConversationID    string    `json:"conversation_id,omitempty"`
	To                []string  `json:"to" example:"alice@example.com"`
	CC                []string  `json:"cc,omitempty"`
	BCC               []string  `json:"bcc,omitempty"`
	Status            string    `json:"status" example:"pending_approval" enums:"sent,pending_approval,rejected,expired_approved,expired_rejected"`
	ApprovalExpiresAt string    `json:"approval_expires_at,omitempty" example:"2025-01-15T10:30:00Z"`
	CreatedAt         time.Time `json:"created_at"`

} // @name PendingMessageSummary

PendingMessageSummary is a row in GET /api/v1/messages?status=pending_approval. Body and attachments are intentionally omitted — fetch the full detail via GET /api/v1/messages/{id} when the reviewer drills in.

type RedeliverDeliveryResult added in v0.3.0

type RedeliverDeliveryResult struct {
	WebhookID  string `json:"webhook_id"`
	DeliveryID string `json:"delivery_id,omitempty"`
	Status     string `json:"status"`
	Reason     string `json:"reason,omitempty"`

} // @name RedeliverDeliveryResult

RedeliverDeliveryResult is one element of a fan-out replay response.

type RedeliverRequest added in v0.3.0

type RedeliverRequest struct {
	WebhookID string `json:"webhook_id,omitempty"`

} // @name RedeliverRequest

RedeliverRequest is the body of POST /events/{id}/redeliver.

type RedeliverResponse added in v0.3.0

type RedeliverResponse struct {
	DeliveryID string                    `json:"delivery_id,omitempty"`
	EventID    string                    `json:"event_id"`
	WebhookID  string                    `json:"webhook_id,omitempty"`
	Status     string                    `json:"status"`
	Deliveries []RedeliverDeliveryResult `json:"deliveries,omitempty"`

} // @name RedeliverResponse

RedeliverResponse wraps the result of a replay request.

type RedeliverSinceRequest added in v0.3.0

type RedeliverSinceRequest struct {
	Since string `json:"since"`

} // @name RedeliverSinceRequest

RedeliverSinceRequest is the body of POST /webhooks/{id}/redeliver-since.

type RedeliverSinceResponse added in v0.3.0

type RedeliverSinceResponse struct {
	WebhookID             string `json:"webhook_id"`
	Since                 string `json:"since"`
	Scheduled             int    `json:"scheduled"`
	SkippedAlreadyPending int    `json:"skipped_already_pending"`

} // @name RedeliverSinceResponse

RedeliverSinceResponse wraps the result of a bulk-replay request.

type RegisterAgentRequest

type RegisterAgentRequest struct {
	Email      string `json:"email" example:"my-bot@yourdomain.com"`
	Slug       string `json:"slug" example:"my-bot"`
	Name       string `json:"name" example:"My Bot"`
	WebhookURL string `json:"webhook_url,omitempty" example:"https://example.com/e2a/webhook"`
	// AgentMode selects how inbound mail is delivered. Required; must be "local" or "cloud". See the type-level docs for the difference.
	AgentMode string `json:"agent_mode" example:"local" enums:"local,cloud" binding:"required"`

} // @name RegisterAgentRequest

RegisterAgentRequest is the request body for POST /api/v1/agents.

`agent_mode` is required and must be either "local" or "cloud":

  • "local" — the e2a server queues inbound mail in its own store and pushes notifications over a WebSocket (`/api/v1/agents/{email}/ws`) or makes it pollable via the REST API. Use this when the agent runs on a laptop, edge device, or behind NAT without a public URL. `webhook_url` is not required.

  • "cloud" — the e2a server delivers inbound mail to the agent over HTTPS POST to `webhook_url`. Use this when the agent is deployed somewhere publicly reachable. `webhook_url` MUST be set and must point to an HTTPS endpoint that resolves to a non-private IP.

type RegisterAgentResponse

type RegisterAgentResponse struct {
	ID     string `json:"id"`
	Domain string `json:"domain"`
	Email  string `json:"email"`

} // @name RegisterAgentResponse

type RegisterDomainRequest

type RegisterDomainRequest struct {
	Domain string `json:"domain" example:"yourdomain.com"`

} // @name RegisterDomainRequest

RegisterDomainRequest is the request body for POST /api/v1/domains.

type RegisterDomainResponse

type RegisterDomainResponse = DomainInfo // @name RegisterDomainResponse

RegisterDomainResponse is the response for POST /api/v1/domains.

type RejectPendingMessageRequest

type RejectPendingMessageRequest struct {
	Reason string `json:"reason,omitempty" example:"wrong recipient"`

} // @name RejectPendingMessageRequest

RejectPendingMessageRequest is the optional body for POST /api/v1/messages/{id}/reject.

type RejectPendingMessageResponse

type RejectPendingMessageResponse struct {
	Status          string `json:"status" example:"rejected"`
	MessageID       string `json:"message_id"`
	RejectionReason string `json:"rejection_reason,omitempty"`

} // @name RejectPendingMessageResponse

RejectPendingMessageResponse is the response when the reviewer rejects a held message.

type ReplyRequest

type ReplyRequest struct {
	Body           string                `json:"body"`
	HTMLBody       string                `json:"html_body,omitempty"`
	ReplyAll       bool                  `json:"reply_all,omitempty"`
	CC             []string              `json:"cc,omitempty"`
	BCC            []string              `json:"bcc,omitempty"`
	ConversationID string                `json:"conversation_id,omitempty"`
	Attachments    []outbound.Attachment `json:"attachments,omitempty"`
}

type ReplyToMessageRequest

type ReplyToMessageRequest struct {
	Body           string       `json:"body" example:"Thanks for your email!"`
	HTMLBody       string       `json:"html_body,omitempty" example:"<p>Thanks for your email!</p>"`
	ReplyAll       bool         `json:"reply_all,omitempty" example:"false"`
	CC             []string     `json:"cc,omitempty" example:"bob@example.com"`
	BCC            []string     `json:"bcc,omitempty" example:"carol@example.com"`
	ConversationID string       `json:"conversation_id,omitempty"`
	Attachments    []Attachment `json:"attachments,omitempty"`

} // @name ReplyToMessageRequest

ReplyToMessageRequest is the request body for replying to a message.

type RotateWebhookSecretResponse added in v0.3.0

type RotateWebhookSecretResponse struct {
	SigningSecret           string `json:"signing_secret"`
	PreviousSecretExpiresAt string `json:"previous_secret_expires_at"`

} // @name RotateWebhookSecretResponse

RotateWebhookSecretResponse carries the new plaintext secret + the 24h expiry for the previous secret's grace window.

type SendEmailRequest

type SendEmailRequest struct {
	From           string       `json:"from,omitempty" example:"my-bot@example.com"`
	To             []string     `json:"to" example:"alice@example.com"`
	CC             []string     `json:"cc,omitempty" example:"bob@example.com"`
	BCC            []string     `json:"bcc,omitempty" example:"carol@example.com"`
	Subject        string       `json:"subject" example:"Hello from my agent"`
	Body           string       `json:"body" example:"Hi Alice, this is my agent reaching out."`
	HTMLBody       string       `json:"html_body,omitempty" example:"<p>Hi Alice</p>"`
	ConversationID string       `json:"conversation_id,omitempty" example:"conv_abc123"`
	Attachments    []Attachment `json:"attachments,omitempty"`

} // @name SendEmailRequest

SendEmailRequest is the request body for POST /api/v1/send.

type SendEmailResponse

type SendEmailResponse struct {
	Status            string `json:"status" example:"sent" enums:"sent,pending_approval"`
	MessageID         string `json:"message_id" example:"msg_abc123"`
	Method            string `json:"method,omitempty" example:"smtp"`
	ApprovalExpiresAt string `json:"approval_expires_at,omitempty" example:"2025-01-15T10:30:00Z"`

} // @name SendEmailResponse

SendEmailResponse is the response for send and reply operations. When the owning agent has HITL enabled, the server responds with status = "pending_approval" and 202 Accepted; approval_expires_at is set in that case. Otherwise status = "sent" with 200.

type SigningSecretSummary added in v0.3.0

type SigningSecretSummary struct {
	ID           string  `json:"id"`
	Name         string  `json:"name"`
	Secret       string  `json:"secret"`
	SecretPrefix string  `json:"secret_prefix"`
	CreatedAt    string  `json:"created_at"`
	LastSignedAt *string `json:"last_signed_at,omitempty"`

} // @name SigningSecretSummary

SigningSecretSummary is the list shape. Includes the full plaintext `secret` because the relay already stores it that way (it has to — it signs with it on every inbound). Surfacing it on list lets the dashboard show the value on demand without storing yet another copy. API keys are deliberately not handled the same way: their hashes-only posture is intentional and we keep it. The `secret_prefix` field stays populated for compact list views.

type TestWebhookRequest added in v0.3.0

type TestWebhookRequest struct {
	Event string                 `json:"event" example:"email.received"`
	Data  map[string]interface{} `json:"data,omitempty"`

} // @name TestWebhookRequest

TestWebhookRequest fires a synthetic event for development.

type TestWebhookResponse added in v0.3.0

type TestWebhookResponse struct {
	DeliveryID string `json:"delivery_id"`

} // @name TestWebhookResponse

TestWebhookResponse echoes the delivery id of the synthetic event so the caller can correlate it in GET /webhooks/{id}/deliveries.

type UpdateAgentRequest

type UpdateAgentRequest struct {
	WebhookURL           *string `json:"webhook_url,omitempty"`
	AgentMode            *string `json:"agent_mode,omitempty" enums:"cloud,local"`
	HITLEnabled          *bool   `json:"hitl_enabled,omitempty"`
	HITLTTLSeconds       *int    `json:"hitl_ttl_seconds,omitempty" example:"604800"`
	HITLExpirationAction *string `json:"hitl_expiration_action,omitempty" enums:"approve,reject"`

} // @name UpdateAgentRequest

UpdateAgentRequest is the request body for PUT /api/v1/agents/{email}. All fields are optional; only the fields you send are updated, so callers can PATCH a single setting without re-sending the rest.

type UpdateDomainRequest added in v0.3.0

type UpdateDomainRequest struct {
	IsPrimary *bool `json:"is_primary,omitempty"`

} // @name UpdateDomainRequest

UpdateDomainRequest is the body for PATCH /api/v1/domains/{domain}. Only `is_primary=true` is meaningful — see handleUpdateDomain.

type UpdateMessageRequest added in v0.3.0

type UpdateMessageRequest struct {
	AddLabels    []string `json:"add_labels,omitempty" example:"urgent"`
	RemoveLabels []string `json:"remove_labels,omitempty" example:"unread"`

} // @name UpdateMessageRequest

UpdateMessageRequest is the request body for PATCH /api/v1/agents/{email}/messages/{id}. Currently the only supported mutation is the labels delta — passing an empty body is a no-op. Both add_labels and remove_labels may be set in one request; on overlap the remove wins (the union is applied first, then the difference, mirroring Gmail's semantics).

type UpdateMessageResponse added in v0.3.0

type UpdateMessageResponse struct {
	MessageID string   `json:"message_id" example:"msg_abc123"`
	Labels    []string `json:"labels"`

} // @name UpdateMessageResponse

UpdateMessageResponse is the response shape for label mutations. Returns the post-update label set so callers can echo state without a separate fetch.

type UpdateWebhookRequest added in v0.3.0

type UpdateWebhookRequest struct {
	URL         *string         `json:"url,omitempty"`
	Events      *[]string       `json:"events,omitempty"`
	Filters     *WebhookFilters `json:"filters,omitempty"`
	Description *string         `json:"description,omitempty"`
	Enabled     *bool           `json:"enabled,omitempty"`

} // @name UpdateWebhookRequest

UpdateWebhookRequest is the PATCH body. All fields optional; only fields present in the request are touched. url / events / filters are full-replace when present (the sent value is canonical).

type UserExport

type UserExport = identity.UserExport // @name UserExport

type VerifyDomainResponse

type VerifyDomainResponse struct {
	Domain     string     `json:"domain" example:"yourdomain.com"`
	Verified   bool       `json:"verified"`
	VerifiedAt *time.Time `json:"verified_at,omitempty"`
	// MX status: "found" iff at least one MX record points at the
	// deployment's smtp domain. "missing" otherwise.
	MX string `json:"mx,omitempty" example:"found" enums:"found,missing"`
	// SPF status: "found" iff a v=spf1 TXT record includes the
	// deployment's send domain. "missing" otherwise.
	SPF string `json:"spf,omitempty" example:"found" enums:"found,missing"`
	// DKIM status: "found" iff the published TXT record at
	// "{selector}._domainkey.{domain}" matches the per-domain public
	// key stored at registration time. "missing" iff a keypair is
	// stored but the TXT record isn't published yet. "deferred" iff
	// no keypair is stored — pre-migration rows that haven't been
	// re-claimed since #5 shipped. A fresh-claimed domain always has
	// a keypair, so "deferred" only appears on legacy data.
	DKIM string `json:"dkim,omitempty" example:"found" enums:"found,missing,deferred"`

} // @name VerifyDomainResponse

VerifyDomainResponse is the response for POST /api/v1/domains/{domain}/verify. Per-record diagnostic fields (MX, SPF, DKIM) report what the probe found in DNS independent of the verified bool — verified=true iff the TXT ownership token is present, while MX/SPF/DKIM are advisory.

type WebSocketNotification

type WebSocketNotification struct {
	MessageID      string    `json:"message_id" example:"msg_abc123"`
	ConversationID string    `json:"conversation_id,omitempty"`
	From           string    `json:"from" example:"alice@example.com"`
	Recipient      string    `json:"recipient" example:"my-bot@example.com"`
	Subject        string    `json:"subject" example:"Hello"`
	ReceivedAt     time.Time `json:"received_at"`

} // @name WebSocketNotification

WebSocketNotification is the lightweight notification sent over WebSocket when a new message arrives for an agent. It contains only metadata — the full message content (including the To/Cc lists) is fetched via GET /api/v1/agents/{email}/messages/{id}.

type WebhookDeliveryResponse added in v0.3.0

type WebhookDeliveryResponse struct {
	ID             string `json:"id"`
	EventType      string `json:"event_type"`
	Status         string `json:"status"`
	Attempts       int    `json:"attempts"`
	LastError      string `json:"last_error,omitempty"`
	LastStatusCode *int   `json:"last_status_code,omitempty"`
	LastAttemptAt  string `json:"last_attempt_at,omitempty"`
	NextRetryAt    string `json:"next_retry_at"`
	CreatedAt      string `json:"created_at"`

} // @name WebhookDeliveryResponse

WebhookDeliveryResponse is one row in GET /webhooks/{id}/deliveries.

type WebhookEvent added in v0.3.0

type WebhookEvent struct {
	ID             string                 `json:"id"`
	Type           string                 `json:"type"`
	SchemaVersion  int                    `json:"schema_version"`
	CreatedAt      string                 `json:"created_at"`
	AgentID        *string                `json:"agent_id,omitempty"`
	ConversationID *string                `json:"conversation_id,omitempty"`
	MessageID      *string                `json:"message_id,omitempty"`
	Status         string                 `json:"status"`
	Data           map[string]interface{} `json:"data"`
	DeliveryStatus *DeliveryStatus        `json:"delivery_status,omitempty"`

} // @name WebhookEvent

WebhookEvent is the wire shape returned by GET /events and GET /events/{id}. Mirrors design §4.6.

type WebhookFilters added in v0.3.0

type WebhookFilters struct {
	AgentIDs        []string `json:"agent_ids,omitempty"`
	ConversationIDs []string `json:"conversation_ids,omitempty"`
	Labels          []string `json:"labels,omitempty"`

} // @name WebhookFilters

WebhookFilters is the structured scope filter on a webhook. Empty / missing keys mean "no constraint of that type". A webhook with all-empty filters is a cross-cutting subscriber that matches every event of the right type for the owning user.

type WebhookPayload

type WebhookPayload struct {
	MessageID      string            `json:"message_id" example:"msg_abc123"`
	ConversationID string            `json:"conversation_id,omitempty"`
	From           string            `json:"from" example:"alice@example.com"`
	To             []string          `json:"to" example:"agent@yourdomain.com"`
	CC             []string          `json:"cc,omitempty"`
	Recipient      string            `json:"recipient" example:"agent@yourdomain.com"`
	RawMessage     []byte            `json:"raw_message"`
	AuthHeaders    map[string]string `json:"auth_headers"`
	ReceivedAt     time.Time         `json:"received_at"`

} // @name WebhookPayload

WebhookPayload is the payload delivered to your webhook URL when your agent receives an email. This schema is for documentation only — the actual delivery is handled by the webhook package. To and CC are the parsed To: / Cc: headers; Recipient is the per-agent target for this delivery.

type WebhookResponse added in v0.3.0

type WebhookResponse struct {
	ID                      string         `json:"id" example:"wh_abc123"`
	URL                     string         `json:"url"`
	Description             string         `json:"description"`
	Events                  []string       `json:"events"`
	Filters                 WebhookFilters `json:"filters"`
	SigningSecret           string         `json:"signing_secret,omitempty"`             // ONLY on create + rotate
	PreviousSecretExpiresAt string         `json:"previous_secret_expires_at,omitempty"` // ONLY on rotate
	Enabled                 bool           `json:"enabled"`
	AutoDisabledAt          string         `json:"auto_disabled_at,omitempty"`
	CreatedAt               string         `json:"created_at"`
	LastDeliveredAt         string         `json:"last_delivered_at,omitempty"`

} // @name WebhookResponse

WebhookResponse is the GET / POST response shape. SigningSecret is populated only on POST /webhooks and POST /webhooks/{id}/rotate-secret — every other endpoint omits it so a stolen API key cannot exfiltrate secrets via list/get.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL