Skip to main content
PayPunch uses JSON Web Tokens (JWT). You obtain a token from a login endpoint, then send it on every authenticated request.

Token types

There are two principal token types, distinguished by the type claim inside the JWT:
TypeObtained fromUsed for
adminPOST /api/v1/auth/login (email + password)Bookkeeper admin endpoints (/api/v1/admin/*)
employeePOST /api/v1/auth/employee/verify (phone + PIN)Employee endpoints (/api/v1/employee/*)
A client token type also exists in the auth layer, but the documented /api/v1 endpoints only require admin or employee tokens.
Tokens are signed with HS256 and expire after JWT_EXPIRES_IN (default 7 days). The token payload includes userId, type, and — depending on the type — bookkeeperOrgId, companyId, and email.

Sending the token

Send the JWT in the Authorization header as a bearer token:
Authorization: Bearer eyJhbGciOiJ...
The header parser also accepts a raw token without the Bearer prefix, but Bearer <token> is the recommended, standard form.
The login endpoints also set an httpOnly cookie named auth-token (7-day max-age, SameSite=Lax, Secure in production). If no Authorization header is present, the server falls back to this cookie. Browser-based clients on the same origin are therefore authenticated automatically; API integrations should prefer the explicit Authorization header. The header takes priority over the cookie when both are present.

Authorization rules

Endpoints enforce the required token type. Requesting an admin endpoint with an employee token (or vice-versa) is rejected.
Missing token
401 Authentication required
No Authorization header and no auth-token cookie, or the token is invalid or expired.
Wrong token type
401 / Access denied
Token is valid but its type claim does not match the endpoint’s required type (Access denied. Required role: <type>).
Cross-tenant access
403 Access denied
Admins can only read/write data belonging to their own bookkeeperOrgId; employees only their own company. Accessing another tenant’s resource returns 403.
Some endpoints are public or have optional auth — for example GET /api/v1/admin/industries works with or without a token.

Example: authenticate and call an endpoint

curl -X POST https://app.paypunch.io/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "sarah@acmebookkeeping.com",
    "password": "Admin123!"
  }'
The token is in data.token of the login response.

Admin login

Full reference for POST /api/v1/auth/login.