DEVELOPER

Paidwell API

A small REST API for logging consulting time, reading entries, managing projects, and pulling hours/revenue rollups. Any tool that can make an authenticated HTTP request — an agent platform, a cron script, curl — can drive it.

Using Claude, ChatGPT, or Cursor? Use the MCP server

Paidwell ships a remote MCP server at /mcp (streamable HTTP, OAuth sign-in — no key handling). Add it as a custom connector and you get job-shaped tools — log_call, whats_unbilled, find_gaps — plus a drafts workflow where inferred work waits for your approval. Per-client setup lives in Settings → Connect your AI.

Scripts and custom agents: one-step key setup

In Settings → API access create a key (pick a scope: full, log-only, or read-only), then hit Copy agent setup prompt. That copies a full instruction set — with your key and base URL baked in — that any agent can use to build a Paidwell skill. No manual wiring.

AUTHENTICATION

Every request needs a Paidwell API key (mint one under Settings → API access; the API is free on every plan, Free tier included). Send it as either header. Keys are secret — they can create, edit, and delete your billing data. Only a SHA-256 hash is stored; the raw paidwell_sk_… secret is shown once, at creation.

Keys are bound to the workspace that minted them. Personal keys stay personal. Team keys act as the member who created them inside that team, and are denied if that member is removed. Member-bound team keys see only assigned projects and their own entries, receive time-only summaries, and cannot create projects or touch billed status / invoice references.

Authorization: Bearer paidwell_sk_…
# or
X-API-Key: paidwell_sk_…

Base URL: /api/v1 on this host. Verify a key:

curl https://<your-paidwell-host>/api/v1/ping \
  -H "Authorization: Bearer paidwell_sk_…"
# → {"ok":true,"service":"paidwell","authenticatedAs":"<label>", …}
ENDPOINTS

All paths under /api/v1. Bodies and responses are JSON. Every request is scoped to the workspace bound to the key.

GET/ping
Health/auth check. Returns the authenticated key’s label.
GET/projects
List projects. Optional ?archived=true|false. Project rows include a rates array. Member-bound team keys list only assigned projects and omit project and rate hourly values.
POST/projects
Create a project. Only name is required; call-billing fields are optional. Team project creation requires an owner/admin key.
{ "name": "Acme", "client": "Acme Inc", "hourlyRate": 200,
  "callMinimumMinutes": 60, "callPaddingMinutes": 60, "roundToMinutes": 60 }
GET/entries
List entries. Query params: projectId, from, to (YYYY-MM-DD, inclusive), billable, billed, search, limit (≤1000, default 100), proposed(true lists the draft approve-queue; drafts are excluded by default). Member-bound team keys return only the bound member’s own entries in assigned projects and omit money fields.
POST/entries
Log time — the main surface. Identify the project by projectId or project (name, case-insensitive); an optional non-Standard rate by rateId or rate (its label). Express duration three ways, and always pass an explicit date.
# human duration string
{ "project": "Acme", "description": "Architecture review", "duration": "1h30m", "date": "2026-05-31" }

# explicit minutes
{ "project": "Acme", "description": "Bugfix", "durationMinutes": 45, "date": "2026-05-31" }

# a CALL — applies the project's billing rules (minimum → padding → rounding)
{ "project": "Acme", "description": "Kickoff call", "isCall": true, "bookedDuration": "1h" }

Two agent-trust flags: "proposed": true lands the entry in the user’s in-app Inbox as a draft (invisible to every report until approved — use for anything inferred), and "dryRun": true computes the billing math and returns the would-be entry without saving anything.

Optional startedAt stores when work began as a UTC ISO instant ending in Z; omit it or send null when there is no specific start time.

Member-bound team keys cannot send billed or invoiceRef; those are owner/admin-only billing fields.

GET/entries/{id}
Read one entry.
PATCH/entries/{id}
Partial update. Any of description, date, durationMinutes, billable, billed, invoiceRef (send null to clear it), proposed (only false — approving a draft; an entry can never be demoted back to a draft). Member-bound team keys cannot edit billed entries or change billed status / invoice references.
DELETE/entries/{id}
Delete one entry.
GET/summary
Hours + revenue rollup, overall and per project. Optional from, to, projectId, authorId, and byAuthor for owner/admin team reports. Returns { totals: { hours, billableHours, unbilledHours, revenueEstimate, unbilledRevenueEstimate, entryCount }, byProject: [...], byAuthor?: [...] }. Member-bound team keys receive the same time fields without revenue fields.
GET/gaps
Weekdays with no (or under-threshold) logged time — for cross-referencing against a calendar. Required from, to (≤92 days); optional minHours. Weekends skipped; future days are never gaps; drafts don’t count as logged.
KEY SCOPES

Keys carry a least-privilege scope chosen at mint time: full (read, log, edit, billing — your own agents), log (read + create entries only — a calendar bot), or read(read-only — a bookkeeper’s agent). Out-of-scope requests return 403. Team member keys are additionally capped by the member role: assigned projects only, time-only summaries, no project creation, and no billed/invoice-reference mutations.

CALL BILLING

When isCall: true, the booked duration is transformed by the project’s rules in order: minimum → + padding → round to nearest N (floored at one unit). Example: a 1h booked call with min 60, padding 60, round 60 bills 2h. A short call never collapses below one rounding unit.

ERRORS

Errors return { "error": { "code", "message" } } with a matching HTTP status: 400 bad_request · 401 unauthorized · 403 forbidden (revoked key, out-of-scope operation, plan limits, member-role limits, or removed team membership) · 404 not_found · 429 rate_limited (>120 req/min per key) · 500 server_error.

Get an API keyopenapi.json