Authentication
Every Rise Public API request carries an OAuth 2.0 bearer token in the Authorization header. This page walks through registering an application, choosing a grant type, and obtaining + refreshing tokens.
Choosing a grant type
| Use case | Flow |
|---|---|
| Server-to-server, no user interaction (your backend → Rise) | Client Credentials |
| Acting on behalf of a signed-in Rise user (partner app, public client) | Authorization Code with PKCE |
| Refreshing an expired user access token | Refresh token |
Device flow, implicit flow, and password grant are not supported.
Registering an application
- Sign in to your Rise account as an account owner or administrator.
- Go to Settings → Developer → OAuth applications.
- Click Create application and fill in:
- Name — shown to users on the consent screen
- Redirect URI(s) — for Authorization Code flow; must be
https://(excepthttp://localhostin development) - Scopes — see Scope reference below
- Choose client type:
- Confidential — server-to-server; issues a
client_id+client_secret - Public — browser or mobile; issues
client_idonly, requires PKCE
- Confidential — server-to-server; issues a
You'll receive the client_id (and client_secret if confidential) immediately. The secret is shown once — store it somewhere safe.
Client Credentials
Use this when your own backend is the caller and no end user is involved.
- curl
- Node.js
- Ruby
curl -X POST https://api.risepeople.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$RISE_CLIENT_ID" \
-d "client_secret=$RISE_CLIENT_SECRET" \
-d "scope=employees:read payroll:read"
const res = await fetch("https://api.risepeople.com/v1/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.RISE_CLIENT_ID,
client_secret: process.env.RISE_CLIENT_SECRET,
scope: "employees:read payroll:read",
}),
});
const { access_token, expires_in } = await res.json();
require "net/http"
require "json"
res = Net::HTTP.post_form(
URI("https://api.risepeople.com/v1/oauth/token"),
grant_type: "client_credentials",
client_id: ENV.fetch("RISE_CLIENT_ID"),
client_secret: ENV.fetch("RISE_CLIENT_SECRET"),
scope: "employees:read payroll:read",
)
token = JSON.parse(res.body).fetch("access_token")
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "employees:read payroll:read"
}
Cache the token for expires_in seconds (currently 1 hour); request a new one before it expires.
Authorization Code with PKCE
Use this when a human is involved — your app acts on behalf of a Rise user who has explicitly granted you permission.
Step 1 — generate a PKCE verifier + challenge
The code verifier is a high-entropy random string; the challenge is its SHA-256 hash, base64url-encoded.
- Node.js
- Ruby
import crypto from "node:crypto";
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
require "securerandom"
require "digest"
require "base64"
verifier = SecureRandom.urlsafe_base64(32)
challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
Store the verifier server-side (or in session storage) keyed by a state value you'll send to Rise.
Step 2 — redirect the user to /oauth/authorize
https://api.risepeople.com/v1/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour-app.example.com%2Fcallback
&scope=employees%3Aread%20payroll%3Aread
&state=OPAQUE_ANTI_CSRF_VALUE
&code_challenge=CHALLENGE_FROM_STEP_1
&code_challenge_method=S256
Rise shows a consent screen; if the user approves, they're redirected to your redirect_uri with ?code=...&state=....
Step 3 — exchange the code for tokens
- curl
- Node.js
curl -X POST https://api.risepeople.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=$CODE_FROM_CALLBACK" \
-d "redirect_uri=https://your-app.example.com/callback" \
-d "client_id=$RISE_CLIENT_ID" \
-d "code_verifier=$VERIFIER_FROM_STEP_1"
const res = await fetch("https://api.risepeople.com/v1/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: "https://your-app.example.com/callback",
client_id: process.env.RISE_CLIENT_ID,
code_verifier: verifier,
}),
});
const { access_token, refresh_token, expires_in } = await res.json();
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200...",
"scope": "employees:read payroll:read"
}
Refreshing a token
When an access token expires (401 token_expired), exchange the refresh token for a new one:
curl -X POST https://api.risepeople.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=$REFRESH_TOKEN" \
-d "client_id=$RISE_CLIENT_ID"
Refresh tokens rotate: the response contains a new refresh_token. Discard the old one — using it again returns 401.
Using the token
Pass the access token in the Authorization header on every call:
curl https://api.risepeople.com/v1/employees/42 \
-H "Authorization: Bearer $ACCESS_TOKEN"
Scope reference
| Scope | Grants |
|---|---|
employees:read | List and retrieve employee records |
employees:write | Create, update, and terminate employees |
payroll:read | Read payroll runs, pay stubs, and payroll configuration |
payroll:write | Submit payroll runs, edit deductions |
webhooks:manage | Register, update, and delete webhook subscriptions |
A valid token without the right scope returns 403 insufficient_scope, not 401 — check the error response to distinguish authentication ("we don't know who you are") from authorization ("we know who you are, but you can't do this") failures.
Token lifetime
| Token | Lifetime |
|---|---|
| Access token (Client Credentials) | 1 hour |
| Access token (Authorization Code) | 1 hour |
| Refresh token | 30 days, rotated on every use |
Revoking a token
curl -X POST https://api.risepeople.com/v1/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "token=$ACCESS_OR_REFRESH_TOKEN" \
-d "client_id=$RISE_CLIENT_ID" \
-d "client_secret=$RISE_CLIENT_SECRET" # omit for public clients
Revoking a refresh token also revokes all access tokens derived from it.
Common errors
| HTTP | type | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or malformed parameter |
| 401 | invalid_client | client_id / client_secret wrong |
| 401 | invalid_grant | Code expired, already used, or PKCE verifier mismatch |
| 401 | token_expired | Access token past expires_in — refresh it |
| 403 | insufficient_scope | Valid token but missing the scope for this endpoint |
See Errors for the full problem-details envelope.