If your integration calls the Act-On API by sending a username and password to POST /token (grant_type=password), you can switch it to a one-time browser sign-in. Today the integration stores an Act-On user's password and sends it every time it needs a token. After the change, a person signs in once in a browser on Act-On's sign-in page, with multi-factor authentication (MFA) if their account requires it. The integration receives a refresh token from that sign-in and uses it from then on to get new access tokens, with no password and no person involved. This is the OAuth 2.0 "authorization code" flow.
This article is for the developer or IT person who owns the integration.
Why this matters
- No stored password. The integration never sees or keeps the user's password.
- Works with MFA. A password-based token request can't complete an MFA challenge, because there is no person to answer it. With the browser sign-in, the person completes MFA once, during that sign-in. Act-On's sign-in applies MFA to the browser sign-in and not to refresh-token requests, so the integration keeps running unattended afterwards.
- Access can be cut off without a password change. Revoking the refresh token stops the integration, and the user's own sign-in is unaffected.
- The integration doesn't break when the user changes their password.
Quick Reference
- Sign in once: open
/authorizein a browser and sign in as the integration's user. - Exchange the code: send it straight away to
POST /tokenwithgrant_type=authorization_code. - Refresh:
POST /tokenwithgrant_type=refresh_token. Access tokens last 1 hour. - Rotation: save the new refresh token from every refresh. Never use a refresh token twice.
- Recovery: nearly every refresh-token failure is fixed by repeating the browser sign-in.
Not supported: app-only access ("client credentials") and static API keys. Every Act-On API token must belong to a user.
Before You Start
You need four things.
- Your Act-On API app's client ID and client secret. These are the same ones the integration uses today.
- A callback (redirect) URL registered on that app. After sign-in, Act-On's sign-in page sends the browser back to this URL with a one-time code. The URL must be registered on your API app in advance, and customers can't register it themselves today. Request it through: [Support? see Q1]. Give the exact URL, including scheme, host, port and path, for example
https://integrations.example.com/acton/callback.- Important: The
redirect_uriyou send must match a registered URL exactly. If it doesn't, the sign-in page shows "Callback URL mismatch" and no code is issued. - A back-end job with no web interface can register a local address such as
http://localhost:8080/callbackand run a small one-off script for the sign-in (see Step 3).
- Important: The
- The user the integration will act as (see Which User to Sign In As).
- Somewhere secure to store the refresh token, which the integration can both read and write. It is a long-lived credential: treat it like a password.
The One-Time Setup
Step 1: Send a person to the sign-in page
Open this URL in a browser:
https://api.actonsoftware.com/authorize?response_type=code&client_id=<CLIENT_ID>&redirect_uri=<URL-ENCODED REDIRECT URI>&state=<RANDOM VALUE>EU customers use https://api-eu.actonsoftware.com/authorize with the same parameters. Both send the person to the same Act-On sign-in page.
| Parameter | Required | Value |
|---|---|---|
response_type | Yes | code |
client_id | Yes | Your API app's client ID |
redirect_uri | Yes | A registered callback URL, URL-encoded |
state | Recommended | A random value your integration generates and checks when the browser comes back (protects against forged callbacks) |
Don't send scope or audience. Act-On sets both itself and ignores any you send. It always asks for offline_access, which is what makes a refresh token come back.
Step 2: The person signs in
The person signs in on Act-On's sign-in page as the integration's user. If MFA is required for that user, they complete it here, and they are asked to enrol first if they haven't yet.
Step 3: The browser comes back with a code
The browser is redirected to:
<redirect_uri>?code=<CODE>&state=<STATE>- Check that
statematches the value you sent. - Important: The code works once and only for a short time, so exchange it straight away (Step 4). Sending it a second time fails with
invalid_grant"Invalid authorization code". - For a back-end job: a one-off script can listen on the registered localhost address and catch the code. Or the person can copy the
codevalue from the browser's address bar after the redirect, even if the page itself shows an error because nothing is listening, and paste it into the exchange step quickly.
Step 4: Exchange the code for tokens
curl -s https://api.actonsoftware.com/token \
-d grant_type=authorization_code \
-d code=<CODE> \
--data-urlencode redirect_uri=<REDIRECT URI> \
-d client_id=<CLIENT_ID> \
-d client_secret=<CLIENT_SECRET>- The client ID and secret can go in the form body, as above, or in an HTTP Basic
Authorizationheader. Both work. - Important:
redirect_urimust be the same value used in Step 1, or the exchange fails (see Troubleshooting).
Response (values shortened):
{
"access_token": "eyJhbGciOi...",
"refresh_token": "v1.MjE3...",
"scope": "offline_access",
"expires_in": 3600,
"token_type": "Bearer"
}access_tokenis valid for 1 hour (expires_inis in seconds).- Store
refresh_tokensecurely now. This is the credential the integration keeps.
Step 5: Call the API
Send the access token exactly as today: Authorization: Bearer <access_token>. Nothing else about the API calls changes.
Getting New Access Tokens
When the access token expires (after an hour, or when the API returns 401), get a new one with the refresh token. No person is involved.
curl -s https://api.actonsoftware.com/token \
-d grant_type=refresh_token \
-d refresh_token=<REFRESH_TOKEN> \
-d client_id=<CLIENT_ID> \
-d client_secret=<CLIENT_SECRET>The response has the same shape as in Step 4.
Refresh Token Rules
Important: These are the standard settings on Act-On customer API apps. Rules 1 and 2 are where integrations break.
- Every refresh returns a new refresh token, and the old one stops working ("rotation"). The integration must save the new
refresh_tokenfrom every refresh response, replacing the old one, before using the new access token. - Never use a refresh token twice, and never refresh from two places at once. Act-On allows no grace period for reusing an old refresh token. Reusing one is treated as a possible theft, and the whole chain is cancelled: the newest refresh token stops working too, and the person has to do the browser sign-in again. In practice:
- Keep one copy of the refresh token.
- Refresh from one process at a time (use a lock, or one scheduled job).
- If a refresh request fails after it was sent (a timeout or dropped connection), the integration can't know whether Act-On rotated the token. Retrying with the old token cancels the chain if it was rotated. The recovery is a new browser sign-in.
- A refresh token not used for 30 days expires. There is no overall limit, so a token refreshed at least once every 30 days lasts indefinitely. An integration that runs less often than monthly should refresh on a schedule, or plan for a new sign-in.
- Access tokens last 1 hour. Reuse an access token until it expires; don't refresh before every call.
Which User to Sign In As
- The integration acts as the user who signs in. Its calls have that user's access, and it stops working when that user is removed or their sign-in is disabled.
- Use a dedicated integration user, or at least a long-lived one, not a person who might leave or change role.
- The person who does the one-time sign-in must be able to sign in as that user, including its MFA if required.
Moving an Existing Integration Over
- Get the callback URL registered (see Before You Start).
- Change the integration's token handling: store a refresh token instead of the password, use the refresh call (see Getting New Access Tokens), and follow the rotation rules.
- Do the one-time sign-in and store the refresh token.
- Run the integration and confirm its calls succeed.
- Remove the stored password from the integration. The user's password can then be changed and MFA turned on without affecting the integration [see Q3].
Troubleshooting
"Token request" means POST /token. Errors from it are shown as the HTTP status, the error value and the error_description text.
| What you see | Where | Why | What to do |
|---|---|---|---|
| Sign-in page shows "Callback URL mismatch. <url> is not in the list of allowed callback URLs" | Browser, Step 1 | redirect_uri isn't registered on your API app, or differs from the registered value (scheme, host, port, path, trailing slash). | Use the registered value exactly, or ask to have the new one registered. |
403 unauthorized_client"The redirect URI is wrong. You sent …, and we expected …" | Token request, Step 4 | The redirect_uri in the exchange differs from the one used at /authorize. | Send the same value in both steps, then repeat the sign-in to get a fresh code. |
403 invalid_grant"Invalid authorization code" | Token request, Step 4 | The code was already used, or it expired. | Repeat the browser sign-in and exchange the new code straight away. |
401 access_denied"Unauthorized" | Token request | Wrong client ID or client secret. | Check the app's credentials. |
403 invalid_grant"Unknown or invalid refresh token." | Token request, refresh | The refresh token is unknown, expired (30 days unused), already used (rotation), or revoked. Reusing an old token also cancels the newest one, so after a reuse every saved token fails this way (rule 2). | Repeat the browser sign-in and store the new refresh token. Then find out why: an old token was saved, two processes refreshed at once, or the integration was idle for more than 30 days. |
| Refresh fails after the user's password was changed, MFA was reset, or the user was removed or blocked | Token request, refresh | [Expected; see Q3–Q5] | Repeat the browser sign-in as a valid user. If the user was removed, choose a new one (see Which User to Sign In As). |
| 401 on API calls after a successful refresh | API calls | The access token wasn't the newest one, or the user lost access to the account. | Use the access token from the latest refresh. If it persists, check the user's access in Act-On. |
General rule for recovery: nearly every refresh-token failure is fixed by repeating the one-time browser sign-in and storing the new refresh token.