/api/v1 route handlers.
This API is for programmatic access. The PayPunch web app and mobile
clients use these same endpoints. Treat tokens and credentials as secrets.
Base URL
Every endpoint is mounted under/api/v1 on your PayPunch deployment.
NEXT_PUBLIC_APP_URL resolves to. All
paths in this reference are written relative to the /api/v1 prefix
(for example, POST /auth/login is POST {baseURL}/api/v1/auth/login).
Versioning
The version is encoded in the URL path (/api/v1/...). A breaking change ships
under a new prefix (/api/v2), so integrations pinned to v1 keep working.
Response envelope
Every endpoint returns the same JSON envelope:true when the request succeeded, false otherwise. Always present.The result payload. Present on success. Its shape is documented per endpoint.
A short error label (for example,
"Authentication required"). Present on failure.An optional, more detailed human-readable message. May appear on both success
and failure responses.
Authentication responses
Login and verification endpoints embed the JWT insidedata alongside the
returned record:
Paginated list responses
List endpoints (such as bookkeeper orgs and pay periods) wrap results in a pagination object insidedata:
Error codes
Errors use standard HTTP status codes with the envelope above (success: false).
| Status | error example | When it happens |
|---|---|---|
400 | Invalid login data / Overlapping pay period | Validation failed (Zod), or a business rule was violated. message lists the offending fields. |
401 | Authentication failed / Authentication required | Missing/invalid credentials, or a missing/expired token. |
403 | Access denied / Account inactive | Authenticated but not allowed (wrong tenant, deactivated account/org/company). |
404 | Pay period not found | The requested resource does not exist. |
429 | Too many login attempts | Rate limit exceeded (login only — see below). |
500 | Login failed / Failed to fetch ... | Unexpected server error. |
message of the
form field: reason, field: reason.
Rate limits
The login endpoint is rate limited by client IP: 5 attempts per 15 minutes. When exceeded it returns429 with these headers:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Maximum attempts allowed in the window. |
X-RateLimit-Remaining | Attempts remaining in the current window. |
X-RateLimit-Reset | ISO-8601 timestamp when the window resets. |
Rate limiting uses Upstash Redis when
UPSTASH_REDIS_REST_URL /
UPSTASH_REDIS_REST_TOKEN are configured, otherwise an in-memory limiter
(per-instance). The login limit is the only limit enforced inside the
documented /api/v1 handlers; broader per-IP API/public limits exist in the
codebase but are not applied by these specific routes.Next steps
Authentication
How to obtain and send a JWT for admin and employee requests.
Admin: Bookkeeper Orgs
List, create, read, update, and deactivate bookkeeper organizations.