REST API authentication: Resource Owner Password Credentials

The tracezilla REST API accepts OAuth 2.0 bearer token. This document covers the Resource Owner Password Credentials grant (grant_type=password): an integration exchanges a client ID, client secret, username and password for a short-lived access token plus a refresh token.

Use it for server-to-server integrations, where a single client should be able to be used by multiple users. For a single long-lived token, a personal access token (the “API tokens” tab) is the simpler alternative.

1. Create the credentials

In the app, go to My account → API tokens → Client Credentials (route my-account.api-tokens, ?tab=client-credentials) and fill in:

Field Meaning
Name Label for the client (max 60 characters). Also used as the API user’s name.
Team (optional) Creates a dedicated API user on that team. Only listed for teams where you are admin, owner or supporter.
Role The dedicated API user’s role on the team. Required when a team is chosen.

What gets created (ApiTokenController::createClientCredentials):

  • With a team selected: a new user with a generated email, {team_slug}.{5 random chars}.{s|u}@tracezilla.api (u for umbrella teams, s otherwise), a random 36-character password, and membership of the team in the chosen role. A password client is created for that user. The client ID, client secret, username and password are shown once.
  • Without a team: a password client is created for your own user. Only the client ID and secret are shown; you authenticate with your own email and password.

Prefer the dedicated API user. It keeps integration access separate from a person’s login, its permissions are limited by the chosen role, and it does not break when someone changes their password.

The secret and the generated password are shown only once. Store them in the integration’s secret store straight away.

Revoking the client in the same tab deletes it. Tokens issued through it stop working.

2. Request an access token

1
2
3
4
5
6
7
8
9
10
POST https://{app-domain}/oauth/token
Content-Type: application/x-www-form-urlencoded
Accept: application/json

grant_type=password
&client_id={client_id}
&client_secret={client_secret}
&username={api_user_email}
&password={api_user_password}
&scope=all

curl example:

1
2
3
4
5
6
7
8
curl -X POST "https://{app-domain}/oauth/token" \
  -H "Accept: application/json" \
  -d grant_type=password \
  -d client_id="$TZ_CLIENT_ID" \
  -d client_secret="$TZ_CLIENT_SECRET" \
  -d username="$TZ_USERNAME" \
  -d password="$TZ_PASSWORD" \
  -d scope=all

Successful response:

1
2
3
4
5
6
{
  "token_type": "Bearer",
  "expires_in": 3600,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
  "refresh_token": "def50200a1b2..."
}

username is the user’s email address. Wrong credentials return 400 invalid_grant, and an unknown or revoked client returns 401 invalid_client.

Scopes

The user will only ever have the permissions granted by the role assigned to the user.

Scope Description
all All available CRUD actions (default when scope is omitted)

3. Call the API

Send the access token as a bearer token. Every API route is prefixed with /api/v1/{team_slug}:

1
2
3
curl "https://{app-domain}/api/v1/{team_slug}/lots" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

The user behind the token must be a member of {team_slug}. What it can do is limited by its role on that team.

Requests are rate-limited per user (throttle:100|rate_limit,1): 100 requests per minute, unless the user’s rate_limit column sets a different value. A 429 response means wait and retry.

4. Refresh the token

Access tokens expire after 60 minutes. Use the refresh token to get a new pair without sending the password again:

1
2
3
4
5
6
7
8
9
POST https://{app-domain}/oauth/token
Content-Type: application/x-www-form-urlencoded
Accept: application/json

grant_type=refresh_token
&refresh_token={refresh_token}
&client_id={client_id}
&client_secret={client_secret}
&scope=all

The response has the same shape as in step 2. Replace both stored tokens. Refresh tokens are single-use, so the old one stops working once it has been exchanged.

Refresh tokens expire after 30 days. An integration idle for longer, or one that gets 400 invalid_grant on refresh, should fall back to step 2.

  • Cache the access token and reuse it until it is close to expires_in. Do not request a new token for every API call.
  • Run one token holder per client and user. Parallel workers should share the cached token rather than each requesting their own (see the cleanup note below).
  • Refresh proactively, about a minute before expiry, or on a 401 from the API, then retry the request once.
  • Never log the client secret, password, access token or refresh token.