SX Customers

Customer Emails

Configure the IMAP mailbox the platform reads withdrawal verification codes from, then verify, enable, or disable that email poller.

For withdrawals funded from your treasury, the platform reads the verification code from your mailbox and forwards it to your webhook. Point the platform at an IMAP inbox here, verify the connection, and turn polling on or off.

Authentication: every endpoint on this page takes your dashboard session token as a Bearer token. The session must own the {customer_id} in the path; any other id answers 404 CUSTOMER_NOT_FOUND.

Authorization: Bearer YOUR_SESSION_TOKEN

One poller per customer

The email poller is a singleton: it has no id of its own and lives at /v1/customers/{customer_id}/email/poller. Read it with GET, create or replace it with PUT, change part of it with PATCH, and run verify, enable, or disable as POST actions. Each call returns the poller. The mailbox user is always your owner credential's email, and the IMAP password is never returned—imap.has_password tells you one is stored.

Responses use the standard envelope and error format. POST and PATCH accept an optional x-idempotency-key header.

Poller Fields

FieldTypeDescription
objectstringAlways email_poller.
customer_idstringOwning customer.
emailstring | nullMailbox address (your owner credential's email).
imapobject | null{host, port, is_tls_enabled, user, has_password}, or null when no mailbox is set.
descriptionstring | nullYour label for the mailbox.
is_enabledbooleanWhether polling is on.
connection_statusstringUNVERIFIED, CONNECTED, or FAILED. Any change to imap resets it to UNVERIFIED.
status_reasonobject | null{code, message} when connection_status is FAILED: IMAP_AUTHENTICATION_FAILED, IMAP_TIMEOUT, or IMAP_UNREACHABLE.
last_verified_atstring | nullLast verify call.
last_polled_at / last_successful_poll_at / last_error_atstring | nullPoll loop timestamps.
last_errorobject | nullLast poll failure as {code, message}, same codes as status_reason.
total_emails_processed / total_pins_extractedintegerRunning counters.
created_at / updated_atstringUTC timestamps without milliseconds.
created_by / updated_bystring | nullActor that created or last changed the poller.

Create or Replace Email Poller

PUThttps://api.sandbox.sovera.io/sovx/v1/customers/:customer_id/email/poller

Set the mailbox. The first call creates the poller enabled and answers 201 Created; later calls replace the mailbox settings, keep the current is_enabled, and answer 200 OK. The connection status resets to UNVERIFIED—run Verify next.

POST /v1/customers/{customer_id}/email/poller still works as a deprecated alias of this call.

Request Body

FieldTypeRequiredDescription
imapobjectYesMailbox settings (below).
descriptionstring | nullNoLabel for the mailbox (max 255).

imap object

FieldTypeRequiredDescription
hoststringYesIMAP server host, e.g. imap.gmail.com.
portintegerYesIMAP port, e.g. 993.
is_tls_enabledbooleanNoUse TLS (default true). The server certificate is always verified. tls is a deprecated alias.
passwordstringYesMailbox or app password. Write-only.
{
  "imap": {
    "host": "imap.gmail.com",
    "port": 993,
    "is_tls_enabled": true,
    "password": "YOUR_APP_PASSWORD"
  },
  "description": "Main trading account"
}

Success Response (201 Created, or 200 OK on replace):

{
  "success": true,
  "data": {
    "object": "email_poller",
    "customer_id": "corp-redundant-firewall",
    "email": "[email protected]",
    "imap": {
      "host": "imap.gmail.com",
      "port": 993,
      "is_tls_enabled": true,
      "user": "[email protected]",
      "has_password": true
    },
    "description": "Main trading account",
    "is_enabled": true,
    "connection_status": "UNVERIFIED",
    "status_reason": null,
    "last_verified_at": null,
    "last_polled_at": null,
    "last_successful_poll_at": null,
    "last_error_at": null,
    "last_error": null,
    "total_emails_processed": 0,
    "total_pins_extracted": 0,
    "created_at": "2026-10-12T14:10:00Z",
    "updated_at": "2026-10-12T14:10:00Z",
    "created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
    "updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
  },
  "meta": {
    "timestamp": "2026-10-12T14:10:00Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
curl -X PUT "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
  -d '{
    "imap": { "host": "imap.gmail.com", "port": 993, "is_tls_enabled": true, "password": "YOUR_APP_PASSWORD" },
    "description": "Main trading account"
  }'

Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND not owned by the session, or deleted · 404 OWNER_CREDENTIAL_NOT_FOUND generate credentials first · 422 VALIDATION_ERROR invalid or unknown field (email isn't accepted).


Get Email Poller

GEThttps://api.sandbox.sovera.io/sovx/v1/customers/:customer_id/email/poller

Return the poller with its connection status and counters.

GET https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller

Success Response (200 OK):

{
  "success": true,
  "data": {
    "object": "email_poller",
    "customer_id": "corp-redundant-firewall",
    "email": "[email protected]",
    "imap": {
      "host": "imap.gmail.com",
      "port": 993,
      "is_tls_enabled": true,
      "user": "[email protected]",
      "has_password": true
    },
    "description": "Main trading account",
    "is_enabled": true,
    "connection_status": "CONNECTED",
    "status_reason": null,
    "last_verified_at": "2026-10-12T14:12:30Z",
    "last_polled_at": "2026-10-12T14:29:00Z",
    "last_successful_poll_at": "2026-10-12T14:29:00Z",
    "last_error_at": null,
    "last_error": null,
    "total_emails_processed": 42,
    "total_pins_extracted": 41,
    "created_at": "2026-10-12T14:10:00Z",
    "updated_at": "2026-10-12T14:29:00Z",
    "created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
    "updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
  },
  "meta": {
    "timestamp": "2026-10-12T14:30:02Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
curl -X GET "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller" \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN"

Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND not owned by the session, or deleted · 404 EMAIL_POLLER_NOT_FOUND no poller configured.


Update Email Poller

PATCHhttps://api.sandbox.sovera.io/sovx/v1/customers/:customer_id/email/poller

Change part of the poller. imap fields merge into the stored settings, so rotating the password doesn't require resending host and port. "imap": null clears the mailbox and stops polling. Any imap change resets connection_status to UNVERIFIED and restarts the poller. To turn polling on or off, use the enable and disable actions.

Request Body

FieldTypeRequiredDescription
imapobject | nullNoAny of host, port, is_tls_enabled, password. null clears the mailbox.
descriptionstring | nullNoLabel for the mailbox (max 255).
{
  "description": "Updated trading mailbox",
  "imap": { "password": "YOUR_NEW_APP_PASSWORD" }
}

Success Response (200 OK):

{
  "success": true,
  "data": {
    "object": "email_poller",
    "customer_id": "corp-redundant-firewall",
    "email": "[email protected]",
    "imap": {
      "host": "imap.gmail.com",
      "port": 993,
      "is_tls_enabled": true,
      "user": "[email protected]",
      "has_password": true
    },
    "description": "Updated trading mailbox",
    "is_enabled": true,
    "connection_status": "UNVERIFIED",
    "status_reason": null,
    "last_verified_at": "2026-10-12T14:12:30Z",
    "last_polled_at": "2026-10-12T14:29:00Z",
    "last_successful_poll_at": "2026-10-12T14:29:00Z",
    "last_error_at": null,
    "last_error": null,
    "total_emails_processed": 42,
    "total_pins_extracted": 41,
    "created_at": "2026-10-12T14:10:00Z",
    "updated_at": "2026-10-12T15:00:00Z",
    "created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
    "updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
  },
  "meta": {
    "timestamp": "2026-10-12T15:00:00Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
curl -X PATCH "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN" \
  -d '{ "description": "Updated trading mailbox", "imap": { "password": "YOUR_NEW_APP_PASSWORD" } }'

Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 404 OWNER_CREDENTIAL_NOT_FOUND · 422 VALIDATION_ERROR invalid field, or imap without host, port, and password when no mailbox is stored (source.pointer: /imap).


Verify Mailbox Connection

POSThttps://api.sandbox.sovera.io/sovx/v1/customers/:customer_id/email/poller/verify

Test the stored mailbox and record the outcome on the poller. The call answers 200 OK whether the mailbox connects or not: read connection_status and status_reason. Send no body.

POST https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/verify

Success Response (200 OK), mailbox rejected the password:

{
  "success": true,
  "data": {
    "object": "email_poller",
    "customer_id": "corp-redundant-firewall",
    "email": "[email protected]",
    "imap": {
      "host": "imap.gmail.com",
      "port": 993,
      "is_tls_enabled": true,
      "user": "[email protected]",
      "has_password": true
    },
    "description": "Main trading account",
    "is_enabled": true,
    "connection_status": "FAILED",
    "status_reason": {
      "code": "IMAP_AUTHENTICATION_FAILED",
      "message": "The mailbox rejected the stored credentials."
    },
    "last_verified_at": "2026-10-12T15:02:11Z",
    "last_polled_at": "2026-10-12T14:29:00Z",
    "last_successful_poll_at": "2026-10-12T14:29:00Z",
    "last_error_at": null,
    "last_error": null,
    "total_emails_processed": 42,
    "total_pins_extracted": 41,
    "created_at": "2026-10-12T14:10:00Z",
    "updated_at": "2026-10-12T15:02:11Z",
    "created_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd",
    "updated_by": "1d8cb40d-8740-5a6b-94c3-7406e08ab4fd"
  },
  "meta": {
    "timestamp": "2026-10-12T15:02:11Z",
    "version": "v1",
    "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
curl -X POST "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/verify" \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN"

Errors: 401 UNAUTHORIZED missing or invalid session · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 422 INVALID_STATE_TRANSITION no mailbox credentials stored.


Enable Email Poller

POSThttps://api.sandbox.sovera.io/sovx/v1/customers/:customer_id/email/poller/enable

Turn polling on and return the poller with is_enabled: true. Send no body.

POST https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/enable

Success Response (200 OK): the poller with "is_enabled": true.

curl -X POST "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/enable" \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN"

Errors: 401 UNAUTHORIZED · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 422 INVALID_STATE_TRANSITION already enabled, or no mailbox credentials stored.


Disable Email Poller

POSThttps://api.sandbox.sovera.io/sovx/v1/customers/:customer_id/email/poller/disable

Turn polling off and return the poller with is_enabled: false. The mailbox settings stay, so you can enable it again later. Send no body.

POST https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/disable

Success Response (200 OK): the poller with "is_enabled": false.

curl -X POST "https://api.sandbox.sovera.io/sovx/v1/customers/corp-redundant-firewall/email/poller/disable" \
  -H "Authorization: Bearer YOUR_SESSION_TOKEN"

Errors: 401 UNAUTHORIZED · 404 CUSTOMER_NOT_FOUND · 404 EMAIL_POLLER_NOT_FOUND · 422 INVALID_STATE_TRANSITION already disabled.


Deprecated routes

These still answer for one release. Move to the routes above.

DeprecatedUse insteadBehavior today
POST /v1/customers/{customer_id}/email/pollerPUT …/email/pollerSame create-or-replace.
DELETE /v1/customers/{customer_id}/email/pollerPOST …/email/poller/disableDisables the poller (no physical delete) and answers 204.
POST /v1/customers/{customer_id}/email/poller/{poller_id}/enablePOST …/email/poller/enable{poller_id} must equal your customer_id; any other value is 404 EMAIL_POLLER_NOT_FOUND.
POST /v1/customers/{customer_id}/email/poller/{poller_id}/disablePOST …/email/poller/disableSame rule as above.

On this page