Skip to content

User API Documentation

User management endpoints for creating, authenticating, and managing user accounts and user groups.

Create a User

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.create
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
RelUserGroupIDIntegerYesUser group ID to assign the user to
EmailAddressStringYesUser's email address
UsernameStringYesUsername for login (must be unique)
PasswordStringYesUser's password (will be hashed)
TimeZoneStringYesUser's timezone
LanguageStringYesUser's language code (e.g., 'en')
CompanyNameStringConditionalCompany name (required if FirstName not provided)
FirstNameStringConditionalFirst name (required if CompanyName not provided)
LastNameStringNoLast name
WebsiteStringNoWebsite URL
OtherEmailAddressesStringNoAdditional email addresses
StreetStringNoStreet address
CityStringNoCity
StateStringNoState/Province
ZipStringNoZIP/Postal code
CountryStringNoCountry
PhoneStringNoPhone number
PhoneVerifiedBooleanNoPhone verification status
FaxStringNoFax number
PreviewMyEmailAccountStringNoPreview email account
PreviewMyEmailAPIKeyStringNoPreview email API key
ForwardToFriendHeaderStringNoForward to friend header text
ForwardToFriendFooterStringNoForward to friend footer text
AccountStatusStringNoAccount status ('Enabled' or 'Disabled', default: 'Enabled')
AvailableCreditsIntegerNoInitial credits (default: 0)
ReputationLevelStringNoReputation level ('Trusted' or 'Untrusted', default: 'Trusted')
SignUpIPAddressStringNoUser signup IP address
SSOIDStringNoSingle sign-on ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.create",
    "SessionID": "your-session-id",
    "RelUserGroupID": 1,
    "EmailAddress": "user@example.com",
    "Username": "newuser",
    "Password": "securepassword",
    "TimeZone": "America/New_York",
    "Language": "en",
    "FirstName": "John",
    "LastName": "Doe"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserID": 123
}
json
{
  "Success": false,
  "ErrorCode": [1, 2, 3]
}
txt
0: Success
1: Missing RelUserGroupID parameter
2: Missing EmailAddress parameter
3: Missing Username parameter
4: Missing Password parameter
6: Missing CompanyName or FirstName parameter
8: Missing TimeZone parameter
9: Missing Language parameter
10: Invalid email address format
11: Invalid user group ID
12: Username already exists
13: Email address already exists
14: Invalid language code
15: Invalid reputation level (must be 'Trusted' or 'Untrusted')
16: Maximum number of user accounts exceeded

User Login

POST /api.php

API Usage Notes

  • No authentication required
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.login
SessionIDStringNoSession ID obtained from login
APIKeyStringConditionalAPI key for authentication (alternative to username/password)
UsernameStringConditionalUsername or email address (required if not using APIKey)
PasswordStringConditionalUser's password (required if not using APIKey)
PasswordEncryptedBooleanNoSet to true if password is already MD5 hashed
TFACodeStringConditionalTwo-factor authentication code (required if 2FA is enabled)
TFARecoveryCodeStringNoTwo-factor authentication recovery code
Disable2FABooleanNoSkip 2FA verification for this login. Honored only when Disable2FAToken is also supplied and valid (see note below).
Disable2FATokenStringConditionalServer-derived token that authorizes Disable2FA. Required for Disable2FA to take effect.

Behavior change (v5.9.3, #2333)

Disable2FA alone no longer skips two-factor authentication. In earlier versions any client could send Disable2FA=true and bypass 2FA — a security hole. It is now honored only when accompanied by a matching Disable2FAToken:

Disable2FAToken = HMAC_SHA256("user.login.disable2fa", SCRTY_SALT)   // lowercase hex

SCRTY_SALT is a server-side secret from .oempro_env, so only a trusted integration that has access to it (for example a custom SSO bridge running on the same host) can compute the token; an ordinary API caller cannot forge it. If SCRTY_SALT is empty the token can never validate and Disable2FA is ignored. When the token is absent or invalid, the request falls through to normal 2FA handling — supply TFACode (or TFARecoveryCode). Callers authenticating with APIKey are unaffected.

bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.login",
    "Username": "john.doe",
    "Password": "securepassword"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "SessionID": "abc123def456",
  "UserInfo": {
    "UserID": 123,
    "Username": "john.doe",
    "EmailAddress": "john@example.com",
    "FirstName": "John",
    "LastName": "Doe",
    "AccountStatus": "Enabled"
  }
}
json
{
  "Success": false,
  "ErrorCode": [3],
  "ErrorText": ["Invalid login information"]
}
txt
0: Success
1: Missing Username parameter
2: Missing Password parameter
3: Invalid login information
6: Invalid 2FA code or recovery code

Get Current User Information

POST /api.php

API Usage Notes

  • Authentication required: User API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.current
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.current",
    "SessionID": "your-session-id"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserInfo": {
    "UserID": 123,
    "RelUserGroupID": 1,
    "EmailAddress": "user@example.com",
    "Username": "john.doe",
    "ReputationLevel": "Trusted",
    "UserSince": "2024-01-15 10:30:00",
    "FirstName": "John",
    "LastName": "Doe",
    "CompanyName": "",
    "Website": "",
    "Street": "",
    "Street2": "",
    "City": "",
    "State": "",
    "Zip": "",
    "Country": "",
    "VAT": "",
    "Phone": "",
    "PhoneVerified": 0,
    "Fax": "",
    "TimeZone": "America/New_York",
    "LastActivityDateTime": "2024-12-28 10:00:00",
    "AccountStatus": "Enabled",
    "AvailableCredits": 1000,
    "2FA_Enabled": "No",
    "2FA_RecoveryKey": "",
    "SSOID": "",
    "GroupInfo": {
      "UserGroupID": 1,
      "GroupName": "Standard Users",
      "GroupPlanName": "Standard Plan",
      "DefaultSenderDomain": "example.com",
      "Permissions": "User.Update,Campaign.Create,Campaign.Update,Campaigns.Get,List.Create",
      "ForceUnsubscriptionLink": "Disabled",
      "ForceRejectOptLink": "Disabled",
      "ForceOptInList": "Disabled",
      "LimitSubscribers": "0",
      "LimitLists": "0",
      "LimitCampaignSendPerPeriod": "0",
      "LimitCampaignSendPeriod": "Monthly",
      "LimitEmailSendPerPeriod": "0",
      "LimitEmailSendPerDay": "0",
      "LimitEmailSendPeriod": "Monthly",
      "LimitEmailSendLifetime": "0",
      "LimitEmailGatewaySenderDomains": "0",
      "SenderDomainManagement": "Enabled",
      "EnableSenderInfo": "Enabled",
      "ForcedSenderInfo": "Disabled",
      "DefaultSenderDomainActivate": "Disabled"
    },
    "MFA_QRCode": "otpauth://totp/...",
    "MFA_SecretKey": "ABCDEF123456",
    "SubscriptionID": false
  },
  "Usage": {
    "EmailGateway_TotalSentThisMonth": 500,
    "EmailGateway_TotalSentAllTime": 5000,
    "Limit_Monthly": 10000,
    "Limit_Lifetime": 100000
  },
  "SendRateLimits": {
    "EmailGateway": {
      "RateLimits": {},
      "SendRates": {}
    },
    "DefaultSenderDomain": {
      "MonthlyLimit": 5000,
      "SendRates": 500,
      "RemainingMonthlyQuota": 4500
    }
  }
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Authentication required

UserInfo.GroupInfo

An explicit, user-safe projection of the authenticated user's user group. It intentionally exposes only capability flags and plan quotas the user is already subject to.

Not exposed here — by design

Delivery-server records and their ConnectionParams, the group's SendMethod* SMTP settings (including SendMethodSMTPPassword), all Payment* values, and the ThresholdImport / ThresholdEmailSend abuse-moderation thresholds are not returned by user.current. They remain reachable only through the admin-authenticated user.get.

KeyTypeDescription
UserGroupIDstring (numeric)The group's ID.
GroupNamestringAdministrative name of the group.
GroupPlanNamestringHuman-readable label for the group's subscription plan. Empty string when the group has no plan.
DefaultSenderDomainstring | nullThe default sender domain applied to this account, or null when none is configured. A user-level override takes precedence over the group value.
PermissionsstringThe user's granted permissions. See Permissions format below.
ForceUnsubscriptionLink"Enabled" | "Disabled"When Enabled, campaign content must contain an unsubscribe link; content that does not is rejected at save time.
ForceRejectOptLink"Enabled" | "Disabled"When Enabled, an opt-out/reject link is required in campaign content.
ForceOptInList"Enabled" | "Disabled"When Enabled, newly created lists are forced to double opt-in.
LimitSubscribersstring (numeric)Maximum subscribers on the account. 0 means unlimited.
LimitListsstring (numeric)Maximum subscriber lists. 0 means unlimited.
LimitCampaignSendPerPeriodstring (numeric)Maximum campaign sends per reset period. 0 means unlimited.
LimitCampaignSendPeriod"Monthly"Reset period for LimitCampaignSendPerPeriod.
LimitEmailSendPerPeriodstring (numeric)Maximum emails per reset period. 0 means unlimited. Same value as Usage.Limit_Monthly.
LimitEmailSendPerDaystring (numeric)Maximum emails per day. 0 means unlimited.
LimitEmailSendPeriod"Monthly"Reset period for LimitEmailSendPerPeriod.
LimitEmailSendLifetimestring (numeric)Lifetime email cap. 0 means unlimited. Same value as Usage.Limit_Lifetime.
LimitEmailGatewaySenderDomainsstring (numeric)Maximum Email Gateway sender domains. 0 means unlimited.
SenderDomainManagement"Enabled" | "Disabled"Whether the account may manage its own sender domains. Gates the sender-domain UI and sender-domain-scoped sends.
EnableSenderInfo"Enabled" | "Disabled"Whether sender-info (physical address block) fields are shown.
ForcedSenderInfo"Enabled" | "Disabled"Whether sender-info is mandatory. Only meaningful when EnableSenderInfo is Enabled.
DefaultSenderDomainActivate"Enabled" | "Disabled"Whether the default sender domain is actively applied to this account's sends.

Permissions format

Permissions is not an array. It is either:

  • a comma-separated string of permission names, e.g. "Campaign.Create,Campaign.Update,Campaigns.Get,List.Create" — no spaces after the commas; or
  • the single-character sentinel "*", meaning all permissions are granted.

A client-side permission check must handle the sentinel explicitly:

js
function hasPermission(groupInfo, permission) {
  if (groupInfo.Permissions === '*') return true;             // full privileges
  return groupInfo.Permissions.split(',').indexOf(permission) !== -1;
}

The "*" sentinel is emitted when an administrator is impersonating the user with Full privileges. Treating it as a literal permission name rather than a wildcard will incorrectly deny every feature for that session.

Defaults for older groups

SenderDomainManagement, EnableSenderInfo, ForcedSenderInfo and DefaultSenderDomainActivate come from the group's options blob, and groups created before those options existed do not carry them. The endpoint always returns one of the two defined string values and never null — an absent option is reported as "Disabled", which matches how the application itself evaluates it.

Duplicated quota values

LimitEmailSendPerPeriod and LimitEmailSendLifetime also appear in the response as Usage.Limit_Monthly and Usage.Limit_Lifetime. Both names carry the same value. The Usage.* names are retained unchanged for backwards compatibility; GroupInfo repeats them so the group's quota set is complete in one place.

New in v5.9.3

Everything below DefaultSenderDomain in the table above was added in v5.9.3. Previously these were readable only through the admin-authenticated user.get, which forced frontend integrations to hold an admin API key purely to render a user's own UI. The change is purely additive — the four pre-existing GroupInfo keys are unchanged in name and value.

Get User Information

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserIDIntegerConditionalUser ID (required if EmailAddress not provided)
EmailAddressStringConditionalEmail address (required if UserID not provided)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.get",
    "SessionID": "your-session-id",
    "UserID": 123
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserInformation": {
    "UserID": 123,
    "RelUserGroupID": 1,
    "EmailAddress": "user@example.com",
    "Username": "john.doe",
    "FirstName": "John",
    "LastName": "Doe",
    "GroupInformation": {}
  },
  "LimitUtilization": {
    "Subscribers": {"Used": 100, "Limit": 1000},
    "Lists": {"Used": 5, "Limit": 10}
  }
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing UserID or EmailAddress parameter
3: User not found

Update User

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key or User API Key
  • Required permissions: User.Update
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.update
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserIDIntegerYesUser ID to update
EmailAddressStringNoNew email address
UsernameStringNoNew username
PasswordStringNoNew password (will be hashed)
FirstNameStringNoFirst name
LastNameStringNoLast name
CompanyNameStringNoCompany name
WebsiteStringNoWebsite URL
OtherEmailAddressesStringNoAdditional email addresses
StreetStringNoStreet address
Street2StringNoStreet address line 2
CityStringNoCity
StateStringNoState/Province
ZipStringNoZIP/Postal code
CountryStringNoCountry
VATStringNoVAT number
PhoneStringNoPhone number
PhoneVerifiedBooleanNoPhone verification status
FaxStringNoFax number
TimeZoneStringNoTimezone
LanguageStringNoLanguage code
AccountStatusStringNoAccount status (admin only)
AvailableCreditsIntegerNoAvailable credits (admin only)
RelUserGroupIDIntegerNoUser group ID (admin only)
ReputationLevelStringNoReputation level (admin only)
RateLimitsStringNoJSON string of rate limits, normalised before storage to {"EmailGateway":{Minute,Hour,Day,Week,Month,Year},"SMS":{…}} with integer values (-1 = unlimited; an omitted interval becomes -1). Omit to keep the existing row's value; pass an empty string to clear it, which means "no user-level override — inherit the user group's default rate limits" (an empty value is stored as-is, never normalised into an all-unlimited document).
CustomEmailHeadersStringNoCustom email headers. Omit to keep the existing row's value; pass an empty string to clear it.
WhiteListedEmailAddressesStringNoWhitelisted email addresses. Omit to keep the existing row's value; pass an empty string to clear it.
Enable2FAStringNoSet to 'true' to enable 2FA
2FACodeStringConditional2FA code (required when enabling 2FA)
Cancel2FAStringNoSet to 'true' to disable 2FA
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.update",
    "SessionID": "your-session-id",
    "UserID": 123,
    "FirstName": "Jane",
    "LastName": "Smith"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": ""
}
json
{
  "Success": false,
  "ErrorCode": 2
}
txt
0: Success
1: Missing UserID parameter
2: User can only update their own account
3: PreviewMyEmail connection error
4: Invalid 2FA code
5: User not found
6: Email address or username already exists

Get Monthly User Snapshot

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key or User API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.snapshot
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserIDIntegerConditionalUser ID (required if not logged in and EmailAddress not provided)
EmailAddressStringConditionalEmail address (alternative to UserID)
MonthStringYesMonth in YYYY-MM format (e.g., '2024-12')
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.snapshot",
    "SessionID": "your-session-id",
    "Month": "2024-12"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "CampaignsSent": 15,
  "EmailsSent": 5000,
  "Subscribers": 1500,
  "Lists": 8
}
json
{
  "Success": false,
  "ErrorCode": [3]
}
txt
0: Success
1: Missing EmailAddress parameter
2: Missing UserID parameter
3: Missing Month parameter
4: Invalid month format
5: User not found

Get User Statistics

POST /api/v1/user.stats

API Usage Notes

  • Authentication required: User API Key
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.stats
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
StartDateStringYesStart date in YYYY-MM-DD format
EndDateStringYesEnd date in YYYY-MM-DD format
AggregationStringYesAggregation type: 'daily', 'weekly', or 'monthly'
bash
curl -X POST https://example.com/api/v1/user.stats \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.stats",
    "SessionID": "your-session-id",
    "StartDate": "2024-01-01",
    "EndDate": "2024-12-31",
    "Aggregation": "monthly"
  }'
json
{
  "NumberOfLists": 12,
  "TotalActiveLists": 9,
  "TotalActiveSubscribers": 15234,
  "TotalSegments": 28,
  "AvgOpenRate30dWeighted": 0.2418,
  "AvgClickRate30dWeighted": 0.0327,
  "AvgCTOR30dWeighted": 0.1352,
  "AvgForwardRate30dWeighted": 0.0014,
  "AvgBrowserViewRate30dWeighted": 0.0119,
  "New Subscribers": {
    "2024-01-01": 12,
    "2024-01-02": 18
  },
  "Sent Emails": {
    "2024-01-01": 250,
    "2024-01-02": 300
  },
  "Opens": {
    "2024-01-01": 80,
    "2024-01-02": 95
  }
}
json
{
  "Errors": [
    {"Code": 1, "Message": "Missing StartDate parameter"}
  ]
}
txt
1: Missing StartDate parameter
2: Missing EndDate parameter
3: Missing Aggregation parameter
4: Invalid Aggregation value (must be 'daily', 'weekly', or 'monthly')
6: Statistics retrieval error

Top-level overall fields

The response is the merge of two payloads: a stat-strip header (the overall fields below) plus the time-series breakdown for each metric (keyed by metric title — New Subscribers, Sent Emails, Opens, etc).

FieldTypeDescription
NumberOfListsIntegerTotal lists owned by the user, including archived. Back-compat with pre-v5.9.x consumers.
TotalActiveListsIntegerLists with ArchivedAt IS NULL.
TotalActiveSubscribersIntegerSum of ActiveSubscriberCount (denormalized) across non-archived lists. Defines "active" as Subscribed AND BounceType != 'Hard', matching the count lists.get displays.
TotalSegmentsIntegerSum of SegmentCount (denormalized) across non-archived lists.
AvgOpenRate30dWeightedFloat | nullSubscriber-weighted average 30-day open rate across non-archived lists, computed as Σ(ActiveSubscriberCount × UniqueOpens) / Σ(ActiveSubscriberCount × TotalSent). null when no list in the user's active set had any sends in the 30-day window.
AvgClickRate30dWeightedFloat | nullSubscriber-weighted average 30-day click rate. Same shape and weighting basis as AvgOpenRate30dWeighted but with UniqueClicks in the numerator. null when no sends. (Added in v5.9.1, issue #1960.)
AvgCTOR30dWeightedFloat | nullSubscriber-weighted average 30-day Click-To-Open Rate, computed as Σ(ActiveSubscriberCount × UniqueClicks) / Σ(ActiveSubscriberCount × UniqueOpens). Different denominator basis from the other rates: lists with zero opens are skipped (not zero-weighted) so they don't drag the average to zero. null when no opens. (Added in v5.9.1, issue #1960.)
AvgForwardRate30dWeightedFloat | nullSubscriber-weighted average 30-day forward rate. Same shape as AvgOpenRate30dWeighted with UniqueForwards in the numerator. (Added in v5.9.1, issue #1960.)
AvgBrowserViewRate30dWeightedFloat | nullSubscriber-weighted average 30-day "view in browser" rate. Same shape as AvgOpenRate30dWeighted with UniqueBrowserViews in the numerator. (Added in v5.9.1, issue #1960.)

Migration note (v5.9.x): TotalActiveSubscribers previously summed across all lists (including archived) using a slightly stricter criterion (BounceType = 'Not Bounced', excluding soft bounces). Both changes were intentional: the new value (a) excludes archived lists — matching the lists.get browse display — and (b) uses BounceType != 'Hard' to align with Subscribers::GetActiveTotal. For a user with no archived lists and a clean deliverability footprint the difference is negligible. For users with many archived lists or noticeable soft-bounce volume the value will shift; treat the new figure as the canonical "subscribers I currently market to."

Switch to User Account

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.switch
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserIDIntegerYesUser ID to switch to
PrivilegeTypeStringNoPrivilege type: 'Default' or 'Full' (default: 'Default')
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.switch",
    "SessionID": "your-admin-session-id",
    "UserID": 123,
    "PrivilegeType": "Full"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "SessionID": "new-session-id",
  "UserInfo": {
    "UserID": 123,
    "Username": "john.doe"
  }
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing UserID parameter
2: User not found
3: Invalid privilege type (must be 'Default' or 'Full')

Send Password Reminder

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.passwordremind
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
EmailAddressStringYesUser's email address
CustomResetLinkStringNoBase64 encoded custom reset link template
ReturnParamsBooleanNoSet to true to return reset token and link
AdminAPIKeyStringNoAdmin API key for custom email settings
ResetEmailSubjectStringNoCustom email subject (requires AdminAPIKey)
ResetEmailContentStringNoCustom email content (requires AdminAPIKey)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.passwordremind",
    "SessionID": "your-session-id",
    "EmailAddress": "user@example.com"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "PasswordResetToken": "",
  "PasswordResetLink": ""
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing EmailAddress parameter
2: Invalid email address format
3: Email address not found (Note: For security, this returns success even if not found)

Reset User Password

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.passwordreset
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserIDStringYesPassword reset token (from passwordremind)
AdminAPIKeyStringNoAdmin API key for custom password
NewPasswordStringNoCustom new password (requires AdminAPIKey)
DontSendNewPasswordEmailStringNoSet to 'true' to skip email (requires AdminAPIKey)
ShowPasswordBooleanNoSet to true to include password in email
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.passwordreset",
    "UserID": "reset-token-here"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": "",
  "WoocommerceUserId": false
}
json
{
  "Success": false,
  "ErrorCode": [2]
}
txt
0: Success
1: Missing UserID parameter
2: Invalid reset token or user not found

Create API Key

POST /api/v1/user.apikey

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: User.Update
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.apikey.create
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
NoteStringYesAdministrative note for the API key
BoundIPAddressStringNoIP address to bind the key to
bash
curl -X POST https://example.com/api/v1/user.apikey \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.apikey.create",
    "SessionID": "your-session-id",
    "Note": "Production API key"
  }'
json
{
  "Success": true,
  "APIKeyID": "5",
  "APIKey": {
    "APIKey": "1234-5678-9abc-def0",
    "Note": "Production API key",
    "IPAddress": "",
    "BoundIPAddress": "",
    "CreatedAt": "2026-05-22 20:45:48",
    "LastUsedAt": null,
    "LastUsedIP": null,
    "RequestCount": 0
  }
}
json
{
  "Errors": [
    {"Code": 1, "Message": "Missing administrative note parameter"}
  ]
}
txt
1: Missing administrative note parameter
3: API key create process failed
4: New API key create process has failed

Response Fields (APIKey object):

FieldTypeDescription
APIKeyStringThe generated API key token.
NoteStringAdministrative note supplied at creation.
IPAddressStringDeprecated — kept for backward compatibility. Same value as BoundIPAddress. Prefer BoundIPAddress in new integrations.
BoundIPAddressStringIP address the key is bound to, or "" if unbound.
CreatedAtString (DATETIME)Creation timestamp (UTC, YYYY-MM-DD HH:MM:SS).
LastUsedAtString | nullLast time this key was used to authenticate (always null for a freshly created key).
LastUsedIPString | nullClient IP recorded at last use, null if never used.
RequestCountIntegerLifetime number of authentications with this key (always 0 for a freshly created key).

Counter semantics

RequestCount and LastUsedAt are bumped each time the key is used to start a session via User.Login (with the APIKey parameter). They do not count every individual REST request made within a session — once a client has a SessionID, subsequent calls authenticate with session credentials and do not re-touch the API key. Treat RequestCount as "how many times has this integration checked in?", which is what an operator cares about when answering "is this key still in use anywhere?".

:::

Delete API Key

POST /api/v1/user.apikey.delete

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: User.Update
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.apikey.delete
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
APIKeyIDIntegerYesAPI key ID to delete
bash
curl -X POST https://example.com/api/v1/user.apikey.delete \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.apikey.delete",
    "SessionID": "your-session-id",
    "APIKeyID": 5
  }'
json
{
  "Success": true
}
json
{
  "Errors": [
    {"Code": 1, "Message": "Missing APIKeyID parameter"}
  ]
}
txt
1: Missing APIKeyID parameter
2: API key not found

List API Keys

GET /api/v1/user.apikeys

API Usage Notes

  • Authentication required: User API Key
  • Required permissions: User.Update
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.apikey.list
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
bash
curl -X GET https://example.com/api/v1/user.apikeys \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.apikey.list",
    "SessionID": "your-session-id"
  }'
json
{
  "Success": true,
  "APIKeys": [
    {
      "APIKeyID": 17,
      "APIKey": "7c7c-0a2b-66e2-3738-d373-5362-7ab9-9a68",
      "Note": "Shopify sync",
      "BoundIPAddress": "",
      "CreatedAt": "2026-02-04 09:12:33",
      "LastUsedAt": "2026-04-25 14:07:11",
      "LastUsedIP": "52.14.72.10",
      "RequestCount": 12840
    }
  ]
}
json
{
  "Errors": []
}
txt
No specific error codes for this endpoint

Response Fields (each entry in APIKeys[]):

FieldTypeDescription
APIKeyIDIntegerInternal numeric ID of the key.
APIKeyStringThe API key token.
NoteStringAdministrative note supplied at creation.
BoundIPAddressStringIP address the key is bound to, or "" if unbound.
CreatedAtString (DATETIME)Creation timestamp. Keys that pre-date the usage-tracking migration return the sentinel "1970-01-01 00:00:00" — clients should render this as "Unknown".
LastUsedAtString | nullLast time this key was used to authenticate, or null if the key has never been used.
LastUsedIPString | nullClient IP recorded at last use (IPv4 or IPv6), null if never used.
RequestCountIntegerLifetime number of authentications with this key. See counter semantics on the Create endpoint above.

Get Users List

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: users.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
RecordsPerRequestIntegerNoNumber of records per page (default: 25)
RecordsFromIntegerNoStarting record offset (default: 0)
OrderFieldStringNoField to order by (default: UserID). Must be a plain column identifier — letters, digits and underscores only, starting with a letter or underscore. Pipe-separate for multi-column sort (e.g. AccountStatus|UserID). See the sorting note below.
OrderTypeStringNoOrder direction: 'ASC' or 'DESC' (default: ASC). Pipe-separate to match a multi-column OrderField. See the sorting note below.
RelUserGroupIDMixedNoUser group ID, array of IDs, or special value ('Online', 'Enabled', 'Disabled', 'Trusted', 'Untrusted')
RelUserCategoryIDIntegerNoUser category ID (-1 for uncategorized)
SearchFieldStringNoField to search in
SearchKeywordStringNoSearch keyword
ReturnStatsBooleanNoSet to true to include statistics
IncludeLimitUtilizationBooleanNoSet to true to include limit utilization data

Sorting parameters are format-filtered (v5.9.3)

OrderField and OrderType are validated for shape, not against a list of sortable columns. Each pipe-separated segment of OrderField must be a plain identifier, and each segment of OrderType must be ASC or DESC.

  • A value containing anything else (backticks, quotes, spaces, parentheses or any other metacharacter) is silently discarded. When that happens the pair is reset together — ordering falls back to UserID ASC even if only one of the two parameters was malformed. The response is still HTTP 200 with Success: true and no error code.
  • Because there is no column allow-list, a value that is identifier-shaped but names a column that does not exist (for example OrderField: "Bogus") is passed through to the query and surfaces as a database error — it does not fall back to the default.

Neither case returns a validation error. If results come back in an unexpected order after upgrading, your sort parameter is being rejected silently — check it against the shape rules above.

bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "users.get",
    "SessionID": "your-session-id",
    "RecordsPerRequest": 25,
    "RecordsFrom": 0,
    "OrderField": "UserID",
    "OrderType": "DESC"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Users": [
    {
      "UserID": 123,
      "Username": "john.doe",
      "EmailAddress": "john@example.com",
      "GroupInformation": {}
    }
  ],
  "TotalUsers": 150
}
json
{
  "Success": false,
  "ErrorCode": 0
}
txt
0: Success

Delete Users

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Note: Not available in demo mode

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: users.delete
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UsersStringYesComma-separated list of user IDs to delete
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "users.delete",
    "SessionID": "your-session-id",
    "Users": "123,124,125"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "ErrorText": ""
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing Users parameter

Get Users Status

GET /api/v1/users.status

API Usage Notes

  • Authentication required: Admin API Key
  • Rate limit: 100 requests per 60 seconds
  • Legacy endpoint access via /api.php is also supported

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: users.status
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
DurationIntegerNoNumber of days to look back (1-90, default: 30)
bash
curl -X GET https://example.com/api/v1/users.status \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "users.status",
    "SessionID": "your-session-id",
    "Duration": 30
  }'
json
{
  "Success": true,
  "Users": [
    {
      "UserID": 123,
      "Status": "active",
      "LastActivity": "2024-12-28 10:00:00"
    }
  ],
  "Summary": {
    "ActiveUsers": 50,
    "IdleUsers": 10
  }
}
json
{
  "Success": false,
  "ErrorCode": 1,
  "ErrorText": "Failed to retrieve users status"
}
txt
0: Success
1: Failed to retrieve users status

Create User Group

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: usergroup.create
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
GroupNameStringYesName of the user group
SubscriberAreaLogoutURLStringYesSubscriber area logout URL
LimitSubscribersIntegerYesMaximum number of subscribers
LimitListsIntegerYesMaximum number of lists
LimitCampaignSendPerPeriodIntegerYesCampaign send limit per period
LimitEmailSendPerPeriodIntegerYesEmail send limit per period
LimitEmailSendPerDayIntegerYesEmail send limit per day
RelThemeIDIntegerYesTheme ID
ForceUnsubscriptionLinkStringYes'Enabled' or 'Disabled'
ForceRejectOptLinkStringYes'Enabled' or 'Disabled'
DefaultRateLimitsStringNoJSON-encoded rate limits with SMS and EmailGateway buckets, each containing Minute/Hour/Day/Week/Month/Year integer counts (-1 = unlimited). Posted values are deep-merged over the canonical defaults, so a partial payload (only one bucket, or only some intervals) preserves the missing keys at -1. Omit to store the full all--1 defaults.
CustomEmailHeadersStringNoJSON-encoded SMTP header overrides for users in this group (e.g. {"Add":{"X-Header":"value"},"Remove":["X-Other"]}).
OptionsObjectNoJSON object of per-group options (e.g. TargetDeliveryServerID_Marketing, EmailGatewayDNSTemplate, DefaultSenderDomain, EnableSenderInfo). Pass as an object — the endpoint JSON-encodes it.
SubscriptionPlanStringNoSubscription plan identifier for the group.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "usergroup.create",
    "SessionID": "your-session-id",
    "GroupName": "Premium Users",
    "SubscriberAreaLogoutURL": "https://example.com/logout",
    "LimitSubscribers": 10000,
    "LimitLists": 50,
    "LimitCampaignSendPerPeriod": 100,
    "LimitEmailSendPerPeriod": 50000,
    "LimitEmailSendPerDay": 5000,
    "RelThemeID": 1,
    "ForceUnsubscriptionLink": "Enabled",
    "ForceRejectOptLink": "Enabled"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserGroupID": 5
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing GroupName parameter
2: Missing SubscriberAreaLogoutURL parameter
5: Missing LimitSubscribers parameter
6: Missing LimitLists parameter
7: Missing LimitCampaignSendPerPeriod parameter
8: Missing RelThemeID parameter
17: Missing ForceUnsubscriptionLink parameter
18: Missing ForceRejectOptLink parameter
19: Invalid theme ID
20: Missing LimitEmailSendPerPeriod parameter
22: Invalid send method
23: Invalid SMTP secure setting
24: Invalid SMTP auth setting
25: Email settings test failed

Update User Group

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: usergroup.update
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserGroupIDIntegerYesUser group ID to update
GroupNameStringYesName of the user group
SubscriberAreaLogoutURLStringYesSubscriber area logout URL
LimitSubscribersIntegerYesMaximum number of subscribers
LimitListsIntegerYesMaximum number of lists
LimitCampaignSendPerPeriodIntegerYesCampaign send limit per period
LimitEmailSendPerPeriodIntegerYesEmail send limit per period
LimitEmailSendPerDayIntegerYesEmail send limit per day
RelThemeIDIntegerYesTheme ID
ForceUnsubscriptionLinkStringYes'Enabled' or 'Disabled'
ForceRejectOptLinkStringYes'Enabled' or 'Disabled'
DefaultRateLimitsStringNoJSON-encoded rate limits with SMS and EmailGateway buckets, each containing Minute/Hour/Day/Week/Month/Year integer counts (-1 = unlimited). Posted values are deep-merged over the canonical defaults, so a partial payload (only one bucket, or only some intervals) preserves the missing keys at -1. Omit the field entirely to keep the existing row's value.
CustomEmailHeadersStringNoJSON-encoded SMTP header overrides for users in this group (e.g. {"Add":{"X-Header":"value"},"Remove":["X-Other"]}). Omit to keep the existing row's value.
OptionsObjectNoJSON object of per-group options (e.g. TargetDeliveryServerID_Marketing, EmailGatewayDNSTemplate, DefaultSenderDomain, EnableSenderInfo). Pass as an object — the endpoint JSON-encodes it. Omit to keep the existing row's value.
SubscriptionPlanStringNoSubscription plan identifier for the group. Omit to keep the existing row's value.
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "usergroup.update",
    "SessionID": "your-session-id",
    "UserGroupID": 5,
    "GroupName": "Premium Plus Users",
    "SubscriberAreaLogoutURL": "https://example.com/logout",
    "LimitSubscribers": 20000,
    "LimitLists": 100,
    "LimitCampaignSendPerPeriod": 200,
    "LimitEmailSendPerPeriod": 100000,
    "LimitEmailSendPerDay": 10000,
    "RelThemeID": 1,
    "ForceUnsubscriptionLink": "Enabled",
    "ForceRejectOptLink": "Enabled",
    "DefaultRateLimits": "{\"SMS\":{\"Minute\":-1,\"Hour\":-1,\"Day\":-1,\"Week\":-1,\"Month\":-1,\"Year\":-1},\"EmailGateway\":{\"Minute\":100,\"Hour\":5000,\"Day\":-1,\"Week\":-1,\"Month\":-1,\"Year\":-1}}"
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": [20]
}
txt
0: Success
1: Missing GroupName parameter
2: Missing SubscriberAreaLogoutURL parameter
5: Missing LimitSubscribers parameter
6: Missing LimitLists parameter
7: Missing LimitCampaignSendPerPeriod parameter
8: Missing RelThemeID parameter
17: Missing ForceUnsubscriptionLink parameter
18: Missing ForceRejectOptLink parameter
19: Invalid theme ID
20: Missing UserGroupID parameter
21: User group not found
22: Invalid send method
23: Invalid SMTP secure setting
24: Invalid SMTP auth setting
25: Email settings test failed

Patch a User Group

POST /api.phpNew in v5.9.3

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Partial update. Only the fields present in the request body are written. Every other column of the user group is left untouched — it is not included in the UPDATE statement at all.

Why this exists alongside usergroup.update

usergroup.update rebuilds the entire user group row from the request body. Around 30 optional columns are written unconditionally, so any field the caller omits is persisted as an empty string, and PaymentSystem / CreditSystem are written as Disabled whenever their key is absent — silently switching those systems off for every user in the group.

The only safe way to change a single value through usergroup.update is to read the whole group back with usergroup.get and echo every field, which forces the client to hold and re-transmit SendMethodSMTPPassword.

usergroup.patch removes that requirement: send UserGroupID plus only what you want to change. usergroup.update is unchanged and remains fully supported.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: usergroup.patch
SessionIDStringNoSession ID obtained from admin login
APIKeyStringNoAdmin API key for authentication
UserGroupIDIntegerYesThe user group to patch. This is the only required field — a request carrying just UserGroupID is a valid no-op
GroupNameStringNoName of the user group
RelThemeIDIntegerNoTheme ID. Must reference an existing theme
SubscriberAreaLogoutURLStringNoSubscriber area logout URL
ForceUnsubscriptionLinkStringNoEnabled or Disabled
ForceRejectOptLinkStringNoEnabled or Disabled
ForceOptInListStringNoEnabled or Disabled
PermissionsArray | StringNoArray of permission keys (stored comma-separated), or a pre-joined comma-separated string
PaymentSystemStringNoEnabled or Disabled
CreditSystemStringNoEnabled or Disabled
PaymentPricingRangeStringNoPricing range payload
PaymentCampaignsPerRecipientStringNoEnabled or Disabled
PaymentCampaignsPerCampaignCostNumberNoPer-campaign cost
PaymentAutoRespondersChargeAmountNumberNoAuto-responder charge amount
PaymentAutoRespondersPerRecipientStringNoEnabled or Disabled
PaymentDesignPrevChargeAmountNumberNoDesign-preview charge amount
PaymentDesignPrevChargePerReqNumberNoDesign-preview charge per request
PaymentSystemChargeAmountNumberNoSystem charge amount
LimitSubscribersIntegerNoMaximum number of subscribers
LimitListsIntegerNoMaximum number of lists
LimitCampaignSendPerPeriodIntegerNoCampaign send limit per period
LimitEmailSendPerPeriodIntegerNoEmail send limit per period
LimitEmailSendPerDayIntegerNoEmail send limit per day
LimitEmailSendLifetimeIntegerNoLifetime email limit
LimitEmailGatewaySenderDomainsIntegerNoEmail Gateway sender domain limit
ThresholdImportIntegerNoImport approval threshold
ThresholdEmailSendIntegerNoEmail-send approval threshold
PlainEmailHeaderStringNoPlain-text email header
PlainEmailFooterStringNoPlain-text email footer
HTMLEmailHeaderStringNoHTML email header
HTMLEmailFooterStringNoHTML email footer
TrialGroupStringNoYes or No. Enabled / Disabled are accepted as aliases and normalised to Yes / No
TrialExpireSecondsIntegerNoTrial duration in seconds
XMailerStringNoX-Mailer header value
SendMethodStringNoSystem, SMTP, LocalMTA, PHPMail, PowerMTA or SaveToDisk
SendMethodSaveToDiskDirStringNoSave-to-disk directory
SendMethodPowerMTAVMTAStringNoPowerMTA VirtualMTA
SendMethodPowerMTADirStringNoPowerMTA pickup directory
SendMethodLocalMTAPathStringNoLocal MTA binary path
SendMethodSMTPHostStringNoSMTP host
SendMethodSMTPPortIntegerNoSMTP port
SendMethodSMTPSecureStringNossl, tls, or an empty string for none
SendMethodSMTPTimeOutIntegerNoSMTP timeout in seconds
SendMethodSMTPAuthStringNotrue or false
SendMethodSMTPUsernameStringNoSMTP username
SendMethodSMTPPasswordStringNoSMTP password. Omit it and the stored password is left completely untouched
OptionsObject | StringNoGroup options as an object, or a JSON string that decodes to an object
DefaultRateLimitsObject | StringNoRate-limit buckets. Merged over the canonical defaults, so a partial payload cannot drop a bucket
CustomEmailHeadersStringNoCustom email headers
SubscriptionPlanStringNoSubscription plan identifier for the group

Parameter names are matched case-insensitively, as everywhere on /api.php.

Not patchable: LimitCampaignSendPeriod, LimitEmailSendPeriod, PaymentAutoRespondersChargePeriod, PaymentDesignPrevChargePeriod and PaymentSystemChargePeriod are fixed to Monthly by the product. PaymentCreditSystem, PaymentCreditPricing, SendMethodSMTPDebug, SendMethodSMTPKeepAlive and SendMethodSMTPMsgConn are not settable through any user group API command. SubscriptionPlanIsDefault is deliberately excluded — usergroup.update cannot set it either, and the "one default per subscription plan" rule is enforced by the admin interface.

Clearing a value

An empty string is a supplied value: sending "XMailer": "" clears the field. A field sent as null is treated as absent and is never written, because these columns are NOT NULL. Integer fields reject an empty string rather than storing it — see error code 32.

Send-method settings are not connectivity-tested

usergroup.update sends a live test email whenever SendMethod is not System. usergroup.patch validates the format of every send-method field it is given — so no value MySQL would silently coerce to an empty string or a zero can be stored — but does not perform that live test, because a partial payload does not describe a complete send configuration. Use usergroup.update, the admin interface, or settings.emailsendingtest when you want the connection verified.

Response Fields:

UpdatedFields lists the database columns actually written, so a caller can assert the write set rather than infer it. It is an empty array for a no-op patch, and in that case no UPDATE statement is issued at all.

bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "usergroup.patch",
    "AdminAPIKey": "your-admin-api-key",
    "UserGroupID": 3,
    "LimitEmailSendPerPeriod": 250000,
    "LimitSubscribers": 50000
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserGroupID": "3",
  "UpdatedFields": ["LimitSubscribers", "LimitEmailSendPerPeriod"]
}
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserGroupID": "3",
  "UpdatedFields": []
}
json
{
  "Success": false,
  "ErrorCode": 26,
  "ErrorField": "creditsystem"
}
txt
0:  Success
19: Invalid theme ID (RelThemeID)
20: Missing UserGroupID parameter (returned as an array, [20])
21: User group not found
22: Invalid send method
23: Invalid SendMethodSMTPSecure — must be 'ssl', 'tls' or ''
24: Invalid SendMethodSMTPAuth — must be 'true' or 'false'
26: Invalid Enabled/Disabled value; the offending field is returned in ErrorField
27: Invalid TrialGroup value — must be 'Yes' or 'No' (or the 'Enabled'/'Disabled' aliases)
28: Invalid Options payload — must be an object, or a JSON string decoding to one
29: Invalid DefaultRateLimits payload — must be an object, or a JSON string decoding to one
30: Invalid Permissions payload — must be a comma-separated string, or an array whose
    every element is a string
31: Non-scalar value supplied for a scalar field; the offending field is returned in
    ErrorField
32: Invalid integer value; the offending field is returned in ErrorField. The value must
    be a base-10 integer within the signed 32-bit column range (-2147483648..2147483647).
    Floats ('1.5'), scientific notation ('1e3') and hexadecimal ('0x1A') are rejected
    rather than silently coerced by MySQL — '0x1A' would otherwise store 0, which on a
    Limit* column means unlimited. Negative values are accepted: -1 is an established
    "unlimited" sentinel.

Enabled/Disabled values are validated, not coerced

usergroup.update turns any value that is not exactly Enabled into Disabled, including a missing key. usergroup.patch rejects an unrecognised value with ErrorCode: 26 and writes nothing for an absent key, so a system can be turned both on and off explicitly and can never be disabled by accident.

Get User Group

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: usergroup.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserGroupIDIntegerYesUser group ID to retrieve
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "usergroup.get",
    "SessionID": "your-session-id",
    "UserGroupID": 5
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserGroup": {
    "UserGroupID": 5,
    "GroupName": "Premium Users",
    "LimitSubscribers": 10000,
    "LimitLists": 50
  }
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing UserGroupID parameter
2: User group not found

Delete User Group

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Note: Not available in demo mode

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: usergroup.delete
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserGroupIDStringYesComma-separated list of user group IDs to delete
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "usergroup.delete",
    "SessionID": "your-session-id",
    "UserGroupID": "5,6,7"
  }'
json
{
  "Success": true,
  "ErrorCode": 0
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing UserGroupID parameter
4: Cannot delete the last user group

Duplicate User Group

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: usergroup.duplicate
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
UserGroupIDIntegerYesUser group ID to duplicate
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "usergroup.duplicate",
    "SessionID": "your-session-id",
    "UserGroupID": 5
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserGroupID": 8
}
json
{
  "Success": false,
  "ErrorCode": [2]
}
txt
0: Success
1: Missing UserGroupID parameter
2: User group not found

Get All User Groups

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: usergroups.get
SessionIDStringNoSession ID obtained from login
APIKeyStringNoAPI key for authentication
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "usergroups.get",
    "SessionID": "your-session-id"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserGroups": [
    {
      "UserGroupID": 1,
      "GroupName": "Standard Users",
      "LimitSubscribers": 1000,
      "DeliveryServerAssignments": {
        "Marketing": {
          "DeliveryServerID": 5,
          "DeliveryServerName": "Primary SMTP Server"
        },
        "Transactional": {
          "DeliveryServerID": 5,
          "DeliveryServerName": "Primary SMTP Server"
        },
        "AutoResponder": {
          "DeliveryServerID": 0,
          "DeliveryServerName": ""
        }
      }
    },
    {
      "UserGroupID": 2,
      "GroupName": "Premium Users",
      "LimitSubscribers": 10000,
      "DeliveryServerAssignments": {
        "Marketing": {
          "DeliveryServerID": 8,
          "DeliveryServerName": "High Volume SMTP"
        },
        "Transactional": {
          "DeliveryServerID": 8,
          "DeliveryServerName": "High Volume SMTP"
        },
        "AutoResponder": {
          "DeliveryServerID": 8,
          "DeliveryServerName": "High Volume SMTP"
        }
      }
    }
  ]
}
json
{
  "Success": false,
  "ErrorCode": 0
}
txt
0: Success

Response Field Reference:

FieldTypeDescription
UserGroupsArrayList of all user groups
UserGroupIDIntegerUnique identifier for the user group
GroupNameStringDisplay name of the user group
DeliveryServerAssignmentsObjectDelivery server assignments per channel type. Contains three keys: Marketing, Transactional, and AutoResponder. Each contains DeliveryServerID (0 if not assigned) and DeliveryServerName (empty string if not assigned)

Add Credits (DEPRECATED)

DEPRECATED

This endpoint is deprecated and will be removed in a future version. There is no replacement endpoint.

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Status: DEPRECATED - No replacement available

Order Payment (DEPRECATED)

DEPRECATED

This endpoint is deprecated and will be removed in a future version. There is no replacement endpoint.

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Status: DEPRECATED - No replacement available

Get Payment Periods (DEPRECATED)

DEPRECATED

This endpoint is deprecated and will be removed in a future version. There is no replacement endpoint.

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Status: DEPRECATED - No replacement available

Update Payment Periods (DEPRECATED)

DEPRECATED

This endpoint is deprecated and will be removed in a future version. There is no replacement endpoint.

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Status: DEPRECATED - No replacement available

Change Subscription Payment (DEPRECATED)

DEPRECATED

This endpoint is deprecated and will be removed in a future version. There is no replacement endpoint.

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Status: DEPRECATED - No replacement available

Upgrade Subscription (DEPRECATED)

DEPRECATED

This endpoint is deprecated and will be removed in a future version. There is no replacement endpoint.

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)
  • Status: DEPRECATED - No replacement available

Get Per-User Usage and Feature Adoption

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Returns a single account's current usage figures and which features it actually uses, in one strictly read-only call — safe to poll on a billing-page render or to gate a plan downgrade. Unlike user.get, it performs no writes: it never materialises a oempro_users_payment_log period and never triggers the active-subscriber-count write-through.

The response reports the two limit-reset periods separately because they run on different clocks: LimitEmailSendPerPeriod resets on the calendar month, LimitCampaignSendPerPeriod on the payment-log window. Each usage figure carries its own freshness ceiling (MaxStalenessSeconds). The active-subscriber count excludes archived lists (Definition: "SubscribedNotHardBounced,ArchivedExcluded").

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: user.usage.get
AdminApiKeyStringYesAdmin API key for authentication
UserIDIntegerYesTarget user account ID
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "user.usage.get",
    "AdminApiKey": "your-admin-api-key",
    "UserID": 42
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "UserID": 42,
  "Periods": {
    "EmailSend":    { "Type": "CalendarMonth", "Start": "2026-07-01 00:00:00", "End": "2026-07-31 23:59:59" },
    "CampaignSend": { "Type": "PaymentLog", "Start": "2026-07-08", "End": "2026-08-08", "Derived": true }
  },
  "Usage": {
    "ActiveSubscribers":     { "Value": 33, "Source": "oempro_subscriber_lists.ActiveSubscriberCount", "AsOf": null, "MaxStalenessSeconds": 300, "Definition": "SubscribedNotHardBounced,ArchivedExcluded" },
    "EmailsSentInPeriod":    { "Value": 0, "Source": "CampaignsEgTransactionalSum", "MaxStalenessSeconds": 120, "Period": "EmailSend" },
    "CampaignsSentInPeriod": { "Value": 0, "Source": "oempro_users_payment_log.CampaignsSent", "MaxStalenessSeconds": 0, "Period": "CampaignSend", "Derived": true }
  },
  "Features": {
    "Campaigns":           { "InUse": true, "Count": 23 },
    "Journeys":            { "InUse": true, "Count": 23, "ActiveCount": 0 },
    "SendingDomains":      { "InUse": true, "Count": 2 },
    "EmailGatewayAPIKeys": { "InUse": true, "Count": 1 },
    "UserAPIKeys":         { "InUse": true, "Count": 1 },
    "AutoResponders":      { "InUse": true, "Count": 4 },
    "Lists":               { "InUse": true, "Count": 7 },
    "Segments":            { "InUse": true, "Count": 4 }
  }
}
json
{
  "Success": false,
  "ErrorCode": [1]
}
txt
0: Success
1: Missing UserID parameter
2: Invalid UserID (must be a positive integer)
3: User not found
4: A feature-usage count query failed (usage temporarily unavailable — do not treat missing counts as zero)

Notes:

  • Periods.CampaignSend.Derived is true when no payment-log row covers today yet; the window was computed read-only and the backend has not materialised it (treat as not-yet-authoritative). CampaignsSentInPeriod.Value is 0 in that case.
  • ActiveSubscribers.AsOf is null because the denormalized count has no last-calculated timestamp; only the MaxStalenessSeconds ceiling is known.
  • Feature counts exclude soft-deleted rows: SendingDomains and EmailGatewayAPIKeys exclude Status = 'Deleted'. Journeys.ActiveCount counts Status = 'Enabled'.
  • EmailsSentInPeriod equals the value LimitEmailSendPerPeriod enforcement uses for the same calendar-month window.

Get Bulk User Usage Metering

POST /api.php

API Usage Notes

  • Authentication required: Admin API Key
  • Legacy endpoint access via /api.php only (no v1 REST alias configured)

Returns date-ranged usage for many users in a single call — a per-day "emails sent" series plus the current active-subscriber count per account — for a daily billing/metering job. The emails-sent figure uses the same enforcement-backed source as user.usage.get (campaign + Email Gateway + auto-responder sends), so display and metering agree. It is bulk (a fixed number of grouped queries regardless of user count) and issues no Redis KEYS scan.

Absent periods are omitted (never padded with synthesized zeros) so a consumer can distinguish "no activity recorded" from "genuinely zero." The active-subscriber count excludes archived lists.

Request Body Parameters:

ParameterTypeRequiredDescription
CommandStringYesAPI command: users.usage.get
AdminApiKeyStringYesAdmin API key for authentication
StartDateStringYesRange start. Accepts the same formats as user.stats (e.g. YYYY-MM-DD)
EndDateStringYesRange end. Same formats as StartDate
UserIDsStringNoCSV or array of user IDs to scope to. Omit for all users
AggregationStringNoPeriod bucketing. Possible values: daily (default), weekly, monthly, yearly
MetricsStringNoCSV of metrics. Only sent_emails is supported (default)
bash
curl -X POST https://example.com/api.php \
  -H "Content-Type: application/json" \
  -d '{
    "Command": "users.usage.get",
    "AdminApiKey": "your-admin-api-key",
    "UserIDs": "1,2,3",
    "StartDate": "2026-06-01",
    "EndDate": "2026-06-30",
    "Aggregation": "daily"
  }'
json
{
  "Success": true,
  "ErrorCode": 0,
  "Aggregation": "daily",
  "StartDate": "2026-06-01 00:00:00",
  "EndDate": "2026-06-30 23:59:59",
  "Metrics": ["sent_emails"],
  "UserCount": 3,
  "Users": {
    "1": {
      "UserID": 1,
      "Metrics": { "sent_emails": { "2026-06-14": 5 } },
      "TotalSentEmails": 5,
      "TotalActiveSubscribers": { "Value": 33, "Source": "oempro_subscriber_lists.ActiveSubscriberCount", "Definition": "SubscribedNotHardBounced,ArchivedExcluded", "MaxStalenessSeconds": 300 }
    }
  }
}
json
{
  "Success": false,
  "ErrorCode": 3,
  "ErrorMessage": "Unsupported aggregation; supported: daily, weekly, monthly, yearly"
}
txt
0: Success
1: Missing StartDate parameter
2: Missing EndDate parameter
3: Unsupported aggregation value
4: Unsupported metric value
5: Invalid StartDate/EndDate format

Notes:

  • When UserIDs is supplied, every requested user appears in Users (with an empty Metrics map and 0 subscribers when they have no data), giving deterministic per-account rows. When omitted, only users with sends or active subscribers in scope are returned.
  • Days with no activity are omitted from each Metrics series; weekly/monthly/yearly fold those daily buckets in the response.
  • TotalActiveSubscribers is always the current live value; it does not honor the date range.

Any questions? Contact us.