How to create an account and log in

Intro

Create Omnisend accounts for your merchants, log them in, and manage their brand — all via API. This guide walks through the partner integration flow: obtaining an access token, creating an account, establishing a login session, and (optionally) updating brand details.

See API reference: Accounts

Authentication model — read this first

This flow uses two different OAuth credentials. They are not interchangeable.

CredentialGrantBound toUse it forTypical scopes
Partner credentialsclient_credentialsyour platform (no brand)Creating accounts, creating login sessions (Steps 1–4)accounts.write, sessions.write
Brand appauthorization_code (+ refresh_token)a single brandAny brand-scoped API: updating the brand, contacts, products, events, etc. (Step 5 and beyond)brands.write, contacts.write, products.write, events.write, …

Why two? Account and session creation happen before a brand exists, so they use a platform-level credential identified by your partner ID. Brand-scoped calls act on a specific brand, so they require a token bound to that brand. A client_credentials token has no brand attached and cannot be used for brand-scoped endpoints — adding brands.write to it will not work (see Troubleshooting).

Note: A brand access token is obtained after the user has logged in (Step 4), by running the standard OAuth authorization flow for your brand app (Step 5).

Prerequisites

  • Partner credentials (client_id and client_secret) for the client_credentials grant — contact Omnisend to obtain them.
  • A brand app (client_id and client_secret, with a registered redirect_uri) for the authorization_code grant, if you need to make brand-scoped calls — contact Omnisend to set it up.
  • Your platform must be registered as an Omnisend partner.

Step 1: Get a partner access token

Request an access token using the OAuth 2.0 client credentials grant with your partner credentials. Request only the scopes used by Steps 2–4.

curl --request POST \
     --url 'https://app.omnisend.com/oauth2/token' \
     --header 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'client_id=YOUR_PARTNER_CLIENT_ID' \
     --data-urlencode 'client_secret=YOUR_PARTNER_CLIENT_SECRET' \
     --data-urlencode 'grant_type=client_credentials' \
     --data-urlencode 'scope=accounts.write sessions.write'

Response:

{
  "access_token": "YOUR_PARTNER_ACCESS_TOKEN",
  "expires_in": 7200,
  "token_type": "Bearer",
  "scope": "accounts.write sessions.write"
}

Note: The access token expires after 2 hours (expires_in: 7200 seconds). Request a new token when the current one expires.

Partner-credential scopes

ScopeUse
accounts.writeCreate accounts (Step 2)
sessions.writeCreate login tokens / sessions (Steps 3–4)

Note: brands.write is not a partner-credential scope. It belongs to your brand app and is used with a brand access token in Step 5. Requesting brands.write here has no effect on brand-scoped endpoints.

Step 2: Create an account

Create a new Omnisend account with business details, a primary user, and an address. Save the returned accountID, brandID, and userID — you will need them to log the user in.

curl --request POST \
     --url https://api.omnisend.com/api/accounts \
     --header 'Authorization: Bearer YOUR_PARTNER_ACCESS_TOKEN' \
     --header 'Content-Type: application/json' \
     --data '{
  "name": "My Store",
  "website": "https://www.mystore.com",
  "timezone": "Europe/Vilnius",
  "currency": "EUR",
  "language": "en",
  "user": {
    "email": "[email protected]",
    "firstName": "Jane",
    "lastName": "Smith"
  },
  "address": {
    "countryCode": "US",
    "stateCode": "CA",
    "city": "San Francisco",
    "address": "123 Market St.",
    "postCode": "94105"
  }
}'

Response (201 Created):

{
  "accountID": "60a6170920d91c215e949b5a",
  "brandID": "60a6170920d91c215e949b5c",
  "userID": "60a6170920d91c215e949b5d"
}

Warning: If countryCode is US, the stateCode field is required. Omitting it returns a 400 validation error.

Field validations

FieldFormat
websiteValid URL
timezoneIANA Time Zone (e.g. Europe/Vilnius)
currencyISO 4217 (e.g. EUR, USD)
languageBCP 47 subtag (e.g. en, de)
address.countryCodeISO 3166-1 alpha-2 (e.g. US, LT)
address.stateCode2 letters, required if countryCode is US

Step 3: Create a login token

From your backend, using the partner access token, request a one-time login token with the brandID and userID from account creation:

curl --request POST \
     --url https://api.omnisend.com/api/sessions/token \
     --header 'Authorization: Bearer YOUR_PARTNER_ACCESS_TOKEN' \
     --header 'Content-Type: application/json' \
     --data '{
  "brandID": "60a6170920d91c215e949b5c",
  "userID": "60a6170920d91c215e949b5d"
}'

Response:

{
  "token": "ONE_TIME_LOGIN_TOKEN"
}

Note: The login token is single-use. Generate a new token each time you want to log the user in.

Step 4: Log the user in

From your frontend, POST the login token to the sessions endpoint. This must happen in the user's browser so that session cookies are properly set.

Submit a form POST (or use JavaScript to submit a form) to https://api.omnisend.com/api/sessions:

<form method="POST" action="https://api.omnisend.com/api/sessions">
  <input type="hidden" name="token" value="ONE_TIME_LOGIN_TOKEN" />
  <input type="hidden" name="returnUrl" value="https://app.omnisend.com/" />
  <button type="submit">Open Omnisend</button>
</form>

On success, the user is redirected to Omnisend with active session cookies.

ParameterRequiredDescription
tokenYesOne-time login token from Step 3
returnUrlNoRedirect URL after login. Must start with https://app.omnisend.com/

Note: The True-Client-IP header with the end user's IP address and a valid User-Agent header are required. Most browsers and HTTP clients set User-Agent automatically.

Step 5: Make brand-scoped calls (optional)

Brand-scoped endpoints — such as updating the brand, or managing contacts, products, and events — require a brand access token issued to your brand app, not the partner token from Step 1. A brand access token is obtained with the authorization_code grant, which binds the token to the specific brand the user is logged into.

Prerequisite: The user must be logged in (Step 4 completed) in the same browser session. The authorization step relies on that session to determine which brand the token is bound to.

Step 5a: Authorize your brand app

Redirect the user's browser to the authorization endpoint with your brand app client_id. Because the user already has an active session from Step 4, no extra login is needed; if your brand app is configured to skip consent, this redirect is transparent.

GET https://app.omnisend.com/oauth2/authorize
  ?client_id=YOUR_BRAND_APP_CLIENT_ID
  &redirect_uri=YOUR_REGISTERED_REDIRECT_URI
  &response_type=code
  &scope=brands.write
  &state=RANDOM_STATE
  &nonce=RANDOM_NONCE

On success, the browser is redirected to your redirect_uri with an authorization code (and the state you sent, which you must verify).

Step 5b: Exchange the code for a brand access token

From your backend, exchange the authorization code for an access token and a refresh token:

curl --request POST \
     --url 'https://app.omnisend.com/oauth2/token' \
     --header 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'client_id=YOUR_BRAND_APP_CLIENT_ID' \
     --data-urlencode 'client_secret=YOUR_BRAND_APP_CLIENT_SECRET' \
     --data-urlencode 'grant_type=authorization_code' \
     --data-urlencode 'code=AUTHORIZATION_CODE' \
     --data-urlencode 'redirect_uri=YOUR_REGISTERED_REDIRECT_URI'

Response:

{
  "access_token": "YOUR_BRAND_ACCESS_TOKEN",
  "refresh_token": "YOUR_REFRESH_TOKEN",
  "token_type": "Bearer",
  "scope": "brands.write"
}

This access_token is bound to the brand and carries the scopes you requested. Use it for all brand-scoped calls, and store the refresh_token to obtain new access tokens without repeating the browser flow (see Step 5d).

Step 5c: Update brand information

Send a PATCH request with only the fields you want to change, using the brand access token:

curl --request PATCH \
     --url https://api.omnisend.com/api/brands/current \
     --header 'Authorization: Bearer YOUR_BRAND_ACCESS_TOKEN' \
     --header 'Content-Type: application/json' \
     --data '{
  "name": "Updated Store Name",
  "timezone": "America/New_York"
}'

Response: 204 No Content

Step 5d: Refresh the brand token

Brand access tokens expire. When one expires, use the refresh_token from Step 5b to obtain a new brand access token from your backend — no browser flow needed:

curl --request POST \
     --url 'https://app.omnisend.com/oauth2/token' \
     --header 'Content-Type: application/x-www-form-urlencoded' \
     --data-urlencode 'client_id=YOUR_BRAND_APP_CLIENT_ID' \
     --data-urlencode 'client_secret=YOUR_BRAND_APP_CLIENT_SECRET' \
     --data-urlencode 'grant_type=refresh_token' \
     --data-urlencode 'refresh_token=YOUR_REFRESH_TOKEN'

Response:

{
  "access_token": "NEW_BRAND_ACCESS_TOKEN",
  "token_type": "Bearer",
  "scope": "brands.write"
}

Note: The response returns a new access_token but no new refresh_token — keep reusing your existing refresh token for subsequent refreshes.

Complete flow summary

  1. Get partner access tokenPOST https://app.omnisend.com/oauth2/token (client credentials, accounts.write sessions.write)
  2. Create accountPOST https://api.omnisend.com/api/accounts (partner token; save brandID, userID)
  3. Create login tokenPOST https://api.omnisend.com/api/sessions/token (partner token, from backend)
  4. Log user inPOST https://api.omnisend.com/api/sessions (from browser)
  5. Brand-scoped calls (optional):
    • 5a. Authorize brand appGET https://app.omnisend.com/oauth2/authorize (browser, after login)
    • 5b. Exchange codePOST https://app.omnisend.com/oauth2/token (authorization_code → brand token + refresh token)
    • 5c. Update brandPATCH https://api.omnisend.com/api/brands/current (brand token)
    • 5d. RefreshPOST https://app.omnisend.com/oauth2/token (refresh_token → new brand token)

Troubleshooting

403 Forbidden on PATCH /api/brands/current with "This action requires the 'brands.write' scope."
Your token does not carry brands.write. This scope belongs to the brand app (Step 5), not the partner credentials. Adding brands.write to the client_credentials request in Step 1 will not fix it — see below.

403 Forbidden on PATCH /api/brands/current with "The server understood the request, but is refusing to fulfill it."
You are calling a brand-scoped endpoint with a partner (client_credentials) token. That token has no brand attached, so the request is rejected even if it carries brands.write. Use a brand access token obtained via the authorization_code flow in Step 5.


Did this page help you?