Skip to main content

Overview

Rhombus supports OAuth 2.0 with PKCE so you can build applications that sign users in with their existing Rhombus credentials. When a user clicks Sign in with Rhombus in your app, they’re redirected to the Rhombus Console to authenticate, then bounced back to your redirect URI with a short-lived authorization code. You exchange that code for an access token and call the Rhombus API on the user’s behalf. This is the same flow used by the official Rhombus CLI — rhombus login is a working reference implementation in Go that you can read end to end. Use this guide to build:
  • CLI tools that authenticate via browser login (like the Rhombus CLI itself)
  • Web applications that let Rhombus users sign in to your service
  • Admin dashboards and internal integrations for customers that manage many Rhombus orgs
  • Desktop applications using a local-loopback redirect
What this guide isn’t. This is for third-party developers building apps that sign in Rhombus users. If you’re a Rhombus customer trying to configure SAML SSO for your employees (Okta, Azure AD, Google Workspace) or SCIM for user provisioning, see SAML SSO & SCIM Provisioning instead — that’s a separate surface from the OAuth flow described here.

How the flow works

The Rhombus OAuth surface spans three hosts. This is a common source of confusion — your app talks to all three at different stages of the flow.
HostRole
console.rhombussystems.comWhere the user signs in and approves your app
auth.rhombussystems.comToken exchange endpoint
api2.rhombussystems.comAPI calls made with the resulting access token

Before you begin

Before you start, make sure you have:
  • A Rhombus account with an API key (generated in the Rhombus Console under Settings → API Management)
  • A redirect URI your app controls — a public HTTPS URL in production, or http://localhost:<port>/callback for CLI and desktop apps
  • Basic familiarity with OAuth 2.0 Authorization Code flow and PKCE (RFC 7636)

Step 1: Register your application

Before you can start an OAuth flow, Rhombus needs to know about your application. Registration gives you a clientId and clientSecret pair. There are two paths to a clientId, depending on where you are in your build:

Prototyping & development

Call the submitApplication API directly with your existing API key. Fastest path to a working flow on localhost. Self-serve, immediate.

Production & distribution

Apps that will be shipped to customers or accept logins from users outside your own organization must be reviewed by Rhombus. Contact your Rhombus representative or post in the Developer Community to start review.
Production OAuth applications require Rhombus review. You can self-register a clientId with the API call below for local development and testing, but do not distribute apps to end users with a self-registered clientId — Rhombus may rate-limit or revoke unreviewed production use. Start a review as soon as your prototype works.

Register with the API

POST /api/oauth/submitApplication returns a fresh clientId and clientSecret. Store the clientSecret securely — it is not retrievable later.
import requests

headers = {
    "x-auth-scheme": "api-token",
    "x-auth-apikey": "YOUR_API_KEY",
    "Content-Type": "application/json",
}

response = requests.post(
    "https://api2.rhombussystems.com/api/oauth/submitApplication",
    headers=headers,
    json={
        "name": "Acme Console",
        "description": "Acme internal dashboard for Rhombus operators",
        "contactEmail": "engineering@acme.example",
        "redirectUri": "https://acme.example/oauth/callback",
    },
)

data = response.json()
print("clientId:    ", data["clientId"])
print("clientSecret:", data["clientSecret"])  # store securely — not retrievable later
Example response:
{
  "clientId": "PJjjlcKAQPCzIcaeprzEVg",
  "clientSecret": "kixFP1l8c55dDt0WdeX8BNwUlnFknGTr9qdn3AYKpsM"
}
Additional endpoints are available to manage registered applications: getAllApplicationsForOrg, getApplicationByClientId, updateApplication, and deleteApplication. See the API Reference under the OAuth tag.

Step 2: Build the authorization URL

Rhombus uses PKCE (Proof Key for Code Exchange) to protect against authorization code interception. For every login attempt, generate:
  • A code verifier — a 43–128 character URL-safe random string
  • A code challenge — the SHA-256 hash of the verifier, base64url-encoded (without padding)
  • A state parameter — an unguessable random value used to prevent CSRF
Persist the codeVerifier and state alongside the user’s session (or, for CLI tools, in process memory) until the callback lands. You’ll need both.
import base64
import hashlib
import secrets
from urllib.parse import urlencode

def new_pkce_pair():
    verifier = secrets.token_urlsafe(64)  # 86 chars after base64
    digest = hashlib.sha256(verifier.encode("ascii")).digest()
    challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
    return verifier, challenge

client_id    = "PJjjlcKAQPCzIcaeprzEVg"
redirect_uri = "https://acme.example/oauth/callback"

code_verifier, code_challenge = new_pkce_pair()
state = secrets.token_urlsafe(32)

# Save code_verifier + state in the user's session before redirecting.

params = {
    "type":      "oauth",
    "client_id": client_id,
    "redirect":  redirect_uri,
    "state":     state,
    "challenge": code_challenge,
}
authorize_url = f"https://console.rhombussystems.com/login?{urlencode(params)}"
print(authorize_url)
Note that the authorization URL uses the parameter names client_id, redirect, state, and challenge — these differ slightly from standard OAuth parameter names (redirect_uri, code_challenge). Use the names shown above exactly.
Redirect the user’s browser to the URL you built. They’ll sign in to Rhombus and approve your application.

Step 3: Handle the redirect callback

After the user authenticates, Rhombus redirects to your redirectUri with query parameters:
ParameterDescription
codeAuthorization code to exchange for an access token. Short-lived.
stateThe state value you sent — you must verify it matches.
isPartner"true" if the authenticated user is a partner account holder. See partner accounts.
accessToken(sometimes present) A pre-exchanged access token. If set, skip the token exchange step and use it directly.
errorPresent on failure (e.g., access_denied).
error_descriptionHuman-readable error detail.
Always verify the state parameter matches what you sent. A mismatch indicates a possible CSRF attack — abort the flow.
from flask import Flask, request, session, abort

app = Flask(__name__)

@app.route("/oauth/callback")
def callback():
    if request.args.get("error"):
        return f"Login failed: {request.args['error_description']}", 400

    code  = request.args.get("code")
    state = request.args.get("state")
    is_partner = request.args.get("isPartner") == "true"

    if state != session.pop("oauth_state", None):
        abort(400, "state mismatch")

    code_verifier = session.pop("oauth_verifier", None)
    # Proceed to Step 4 — exchange `code` for an access token.
    ...

Step 4: Exchange the code for an access token

Call POST https://auth.rhombussystems.com/oauth/token with the authorization code and your PKCE verifier. This is a different host from the main API — the /oauth/token endpoint lives on auth.rhombussystems.com.
import requests

token_response = requests.post(
    "https://auth.rhombussystems.com/oauth/token",
    headers={
        "Content-Type": "application/json",
        "x-auth-scheme": "web2",
    },
    json={
        "grantType":         "AUTHORIZATION_CODE",
        "authorizationCode": code,
        "clientId":          client_id,
        "clientSecret":      client_secret,
        "redirectUri":       redirect_uri,
        "codeVerifier":      code_verifier,
        "codeChallengeType": "S256",
    },
)
token_response.raise_for_status()
tokens = token_response.json()

access_token  = tokens["accessToken"]
refresh_token = tokens["refreshToken"]
expires_in_s  = tokens["accessTokenExpirationSec"]
Example response:
{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "def50200...",
  "accessTokenExpirationSec": 3600
}

Step 5: Call the Rhombus API

Use the access token with two headers on every Rhombus API call:
  • x-auth-scheme: api-oauth-token
  • x-auth-access-token: <accessToken>
This is different from the standard API key scheme (api-token + x-auth-apikey) — OAuth access tokens use their own scheme identifier so Rhombus can apply OAuth-specific authorization.
import requests

headers = {
    "x-auth-scheme": "api-oauth-token",
    "x-auth-access-token": access_token,
    "Content-Type": "application/json",
}

response = requests.post(
    "https://api2.rhombussystems.com/api/user/getUsersInOrg",
    headers=headers,
    json={},
)

users = response.json().get("users", [])
for user in users:
    print(user.get("email"), "—", user.get("name"))
If this call returns a list of users, your OAuth flow is working end to end. The user has authenticated, you have an access token, and you’re calling the API on their behalf.

Access token lifetime

The accessTokenExpirationSec field on the token response tells you how long the access token is valid (typically one hour). When it expires, the Rhombus API will return an authentication error. For long-lived access — background services, daemons, scheduled jobs, or any client that cannot re-prompt the user — mint a durable API key using the OAuth access token (see next section) rather than trying to maintain a refreshed OAuth session. This is the pattern the Rhombus CLI uses.
A refreshToken is returned alongside the access token. The refresh flow is not currently part of the publicly supported surface — for persistent access, prefer the API key path below.

Mint a long-lived API key

Once a user has signed in with OAuth, you can trade the short-lived access token for a permanent API key. This is what rhombus login does so the CLI can continue making API calls after the browser session ends. Call POST /api/integrations/org/submitApiTokenApplication with x-auth-scheme: api-oauth-token and x-auth-access-token: <accessToken>:
import requests

response = requests.post(
    "https://api2.rhombussystems.com/api/integrations/org/submitApiTokenApplication",
    headers={
        "x-auth-scheme": "api-oauth-token",
        "x-auth-access-token": access_token,
        "Content-Type": "application/json",
    },
    json={
        "displayName": "Acme Console — background worker",
        "authType": "API_TOKEN",
    },
)

data = response.json()
api_key = data["apiKey"]  # store this securely — treat it like a password
From that point on, use the API key with the standard x-auth-scheme: api-token + x-auth-apikey headers — no further OAuth calls required. The CLI also supports a certificate-based (mTLS) variant of this flow for higher-security deployments; see cmd/login.go for the full implementation.
Treat minted API keys like passwords. They are long-lived and confer the same permissions as the user who created them. Store them encrypted at rest, never in source control, and rotate or delete unused keys.

Working with partner accounts

If the authenticating user is a partner account holder (an MSP or reseller managing multiple customer organizations), the callback includes isPartner=true. When isPartner is true:
  • Use x-auth-scheme: partner-api-oauth-token instead of api-oauth-token on API calls
  • Mint long-lived keys via POST /api/partner/submitApiTokenApplication instead of the standard integrations endpoint
  • To act on a specific client org, include the client org UUID in the appropriate request field (refer to Partner endpoints in the API Reference)
Partner accounts are relatively rare for third-party apps. If your app targets end customers with standard Rhombus orgs, you can safely treat isPartner=false as the default path and show a clear error if a partner account tries to log in when you don’t support it.

Reference implementation

The Rhombus CLI is a production reference for everything in this guide. cmd/login.go walks the full flow end to end: PKCE generation, local callback server, authorization URL construction, token exchange, partner branching, mTLS API-key minting, and credential persistence. If something in your implementation isn’t working, diff your behavior against the CLI — it’s the canonical example.

Troubleshooting

Your callback received a state value different from what you sent. Verify you’re persisting the state you generated in Step 2 alongside the user’s session (or in memory for CLI tools) and comparing it on the callback. A persistent mismatch can indicate a CSRF attempt — abort the flow rather than retrying silently.
Common causes:
  • redirectUri mismatch — the redirectUri in the token exchange body must match exactly (including scheme, host, port, and path) the redirect you sent in Step 2 and the URI registered with your OAuth application.
  • Expired code — authorization codes are short-lived (seconds, not minutes). Exchange them immediately on callback.
  • codeVerifier doesn’t hash to codeChallenge — verify you’re using SHA-256 and base64url encoding without = padding on both the challenge generation and the verifier transmission.
  • Wrong header — the token exchange requires x-auth-scheme: web2, not api-token or api-oauth-token.
Check the headers. OAuth access tokens use x-auth-scheme: api-oauth-token and x-auth-access-token: <token>. Using x-auth-apikey (the API key header) with an OAuth access token will fail — those are different schemes with different header names.
The client_id you’re sending may be unrecognized. Double-check you’re using the clientId returned from submitApplication, not the application UUID from a different response. If you rotated applications, the old clientId is no longer valid.
A partner account holder authenticated through a flow your app doesn’t support. Either implement the partner flow or show the user a clear error message explaining your app supports standard Rhombus orgs only.

Next steps

Rhombus CLI

Read how the official CLI uses this flow end to end

API Reference

Browse all endpoints available once you have an access token

Rate Limits

Understand request limits before you ship

Developer Community

Request production OAuth review and ask questions