API Reference
Complete integration guide and OpenAPI specifications for the VeriWorkly backend.
VeriWorkly API Reference
Welcome to the official API documentation for VeriWorkly.
Our backend powers a secure, privacy-first, and highly scalable resume platform. Designed with strict RESTful principles, this API lets you manage profiles, resumes, portfolios, AI generation, ATS scanning, billing, and share links.
Base URLs
All API endpoints are relative to the following base URLs depending on your environment. All routes are prefixed with /api/v1.
https://api.veriworkly.com/api/v1Authentication
There are two ways to authenticate, and which one you need depends on where the request comes from.
1. API keys — for scripts, integrations, and these docs
Send your key in either header; both are accepted:
X-API-Key: vw_...
Authorization: Bearer vw_...Keys are vw_ followed by 64 hexadecimal characters, are scope-gated, and default to 20 requests
per 15 minutes. See API Keys for scopes and lifecycle.
2. Session cookies — for the VeriWorkly dashboard
HttpOnly cookies issued by Better Auth, honoured only for whitelisted first-party origins.
- Local Environment:
veriworkly-auth.session_token - Production Environment:
__Secure-veriworkly-auth.session_token
Important for Frontend Integration
If you are calling this API from a browser or a frontend framework (like Next.js client components), you must configure your fetch requests to include cookies: fetch(url, { credentials: 'include' }). Axios users must set withCredentials: true.
“Public” endpoints still need credentials
Endpoints described as public — public portfolios, public share links, the roadmap, GitHub stats —
need no logged-in user, but they are not open to anonymous callers. Without an API key or a
whitelisted first-party Origin/Referer they return 401. The only genuinely unauthenticated
endpoints on the whole API are GET /health and GET /health/ready.
Auth routes are the one exception to the response format
Endpoints under /api/v1/auth/* are served by Better Auth and return its payload shapes, not
the { success, message, data } envelope described below.
Core Concepts
Optimistic Concurrency Control
To prevent data loss when editing across browser tabs or devices, VeriWorkly implements Optimistic Concurrency Control — but the token you send differs by resource:
| Resource | Field you send | Type | Source |
|---|---|---|---|
Master Profile (PUT /profiles/master) | expectedUpdatedAt | timestamp | The updatedAt you last read. |
Documents (PATCH /documents/{id}) | revision | integer | The document's current revision. |
Portfolio drafts (PUT /portfolios/draft) | revision | integer | The draft's current revision. |
If your value matches the server's, the write succeeds — and for documents and drafts, revision
is incremented by 1. If it doesn't, the API rejects the request with 409 Conflict; re-read the
resource, reapply your changes, and retry.
PUT /profiles/master is a full replacement, not a patch. Its payload is validated strictly:
every property must be present, and unknown keys are rejected. Read the profile first, modify the
object you got back, and send the whole thing.
Document Export Is Client-Side, Not a Background Job
PDF, DOCX, HTML, Markdown, and JSON export are generated entirely in the browser (via react-pdf for PDF and the docx package for Word documents) — there is no server-side rendering job, queue, or webhook involved in producing an exported file. The API's own background jobs (GitHub sync, usage-metrics flush, portfolio access checks) are internal, Redis-lock-guarded scheduled tasks, not something a client polls or receives a webhook for.
Standardized Responses
Every endpoint adheres to a strict, predictable JSON response format, heavily validated by Zod.
Success Response
Successful requests will return a 200 OK status code and a payload containing success: true.
{
"success": true,
"message": "Roadmap features fetched successfully",
"data": {
"totalFeatures": 10,
"todo": 3,
"inProgress": 4,
"done": 3,
"completionRate": "30.00"
}
}Error Handling & Validation
Failed requests return success: false. When the failure is a validation error, a details array pinpoints the exact structural failure. details is omitted entirely for non-validation errors.
{
"success": false,
"message": "Validation failed",
"statusCode": 400,
"details": [
{
"path": "limit",
"message": "Number must be greater than or equal to 1"
}
]
}Common HTTP Status Codes
200 OK: The request was successful.201 Created: A resource was created — document creation, share links, API keys, publishing a portfolio.202 Accepted: Queued for asynchronous processing (portfolio view tracking).400 Bad Request: Validation error (missing parameters, malformed body, unsupported scope).401 Unauthorized: No valid session and no valid API key, or the session expired.402 Payment Required: Not enough AI credits for the requested action.403 Forbidden: Authenticated, but not allowed — an API key missing a required scope, a non-admin calling an admin route, or a feature gated to subscribers.404 Not Found: The requested resource does not exist, or is not yours.408 Request Timeout: Resume text extraction took too long.409 Conflict: Optimistic concurrency failure, or a uniqueness clash (slug/subdomain already in use).410 Gone: The share link expired.413 Payload Too Large: Profile or document content exceeded the 1 MB limit.429 Too Many Requests: Rate limit exceeded. Check theRetry-Afterheader.502 Bad Gateway: An upstream provider (AI or GitHub) failed.503 Service Unavailable: A dependency is unreachable, a feature flag is off, or a required integration is unconfigured.
Rate Limiting
Limits are enforced per IP, and additionally per key for API-key traffic. Rate limiting is disabled entirely in development.
| Scope | Limit |
|---|---|
| Default (per route, per IP) | 100 requests / 15 min |
| Global ceiling (per IP) | 1000 requests / 15 min |
| Per API key | 20 requests / 15 min |
/api/v1/auth/* | 20 requests / min |
/api/v1/ai/* | 20 requests / min |
/api/v1/contact | 5 requests / hour |
/api/v1/stats/events | 15 requests / min |
| Share-link password verification | 3 requests / 5 min |
The default limit is keyed by (method, path, IP), so it bounds each endpoint independently rather than a client's total load. The global ceiling sits underneath every per-route bucket. The Dodo billing webhook is exempt from IP rate limiting — it is authenticated by HMAC signature, and throttling a provider retry storm would diverge payment state from reality.
A 429 response carries Retry-After. API-key traffic also gets X-RateLimit-Limit,
X-RateLimit-Remaining, and X-RateLimit-Reset.
Two rate-limited routes are not in this reference
/api/v1/contact (the marketing site's contact form) and /api/v1/stats/events (usage-metric
ingestion) are internal first-party surfaces, listed above only because you may see their limits.
So is everything under /api/v1/admin/*. None of them are part of the supported public API.
Pagination
Endpoints that return lists (like /roadmap, /github/issues, /api-keys, or
/shares/documents/{documentId}) use Offset Pagination. Paginated payloads put the rows in
items alongside the metadata below.
You can page two equivalent ways — limit/offset, or page/pageSize. The response echoes
both. Page size defaults to 20 and is capped at 50; on /roadmap and /github/issues a limit
above 50 is rejected with 400 rather than clamped.
{
"items": [],
"total": 120,
"limit": 20,
"offset": 0,
"page": 1,
"pageSize": 20,
"totalPages": 6,
"hasMore": true,
"pagination": {
"mode": "offset",
"nextOffset": 20,
"nextCursor": null
}
}nextCursor is always null — these endpoints are offset-paginated, and the field exists only to
keep the envelope stable.
Explore the API
Dive into the specific modules to see precise schemas, request bodies, and live examples powered by our OpenAPI integration.
Health & Diagnostics
A cheap liveness probe for uptime monitoring, plus a deeper readiness probe that verifies PostgreSQL and Redis.
Authentication
Passwordless email-OTP sign-in, session retrieval, and sign-out, powered by Better Auth.
API Keys
Create, rotate, revoke, and scope the keys used for programmatic access.
Users Module
Protected operations for authenticated users. Fetch profile metadata and manage account configurations.
Roadmap Tracker
Query public roadmap features, filter by development status, and aggregate completion statistics.
Changelog
Read published release notes as structured change buckets, filter by version type or tag, and aggregate contributor statistics.
GitHub Integrations
Fetch live repository statistics, paginated issues, and pull requests directly from the VeriWorkly repo.
Master Profile
Read and update the canonical profile that new resumes, cover letters, and portfolios are seeded from.
Profile Import
Import a profile from a real GitHub OAuth connection or an AI-parsed LinkedIn text/PDF paste.
Documents
Create, update, and soft-delete resumes, cover letters, portfolios, and link-in-bio pages.
ATS Scanning
Run a deterministic ATS readiness scan or a deeper AI-powered analysis against a job description.
AI Generation
List available AI writing actions and their credit costs, then generate content with a reserve-then-commit credit flow.
Portfolios
Save drafts, publish to a subdomain, and view analytics for a public portfolio page.
Portfolio Assets
Presigned image uploads for portfolio avatars, project covers, and social share images.
Billing
Manage subscriptions, the AI credit wallet, and Dodo Payments checkout/portal sessions.
Shares
Create public, optionally password-protected share links for any document.
Affiliates
Enroll as an affiliate, track referrals and commissions, and request withdrawals.
Ambassador Program
Apply to the campus ambassador program and check your application status.