Gent API Documentation

Auth API

Endpoints for account creation, session management, MFA, and token refresh. All /auth/ endpoints are unauthenticated unless noted — they do not require a Bearer token.

POST /auth/signup/ Create a new account
Request
{ "email": "you@example.com", "password": "...", "region": "eu", // "us" | "eu" | "asia" — where your data is stored "llm_consent": true // EU only — opt in to AI features at sign-up }
202 Accepted — confirmation email sent
POST /auth/signup/confirm/ Confirm email address
Request
{ "email": "you@example.com", "code": "123456" }
200 OK — account confirmed, ready to sign in
POST /auth/login/ Sign in — returns access + refresh tokens
Request
{ "email": "you@example.com", "password": "..." }
200 OK
{ "access_token": "eyJ...", "refresh_token": "eyJ..." }
FieldTypeRequiredDescription
access_tokenstringoptionalShort-lived.
refresh_tokenstringoptionalLong-lived.
If MFA is enabled the response is {"challenge": "mfa", "session": "..."} instead. Pass session and your TOTP code to POST /auth/mfa/ to complete sign-in.
POST /auth/mfa/ Complete MFA challenge — optional, only if MFA is enabled
Request
{ "email": "you@example.com", "session": "...", "code": "123456" }
FieldTypeRequiredDescription
sessionstringrequiredFrom login challenge response.
emailstringrequiredEmail address for the challenged account.
codestringrequiredTOTP code.
200 OK — same shape as login success
POST /auth/refresh/ Renew an expired access token
Request
{ "refresh_token": "eyJ..." }
200 OK
{ "access_token": "eyJ..." }
POST /auth/logout/ Revoke session — requires session token
# 204 No Content — empty body
POST /auth/signup/resend/ Re-send confirmation code
Request
{ "email": "you@example.com" }
200 OK
{ "message": "If that account exists and is unconfirmed, a new code has been sent." }
Always returns 200 — does not reveal whether the email is registered.
POST /auth/forgot-password/ Request a password reset code
Request
{ "email": "you@example.com" }
200 OK
{ "message": "If that email is registered, a reset code has been sent." }
Always returns 200 — does not reveal whether the email exists.
POST /auth/forgot-password/confirm/ Confirm password reset
Request
{ "email": "you@example.com", "code": "123456", "new_password": "..." }
FieldTypeRequiredDescription
codestringrequired6 digits sent to email.
new_passwordstringrequiredMin 8 characters.
200 OK
{ "message": "Password reset successfully." }
POST /auth/change-password/ Change password  session auth
Request
{ "old_password": "...", "new_password": "..." }
FieldTypeRequiredDescription
new_passwordstringrequiredMin 8 characters.
200 OK
{ "message": "Password changed successfully." }
GET /auth/mfa/status/ Get MFA status for the current user  session auth
200 OK
{ "enabled": true, "preferred": "TOTP" }
FieldTypeRequiredDescription
enabledbooleanoptionalWhether MFA is active.
preferredstringoptionalThe configured MFA type, or null.
POST /auth/mfa/setup/ Begin TOTP enrollment  session auth
200 OK
{ "secret": "BASE32SECRET", "otpauth_uri": "otpauth://totp/..." }
FieldTypeRequiredDescription
secretstringoptionalRaw TOTP secret — show to user or encode as QR.
otpauth_uristringoptionalStandard otpauth URI for QR code generation.
Complete setup by confirming with POST /auth/mfa/setup/confirm/ using a code from the authenticator app.
POST /auth/mfa/setup/confirm/ Confirm TOTP enrollment  session auth
Request
{ "code": "123456" }
FieldTypeRequiredDescription
codestringrequired6-digit TOTP code from the authenticator app.
200 OK
{ "message": "MFA enabled successfully." }
After confirmation, all future sign-ins require a TOTP code via POST /auth/mfa/.
DEL /auth/mfa/disable/ Disable MFA for the current user  session auth
200 OK
{ "message": "MFA disabled." }
Requires an active session. After disabling, sign-in no longer requires a TOTP code.
GET /v1/users/me/ Current user + onboarding status  user auth
200 OK
{ "sub": "<cognito-uuid>", "email": "you@example.com", "display_name": "Alice", "tenant_id": "<tenant_id>", "role": "owner", "status": "active", "created_at": "2026-06-02T10:00:00Z", "next_step": "add_domain" // null when onboarding is complete }
next_step valueWhat to do
provisioningTenant is still being created — retry in a moment.
add_domainPOST /v1/domains/ to register your first domain.
verify_domainSet DNS records, then POST /v1/domains/{name}/verify/.
create_inboxPOST /v1/inboxes/ to create your first inbox.
nullOnboarding complete.
Time Utils

Three utility endpoints for time and duration handling. parse converts any datetime string (natural language, relative, ISO 8601) to UTC — useful for constructing calendar event start values from user input. convert takes a UTC datetime and returns its local equivalent in a given timezone — useful for displaying stored values. duration parse converts any duration string (natural language or ISO 8601) to a normalised ISO 8601 duration with total seconds and a human-readable label — useful for display or validation. Note: the calendar duration field already accepts natural language directly.

Auth: Token auth (any scope) or session auth. No specific scope required.
POST /v1/utils/time/parse/ Parse datetime string → UTC
Request
{ "input": "next Monday at 2pm", "timezone": "America/New_York", "inbox_id": "you@example.com" }
FieldTypeRequiredDescription
inputstringoptionalAny supported format.
timezonestringoptionalIANA timezone; defaults to inbox default_timezone, then UTC.
inbox_idstringsession authSession auth only; used to look up default_timezone when timezone is omitted.
Supported input formats:
CategoryExamples
ISO 8601"2024-06-01T10:00:00"  ·  "2024-06-01T10:00:00-05:00"  ·  "2024-06-01T15:00:00Z"
Natural date"June 1, 2024 10am"  ·  "01/06/2024 10:00"  ·  "2024/06/01"
Named day"today"  ·  "tomorrow"  ·  "yesterday"
Relative offset"in 2 hours"  ·  "in 3 days"  ·  "in 1 week"
Relative past"2 hours ago"  ·  "3 days ago"
Weekday relative"next Monday"  ·  "last Friday"
When the input has no timezone info (e.g. "June 1 10am"), it is interpreted in timezone (which defaults to the inbox's default_timezone). Inputs with an explicit offset (e.g. -05:00 or Z) use that offset directly and the timezone param only affects the local output.
200 OK
{ "utc": "2024-06-10T19:00:00Z", "local": "2024-06-10T14:00:00", "timezone": "America/New_York" }
FieldTypeRequiredDescription
utcstringoptionalISO 8601 UTC — use this as the event start value.
localstringoptionalLocalDateTime in the requested timezone.
timezonestringoptionalTimezone used for interpretation and local output.
400 — unparseable or unknown timezone
{ "detail": "Cannot parse datetime: 'whenever'" }
POST /v1/utils/time/convert/ Convert UTC → local time
Request
{ "utc": "2024-06-01T15:00:00Z", "timezone": "America/New_York", "inbox_id": "you@example.com" }
FieldTypeRequiredDescription
utcstringoptionalISO 8601 UTC datetime.
timezonestringoptionalIANA timezone; defaults to inbox default_timezone, then UTC.
inbox_idstringsession authSession auth only; used to look up default_timezone when timezone is omitted.
200 OK
{ "utc": "2024-06-01T15:00:00Z", "local": "2024-06-01T10:00:00", "timezone": "America/New_York" }
FieldTypeRequiredDescription
utcstringoptionalEchoed back.
localstringoptionalLocalDateTime in the requested timezone.
timezonestringoptionalTimezone used for local output.
400 — missing or invalid UTC value
{ "detail": "utc is required." }
POST /v1/utils/duration/parse/ Parse duration string → ISO 8601
Request
{ "input": "1 hour 30 minutes" }
FieldTypeRequiredDescription
inputstringoptionalNatural language or ISO 8601 duration.
Supported input formats:
InputISO 8601Seconds
"30 minutes"PT30M1800
"1 hour 30 minutes"PT1H30M5400
"2h 15m"PT2H15M8100
"2 days"P2D172800
"1 week"P1W604800
"PT1H30M" (ISO input)PT1H30M5400
Abbreviations are accepted: h / hr / hours, m / min / minutes, d / days, w / weeks, s / sec / seconds. ISO 8601 input is normalised and echoed back unchanged.
200 OK
{ "iso": "PT1H30M", "seconds": 5400, "natural": "1 hour 30 minutes" }
FieldTypeRequiredDescription
isostringoptionalISO 8601 duration — use this as the event duration value.
secondsnumberoptionalTotal seconds.
naturalstringoptionalHuman-readable description.
400 — unparseable or zero-duration input
{ "detail": "Cannot parse duration: 'whenever'" }
Reference catalog

Machine-readable reference endpoints and builder contracts generated from the live API.

GET /v1/reference/ List available reference endpoints  no auth required
200 OK
{ "references": [ { "path": "/v1/reference/api/", "description": "Endpoint, method, auth, scope, parameter, request, and response contract metadata.", "auth": "optional", "resource": "api", "contract": true, "kind": "api_catalog" }, { "path": "/v1/tokens/reference/", "description": "Token builder fields, scopes, and composite scope expansions.", "auth": "optional", "resource": "tokens", "contract": true, "kind": "builder_contract" }, { "path": "/v1/events/reference/", "description": "Event picker fields and event types available to notifications and subscribers.", "auth": "optional", "resource": "events", "contract": true, "kind": "builder_contract" }, { "path": "/v1/events/types/", "description": "Event type list available to event and alert pickers.", "auth": "optional", "resource": "event_types", "contract": false, "kind": "helper_list" }, { "path": "/v1/workflows/reference/", "description": "Workflow builder options filtered by caller permissions and plan.", "auth": "required", "resource": "workflows", "contract": true, "kind": "builder_contract" }, { "path": "/v1/workflows/triggers/", "description": "Workflow triggers available to the caller.", "auth": "required", "resource": "workflow_triggers", "contract": false, "kind": "helper_list" }, { "path": "/v1/workflows/conditions/", "description": "Workflow condition operators available to builders.", "auth": "required", "resource": "workflow_conditions", "contract": false, "kind": "helper_list" }, { "path": "/v1/workflows/write-targets/", "description": "Workflow write target metadata available to the caller; advanced preparation phases are opt-in.", "auth": "required", "resource": "workflow_write_targets", "contract": false, "kind": "helper_list" }, { "path": "/v1/workflows/write-templates/", "description": "Reusable workflow write blocks that can be referenced from multiple workflow rules.", "auth": "required", "resource": "workflow_write_templates", "contract": true, "kind": "collection" }, { "path": "/v1/sequences/reference/", "description": "Sequence template, step, enrollment, status, and action builder metadata.", "auth": "required", "resource": "sequences", "contract": true, "kind": "builder_contract" }, { "path": "/v1/work-routes/reference/", "description": "Work route request types, recipient routing modes, completion rules, and response actions.", "auth": "required", "resource": "work_routes", "contract": true, "kind": "builder_contract" }, { "path": "/v1/alerts/reference/", "description": "Alert builder fields, event delivery, retry, and offset options.", "auth": "none", "resource": "alerts", "contract": true, "kind": "builder_contract" }, { "path": "/v1/files/reference/", "description": "File builder fields, scopes, role capabilities, and thread share roles.", "auth": "none", "resource": "files", "contract": true, "kind": "builder_contract" }, { "path": "/v1/labels/reference/", "description": "Label builder fields, scopes, and label type metadata.", "auth": "none", "resource": "labels", "contract": true, "kind": "builder_contract" }, { "path": "/v1/messages/ticketing/reference/", "description": "Thread ticketing label groups, workflow states, priority values, and endpoint contract metadata.", "auth": "none", "resource": "message_ticketing", "contract": true, "kind": "builder_contract" }, { "path": "/v1/enrichment/reference/", "description": "Enrichment builder fields, models, pricing, and feature keys.", "auth": "none", "resource": "enrichment", "contract": true, "kind": "builder_contract" }, { "path": "/v1/enrichment/models/", "description": "Region-available enrichment model options.", "auth": "none", "resource": "enrichment_models", "contract": false, "kind": "helper_list" }, { "path": "/v1/enrichment/features/", "description": "Enrichment feature options.", "auth": "none", "resource": "enrichment_features", "contract": false, "kind": "helper_list" }, { "path": "/v1/retrieval/reference/", "description": "Retrieval source bucket, task, output, and run builder metadata.", "auth": "none", "resource": "retrieval", "contract": true, "kind": "builder_contract" }, { "path": "/v1/domains/reference/", "description": "Domain setup fields, DNS record guidance, and verification workflow.", "auth": "required", "resource": "domains", "contract": true, "kind": "builder_contract" }, { "path": "/v1/domains/dns-record-types/", "description": "DNS record types used during domain setup.", "auth": "required", "resource": "domain_dns_record_types", "contract": false, "kind": "helper_list" }, { "path": "/v1/sender-rules/reference/", "description": "Sender rule fields, modes, and pattern semantics.", "auth": "required", "resource": "sender_rules", "contract": true, "kind": "builder_contract" }, { "path": "/v1/sender-rules/modes/", "description": "Sender rule inbound and outbound mode options.", "auth": "required", "resource": "sender_rule_modes", "contract": false, "kind": "helper_list" }, { "path": "/v1/forwarding/reference/", "description": "Forwarding config fields, rule fields, and condition type semantics.", "auth": "required", "resource": "forwarding", "contract": true, "kind": "builder_contract" }, { "path": "/v1/forwarding/condition-types/", "description": "Forwarding rule condition type options.", "auth": "required", "resource": "forwarding_condition_types", "contract": false, "kind": "helper_list" } ], "profiles": { "default": { "include_ui": false, "description": "Removes presentation-only hints such as labels, widgets, placeholders, and helper text." }, "ui": { "query": "compact=false", "include_ui": true, "description": "Full builder metadata for UI clients." } }, "counts": { "references": 25 } }
Public callers receive 25 reference entries. Tenant admins receive 30 entries, including tenant-admin-only contracts.
GET /v1/reference/api/ List endpoint, method, auth, scope, and schema metadata  no auth required
200 OK
{ "version": "1.0.0", "source": "openapi", "counts": { "paths": 183, "operations": 276 }, "endpoints": [...] }
Unauthenticated callers receive the public client catalog. Tenant admins also receive tenant-management endpoints. Internal webhook, tracking, and operational routes are excluded.
Endpoint contract highlights
These rows are generated from GET /v1/reference/api/ and show conditional rules that plain OpenAPI schemas cannot express cleanly.
PathMethodQuery contractBody contractResponse contractNotes
/v1/activity/GETone of: contact_id, address; inbox_id or token-bound inbox; prefer: contact_idProvide contact_id or address. Use contact_id for stable contact identity across email aliases and future channel identities. Session requests must include inbox_id; token requests may use the token-bound inbox.
/v1/agenda/GETinbox_id or token-bound inboxnested: items[]Thread items include a source object with normalized email channel metadata when available.
/v1/billing/caps/files/PUTrequired: limit_gb; rules: limit_gb, mode, alert_thresholdsTenant-level cap. Tenant admins only may mutate it.
/v1/billing/caps/files/DELETETenant-level cap. Tenant admins only may delete it.
/v1/billing/caps/mailbox/GETinbox_id or token-bound inbox
/v1/billing/caps/mailbox/PUTinbox_id or token-bound inboxrequired: limit_gb; rules: limit_gb, mode, alert_thresholds
/v1/billing/caps/mailbox/DELETEinbox_id or token-bound inbox
/v1/billing/caps/send/GETinbox_id or token-bound inbox
/v1/billing/caps/send/PUTinbox_id or token-bound inboxrules: limit, mode
/v1/billing/caps/send/DELETEinbox_id or token-bound inbox
/v1/files/folders/{folder_id}/DELETE1 conditional required rule; rules: contentscontents defaults to unparent. Use contents=delete to delete files in the folder. Use contents=move with target_folder_id to move files before deleting the folder.
/v1/files/{file_id}/PATCHoptional: name, folder_id, instructions, viewers, collaborators, auto_share_from_threadMove a file by setting folder_id. Set folder_id to null to unparent the file. Changing folder_id, viewers, or collaborators requires manage access.
/v1/files/{file_id}/versions/POSTone of: file, contentUpload multipart file or JSON content to create a new current version. Previous versions remain available from GET /v1/files/{file_id}/versions/.
/v1/inboxes/{inbox_id}/imports/POSTrequired: source; nested: source, source.auth; rules: source.auth.mode, source.auth.password, options.foldersTenant-admin only. Mail-only import. Contacts, calendars, rules, and continuous sync are not imported. Secrets are write-only and are not returned by the API.
/v1/messages/POSTone of: text_body, html_body; 1 conditional required ruleProvide text_body or html_body. Provide to when reply_to_message_id is not set.
/v1/messages/threads/{thread_id}/fork/POSTrequired: to; one of: text_body, html_body; nested: ticketProvide text_body or html_body. Optional ticket.class_label_key must use ticket/class/<slug>.
/v1/messages/threads/{thread_id}/labels/POSTone of: label_id, label_keyProvide label_id or label_key.
/v1/messages/threads/{thread_id}/ticket/POSTrules: class_label_key, priorityclass_label_key must use ticket/class/<slug>. class_label_id must reference a thread-capable ticket class label.
/v1/messages/threads/{thread_id}/ticket/PATCHrules: class_label_key, status, priorityclass_label_key must use ticket/class/<slug> when present. Use workflow endpoints for resolve, close, and reopen transitions when a note is required.
/v1/messages/threads/{thread_id}/ticket/close/POSTrequired: resolution_note
/v1/messages/threads/{thread_id}/ticket/resolve/POSTrequired: resolution_note
/v1/messages/{message_id}/PATCHone of: mark_read, mark_starred, mailbox_idProvide at least one mutable message field.
/v1/retrieval/config/GETTenant-admin only. Returns tenant retrieval configuration, model options, source options, and pricing policy.
/v1/retrieval/config/PATCHnested: embedding, chunking, extraction; rules: embedding.model_id, chunking.overlap_charsTenant-admin only. Refresh, public crawls, and retention default to manual/conservative behavior. OCR can be disabled, local via tesseract, or managed via textract. Runtime packaging/IAM determines whether a provider can execute.
/v1/retrieval/extraction-tests/POSTrequired: file_id; nested: chunking, extractionTests one readable Files API document with the tenant retrieval config plus optional temporary overrides. Does not index, embed, or persist chunks. Returns status=ocr_required for scanned/image-only documents when OCR is needed but disabled for the request or tenant config. Returns status=ocr_failed when OCR is selected but the provider cannot extract text.
/v1/retrieval/models/GETReturns the platform allowlisted embedding models visible to this tenant. Vector embedding usage follows provider cost plus 5%.
/v1/retrieval/runs/GEToptional: inbox_id
/v1/retrieval/runs/POSTrequired: bucket_id, task, query; rules: output_typeoutput_type must be allowed by the selected task.
/v1/retrieval/source-buckets/GEToptional: owner_type, owner_id
/v1/retrieval/source-buckets/POSTrequired: name, bucket_type, owner_type, owner_id, sources; nested: sources[]Email sources require scope, then the identifier for that scope. Files sources require folder_id. Contacts, calendar, and intelligence sources require scope and the matching identifier for that scope. Public website sources require allowed_domains; seed_urls must stay inside allowed_domains.
/v1/retrieval/source-buckets/{bucket_id}/PATCHoptional: name, bucket_type, sources, policy, index_statusWhen sources are provided, the same source type rules as create apply.
/v1/retrieval/source-buckets/{bucket_id}/estimate/POSTforbidden: trigger; nested: chunks[]Use before indexing to show likely chunk and embedding volume. Estimate uses extraction previews for safe local sources and conservative limits for sources that should not be crawled during preflight.
/v1/retrieval/source-buckets/{bucket_id}/index/POSTforbidden: trigger; nested: chunks[]trigger is server-managed and must not be supplied. sensitive_source_approved is required when tenant policy requires approval for sensitive source buckets.
/v1/sequences/POSTrequired: name, emails; nested: emails[]Each email step needs body or prompt.
/v1/sequences/enrollments/GEToptional: contact_id, status
/v1/sequences/enrollments/POSTrequired: inbox_id, recipient; one of: sequence_id, emails; nested: recipient, emails[]Provide sequence_id or inline emails. Recipient must identify exactly one contact_id or email.
/v1/sequences/{sequence_id}/PATCHnested: emails[]When emails are provided, each email step needs body or prompt.
/v1/work-routes/POSTrequired: creator_inbox_id, title, request_type, routing_mode, completion, recipients, due_at; 2 conditional rules; nested: recipients[], response_detection.phrasesOrdered routes only support completion=required. completion=any and completion=optional require routing_mode=parallel. Each recipient must provide exactly one identifier matching kind. Custom response phrases must include at least one successful action phrase.
PathResourceAuthBuilder contract
/v1/reference/api/apioptionalyes
/v1/tokens/reference/tokensoptionalyes
/v1/events/reference/eventsoptionalyes
/v1/events/types/event_typesoptionalno
/v1/workflows/reference/workflowsrequiredyes
/v1/workflows/triggers/workflow_triggersrequiredno
/v1/workflows/conditions/workflow_conditionsrequiredno
/v1/workflows/write-targets/workflow_write_targetsrequiredno
/v1/workflows/write-templates/workflow_write_templatesrequiredyes
/v1/sequences/reference/sequencesrequiredyes
/v1/work-routes/reference/work_routesrequiredyes
/v1/alerts/reference/alertsnoneyes
/v1/files/reference/filesnoneyes
/v1/labels/reference/labelsnoneyes
/v1/messages/ticketing/reference/message_ticketingnoneyes
/v1/enrichment/reference/enrichmentnoneyes
/v1/enrichment/models/enrichment_modelsnoneno
/v1/enrichment/features/enrichment_featuresnoneno
/v1/retrieval/reference/retrievalnoneyes
/v1/domains/reference/domainsrequiredyes
/v1/domains/dns-record-types/domain_dns_record_typesrequiredno
/v1/sender-rules/reference/sender_rulesrequiredyes
/v1/sender-rules/modes/sender_rule_modesrequiredno
/v1/forwarding/reference/forwardingrequiredyes
/v1/forwarding/condition-types/forwarding_condition_typesrequiredno
Use the listed reference endpoints for complete option arrays and builder contracts. See Schema Reference for request and response object fields.
Errors

All errors return a consistent JSON body with a machine-readable code and human-readable message.

Error Shape
{ "detail": "Validation failed.", "message": "name: This field is required.", "code": "validation_error", "request_id": "req_...", "errors": { "name": ["This field is required."] } }

Validation errors include errors with field-level details. Other errors may omit it and use detail plus message.

StatusCodeDescription
400validation_errorMissing or invalid parameters
401authentication_errorInvalid or missing credentials
403permission_deniedCaller lacks permission for this action
404not_foundResource does not exist
409conflictRequest conflicts with existing state
429rate_limitedToo many requests
502/503service_unavailableA required upstream service is temporarily unavailable
500server_errorUnexpected server error
Rate Limits

Gent uses adaptive request throttles to protect delivery quality and prevent abuse. Limits are applied by route category and caller identity, such as IP address, session, token, inbox, tenant, demo session, or webhook target. Exact thresholds may change as traffic patterns change.

CategoryHow to handle it
AuthenticationUse backoff after failed login, signup, password, or MFA attempts.
Message readsUse pagination, cursors, and conditional refresh instead of polling aggressively.
Message sendsRequest throttles are separate from tenant and inbox send caps.
Uploads and downloadsRetry with backoff and avoid parallel retries for the same object.
Tracking and public linksHigh-volume public routes are isolated from authenticated API limits.
Webhooks and demoDemo and webhook test delivery limits are intentionally stricter than production tenant traffic.

When a request is throttled, the API returns 429 rate_limited. If the reset time is known, the response includes Retry-After. Clients should use exponential backoff with jitter and should not retry unsafe write requests without an Idempotency-Key.

Idempotency

Any POST request can be made idempotent by supplying an Idempotency-Key header. When the same key is sent again within 24 hours the original response is returned immediately — no action is retried, no side effects re-triggered.

The key is scoped to the authenticated caller (inbox for token auth, tenant for session auth), so two different callers using the same string never collide.

Idempotent endpoints: POST /v1/messages/, POST /v1/contacts/, POST /v1/calendar/events/, POST /v1/inboxes/, POST /v1/tokens/.
Request
POST /v1/messages/ Idempotency-Key: send-invoice-2024-06-01 Authorization: Bearer gent_<token> { "to": "client@example.com", "subject": "Invoice", ... }
Replay Response (key already used)
HTTP/1.1 201 Created X-Idempotency-Replayed: true { "id": "msg_abc123", ... }
StatusMeaning
201 + X-Idempotency-Replayed: trueKey seen before — original response replayed
409A request with this key is currently in-flight (concurrent duplicate)

Use a deterministic key derived from your operation — for example a UUID generated once per business action and stored on your side. Keys expire after 24 hours.