Skip to Content
REST API

REST API

WebPinch exposes a versioned JSON API at /api/v1/*. Authenticate with a personal access token.

Base URL

EnvironmentURL
Productionhttps://www.webpinch.com
Local devhttp://localhost:3000

All examples below use https://www.webpinch.com — swap the host for local testing.

Authentication

Authorization: Bearer wp_pat_...

Or, if setting Authorization is awkward in your environment:

X-Webpinch-Token: wp_pat_...

Every endpoint below requires a PAT. Cookie sessions and the legacy mobile JWT do not work here — they’re scoped to the internal /api/* routes that power the dashboard.

The one exception is POST /api/v1/auth/login, which takes credentials rather than a token — it’s how you obtain a PAT in the first place.

Response envelope

Successful responses always return one of:

{ "data": { /* payload */ } }
{ "data": [ /* items */ ], "meta": { "page": 1, "limit": 50, "total": 134 } }

Errors look like:

{ "error": { "code": "INSUFFICIENT_SCOPE", "message": "..." } }

The HTTP status code reflects the error category (401, 403, 404, 409, 500). The code field is stable and machine-readable; the message is human-readable and may be tweaked over time.

Error codes

HTTPCodeMeaning
400BAD_REQUESTMissing or malformed body / query param
401UNAUTHORIZED / INVALID_TOKENNo token, or token revoked / expired / typo’d
401API_TOKEN_REQUIREDYou sent a non-PAT credential to a v1 endpoint
403INSUFFICIENT_SCOPEThe token is valid but lacks the required scope
404NOT_FOUNDResource doesn’t exist or isn’t visible to you
409CONFLICTE.g. starting an audit while one is already running
500INTERNAL_ERRORServer-side failure — open an issue with the request id

Identity

GET /api/v1/me — required scope: none

Returns the authenticated user, the token’s name and scopes, and every org and project the token can see.

curl https://www.webpinch.com/api/v1/me \ -H "Authorization: Bearer $TOKEN"
{ "data": { "user": { "id": "...", "name": "Jane", "email": "jane@acme.com" }, "token": { "name": "Claude Code on MacBook", "scopes": ["tasks:read", "tasks:write"] }, "orgs": [{ "id": "...", "slug": "acme", "name": "Acme" }], "projects": [{ "id": "...", "name": "Acme Marketing Site", "orgId": "...", "orgSlug": "acme" }] } }

Call whoami first when building a new integration — it tells you which projects and scopes you have available before you start hitting endpoints that might 403.

POST /api/v1/auth/login — required scope: none

Exchanges credentials for a personal access token. This is what the mobile app uses; it’s also the only endpoint here that doesn’t need a token already.

Accepts either an email/username and password, or a Google ID token:

curl -X POST https://www.webpinch.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"identifier": "jane@acme.com", "password": "..."}'
{ "data": { "token": "wp_pat_...", "user": { "id": "...", "name": "Jane", "email": "jane@acme.com" } } }

The token this returns is valid for one year and carries every scope. Prefer minting a scoped token from Dashboard → API Tokens for anything that isn’t the mobile app — a leaked all-scopes token can do everything your account can.

Projects

GET /api/v1/projectsprojects:read

List projects the token can access.

curl https://www.webpinch.com/api/v1/projects \ -H "Authorization: Bearer $TOKEN"

GET /api/v1/projects/:idprojects:read

Detail including kanban columns and members.

curl https://www.webpinch.com/api/v1/projects/$PROJECT_ID \ -H "Authorization: Bearer $TOKEN"
{ "data": { "id": "...", "name": "Acme Marketing Site", "url": "https://acme.com", "status": "active", "priority": "high", "color": "indigo", "orgId": "...", "orgSlug": "acme", "columns": [ { "key": "backlog", "label": "Backlog", "position": 0, "color": "gray" }, { "key": "in_progress", "label": "In progress", "position": 1, "color": "blue" }, { "key": "done", "label": "Done", "position": 2, "color": "green" } ], "members": [ { "userId": "...", "role": "owner", "joinedAt": "..." } ] } }

The columns[].key values are the valid status values you can pass to task endpoints. Different projects may have different column keys — always discover before writing.

GET /api/v1/projects/:id/membersprojects:read

The project’s members with their roles, resolved to names and emails.

curl https://www.webpinch.com/api/v1/projects/$PROJECT_ID/members \ -H "Authorization: Bearer $TOKEN"

Tasks

GET /api/v1/taskstasks:read

List tasks across all accessible projects, with filters and pagination.

Query paramExampleNotes
projectId679f…Limit to one project
statusin_progressKanban column key. Use get_project to discover.
priorityhighOne of low, medium, high, critical
assigneeId679f…User id
labelbugLabel name
reporterEmailqa@acme.comFor tasks submitted via the public widget
createdSince2026-04-01T00:00:00ZISO 8601
qlogin formCase-insensitive substring on title + description
page2Default 1
limit50Default 50, max 100
curl "https://www.webpinch.com/api/v1/tasks?projectId=$PID&status=in_progress&limit=10" \ -H "Authorization: Bearer $TOKEN"

Returns a compact task list. To get the full description, comments, attachments, etc., follow up with GET /tasks/:id.

GET /api/v1/tasks/:idtasks:read

Full detail including comments, checklists, attachments, assignees, reporter, screenshot/video URLs, and the visual pin coordinates from the Chrome extension.

POST /api/v1/taskstasks:write

Create a task.

curl https://www.webpinch.com/api/v1/tasks \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "projectId": "679f...", "title": "Login button is misaligned on mobile", "description": "Visible at < 375px width", "priority": "high", "labels": [{ "name": "bug", "color": "red" }], "pageUrl": "https://acme.com/login" }'

Returns 201 Created with the new task object. If status is omitted, the task lands in the project’s first kanban column.

PATCH /api/v1/tasks/:idtasks:write

Update any subset of fields. Only the fields you pass are changed.

curl -X PATCH https://www.webpinch.com/api/v1/tasks/$TASK_ID \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "done", "priority": "low" }'

Updatable fields: title, description, descriptionHtml, descriptionJson, status, priority, labels, assigneeIds, dueDate, dueDateComplete, pageUrl, screenshotUrl, videoUrl.

POST /api/v1/tasks/:id/commentstasks:write

curl https://www.webpinch.com/api/v1/tasks/$TASK_ID/comments \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "body": "Reproduced on Safari 17 too." }'

POST /api/v1/tasks/:id/attachmentstasks:write

Attach a file to a task.

GET /api/v1/tasks/:id/time-entriestasks:read

List time logged against a task.

POST /api/v1/tasks/:id/time-entriestasks:write

Log time against a task.

curl https://www.webpinch.com/api/v1/tasks/$TASK_ID/time-entries \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "minutes": 45, "note": "Traced the layout shift" }'

PATCH /api/v1/tasks/:id/checklists/:checklistId/items/:itemIdtasks:write

Tick or untick a single checklist item.

Site audits

GET /api/v1/projects/:id/auditsaudits:read

List audits for one project. Optional ?limit= (default 20, max 100).

POST /api/v1/projects/:id/auditsaudits:run

Start a new audit. The project must have a url configured.

curl https://www.webpinch.com/api/v1/projects/$PID/audits \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "maxDepth": 2, "maxPages": 50 }'

Returns 201 with { "data": { "id": "...", "status": "queued" } }. The audit runs in the background — poll GET /api/v1/audits/:id for progress.

If an audit is already running on this project, returns 409 CONFLICT.

GET /api/v1/audits/:idaudits:read

Full audit report. Status moves through queued → crawling → analyzing → complete (or failed).

{ "data": { "id": "...", "status": "complete", "progress": 100, "config": { "maxDepth": 2, "maxPages": 50, "rootUrl": "https://acme.com" }, "crawl": { "pageCount": 47, "hasRobotsTxt": true, "hasSitemap": true, "pages": [...] }, "linkReport": { "totalLinks": 312, "workingLinks": 298, "brokenLinks": 14, "brokenList": [...] }, "seoReport": { "overallScore": 78, "categories": [...], "aiTips": "..." }, "generalReport": { "checks": [...], "aiSummary": "..." } } }

POST /api/v1/audits/:id/reanalyzeaudits:run

Re-runs the analyzer (SEO scoring, general checks) on the existing crawl data without re-crawling. Useful when WebPinch ships analyzer improvements.

GET /api/v1/orgs/:slug/auditsaudits:read

Org-wide audit list across every project in the org.

Stats

GET /api/v1/statsstats:read

Aggregate counts across all your accessible projects.

{ "data": { "totals": { "projects": 7, "tasks": 134, "completedTasks": 89, "inProgressTasks": 12, "overdueTasks": 3, "tasksDueSoon": 8, "completionRate": 66 }, "projectsByStatus": { "active": 5, "completed": 1, "on_hold": 1, "archived": 0 }, "tasksByStatus": { "backlog": 33, "in_progress": 12, "done": 89 }, "tasksByPriority": { "low": 20, "medium": 60, "high": 40, "critical": 14 }, "recentProjects": [ /* up to 5, with progress % */ ] } }

GET /api/v1/orgs/:slug/statsstats:read

Same shape, scoped to one organization.

Rate limits

Rate limiting is not enforced in the current release but the response will include the headers below once it is, so it’s safe to start respecting them now.

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetUnix epoch when the window resets

Planned defaults per token:

  • 120 requests/minute on read endpoints
  • 30 requests/minute on write endpoints
  • 5 requests/minute on audits:run

Versioning and deprecation

  • The version segment (/api/v1) is part of the contract. Breaking changes ship as /api/v2; v1 keeps working for at least 12 months after v2 lands.
  • Adding new optional fields to a response is not a breaking change. Build clients that ignore unknown fields.
  • Removing a field, renaming a field, or changing a field’s type IS breaking and will only happen across major versions.

See also

Last updated on