Introduction

Authentication

The Puntjes API uses OAuth2 with the client credentials grant for authentication. This is a machine-to-machine flow designed for POS systems, backend services, and integrations that act on behalf of a vendor.


Obtaining credentials

API clients are created and managed through the Puntjes admin portal. Navigate to API Clients in the sidebar to:

  • Create a new API client with a label
  • View your client ID
  • Reveal or regenerate your client secret
  • Revoke compromised clients

Each OAuth client is bound to a specific vendor. When you authenticate with a client's credentials, the API scopes all operations to that vendor automatically.


Requesting an access token

Exchange your client credentials for an access token by sending a POST request to the OAuth token endpoint:

curl -X POST https://puntjes.app/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=client_credentials \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET

Response

{
    "token_type": "Bearer",
    "expires_in": 3600,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJS..."
}
FieldDescription
token_typeAlways Bearer
expires_inToken lifetime in seconds (default: 3600 = 60 minutes)
access_tokenThe JWT token to include in API requests

Using the token

Include the access token in the Authorization header of every API request:

curl https://puntjes.app/api/v1/customers/lookup?identifier=CARD-001 \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJS..."

Token expiration

Access tokens expire after 60 minutes. When a token expires, the API returns a 401 Unauthorized response. Your integration should:

  1. Catch 401 responses
  2. Request a new access token
  3. Retry the original request

Token caching

Request a new token only when needed. Cache the token and reuse it until it expires or you receive a 401 response. Avoid requesting a new token for every API call.


Vendor scoping

Authentication implicitly determines which vendor's data you access. The OAuth client is linked to a vendor, so:

  • All customers, transactions, rewards, and campaigns returned are scoped to your vendor
  • You cannot access another vendor's data
  • Creating resources (customers, transactions) automatically associates them with your vendor

Vendor status requirements

Your vendor account must be in active status to use the API. If the vendor is:

  • Suspended: API access continues during the grace period (configured per vendor), then returns 403 VENDOR_SUSPENDED
  • Deactivated: API access is immediately blocked with 403 VENDOR_DEACTIVATED
  • Pending: API access is not available until the vendor is activated

A separate check sits beside these. When a paid period has lapsed and the workspace's subscription has ended, reads keep answering 200 and every write answers 403 SUBSCRIPTION_EXPIRED. Retrying does not help: the route back is a person choosing a plan. A vendor admin does that under Billing, and that is why this is a 403 rather than one of the 429s that clear on their own.


Security best practices

  • Store client secrets securely: never expose them in frontend code or version control
  • Use HTTPS for all API communication
  • Rotate client secrets periodically via the admin portal's Regenerate Secret feature
  • Revoke compromised clients immediately
  • Give each POS terminal or integration its own API client, so you can tell which system did what