Skip to main content
This guide walks through the minimum steps to integrate a backend service with PymtHouse. By the end you will have:
  1. A machine token issued via client credentials (RFC 6749 §4.4).
  2. An end-user provisioned in your app’s tenant.
  3. A user-scoped JWT with sign:job ready to pass to PymtHouse services.

Prerequisites

  • A registered developer app with a confidential M2M client (m2m_… id and pmth_cs_… secret). See Client model for how these relate.
  • curl and jq available locally, or any HTTP client.
  • Your PymtHouse deployment URL (local: http://localhost:3001).
export BASE_URL="https://your-pymthouse.example"
export M2M_ID="m2m_yourClientId"
export M2M_SECRET="pmth_cs_yourSecret"
export PUBLIC_CLIENT_ID="app_yourClientId"
For local development, BASE_URL is http://localhost:3001. The M2M and public client IDs are created from the developer dashboard or by running npm run oidc:seed during setup.

Step 1 — Obtain a machine token

Exchange your M2M client credentials for a short-lived access token. The token endpoint follows OIDC discovery and is stable at {issuer}/token.
MACHINE_TOKEN=$(curl -sS \
  -d "grant_type=client_credentials" \
  -d "client_id=${M2M_ID}" \
  -d "client_secret=${M2M_SECRET}" \
  -d "scope=users:write users:token" \
  "${BASE_URL}/api/v1/oidc/token" | jq -r '.access_token')

echo "Machine token: ${MACHINE_TOKEN:0:40}..."
Request only the scopes your operation requires. users:write is needed for provisioning; users:token is needed for minting user JWTs. Scope assignment is managed on the M2M client; ask the platform admin if a scope is missing.

Step 2 — Provision an end-user

Create or upsert a user record linked to your own user identifier (externalUserId). This call is idempotent: sending the same externalUserId again returns the existing record rather than creating a duplicate.
curl -sS \
  -H "Authorization: Bearer ${MACHINE_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "externalUserId": "user-123",
    "email": "alice@example.com",
    "status": "active"
  }' \
  "${BASE_URL}/api/v1/apps/${PUBLIC_CLIENT_ID}/users" | jq .
A 200 response confirms the user exists in the PymtHouse tenant. Store nothing from this response — externalUserId is your stable join key.

Step 3 — Mint a user-scoped JWT

Issue a short-lived access token on behalf of the user. This token carries the user’s identity as its sub and is scoped to the requested capability.
USER_JWT=$(curl -sS \
  -H "Authorization: Bearer ${MACHINE_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "sign:job" }' \
  "${BASE_URL}/api/v1/apps/${PUBLIC_CLIENT_ID}/users/user-123/token" \
  | jq -r '.access_token')

echo "User JWT: ${USER_JWT:0:40}..."
This token:
  • Has sub = the PymtHouse app-user record id.
  • Has client_id / azp = the public app_… client id.
  • Is bounded to the scopes configured on the public client — sign:job must be in its allowed_scopes.
Pass this token in an Authorization: Bearer header to downstream PymtHouse services.

What’s next

Client model

Deep-dive into the two-client pattern, scope table, and billing mode.

Machine access

Alternative auth patterns and how to keep machine tokens short-lived.

Device flow

Add browser-authenticated device sessions for CLI and limited-input clients.

Usage API

Query fee totals and per-user attribution for billing dashboards.