v3 → v4 Migration Guide

This guide covers the breaking changes and new features when migrating from ProWorkflow API v3 (classic) to v4 (Nexus). The most significant change is the replacement of simple status booleans with the richer activeworkstate enum across all entities.

Breaking Changes

The status parameter values have changed for Projects, Items, Invoices, and Quotes. Your integration may need to be updated to use the new enum values.

Quick Summary

ChangeImpactEffort
Base URLBreakingFind & replace
Status valuesBreakingUpdate filter values
Company address fieldsBreakingUpdate to use Company Locations
Work StagesNewOptional adoption
Company LocationsNewOptional adoption
New fields & filtersNewOptional
AuthenticationChangedMinimal
AT A GLANCE
Before (v3)
GET /api/v3/projects?status=active
GET /api/v3/invoices?status=unpaid
GET /api/v3/quotes?status=approved
After (v4)
GET /api/v4/projects?status=active
GET /api/v4/invoices?status=submitted
GET /api/v4/quotes?status=accepted

Base URL Change Breaking

The API base URL has been updated from v3 to v4.

VersionBase URL
v3https://api.proworkflow.net
v4https://api.proworkflow.com/api/v4/

Tip

Store the base URL in a configuration variable so updating is a single change.

Migration
// Before
const BASE_URL = "https://api.proworkflow.net/";

// After
const BASE_URL = "https://api.proworkflow.com/api/v4";

Response Format Changed

All v4 responses use a uniform envelope. v3 returned resource-keyed payloads (e.g. "projects": [...], "invoices": [...]); v4 always uses data regardless of resource.

List Responses

KeyDescription
statusAlways lowercase "success" or "error"
dataArray of result objects
metaObject containing count, total, page, pagesize (when paginated)

Single-Item Responses

Single-resource endpoints (GET /projects/{id}) return data as an object instead of an array.

Error Responses

Handler-level errors (validation, permission, not-found) use the same envelope, with data populated as an array of message strings:

{"status": "error", "data": ["msg1", "msg2"]}

Middleware-level errors (authentication failures, rate limiting) bypass the envelope and return the framework default:

{"message": "Invalid bearer token"}

Client code should handle both shapes.

Migration Required

Replace any v3 client code that reads response.projects, response.invoices, response.count, or response.totalcount with response.data and response.meta.count / response.meta.total.

List Response
{
  "status": "success",
  "data": [
    { "id": 1, "name": "Acme Corp" },
    { "id": 2, "name": "Globex" }
  ],
  "meta": {
    "count": 2,
    "total": 145,
    "page": 1,
    "pagesize": 50
  }
}
Single-Item Response
{
  "status": "success",
  "data": {
    "id": 42,
    "name": "Website Redesign",
    "activeworkstate": "active"
  }
}
Handler Error
{
  "status": "error",
  "data": [
    "Invalid status value",
    "pagesize must be a positive integer"
  ]
}
Middleware Error
// 401 Unauthorized, 429 Too Many Requests, etc.
{
  "message": "Invalid bearer token"
}

Status → activeworkstate Breaking

The v3 API used simple boolean or limited string status fields (JobComplete, Paid, QuoteStatus). The v4 API introduces activeworkstate — a richer enum system with more granular states per entity.

As a query filter, the parameter is named status (an alias for activeworkstate — both work). In response bodies, the field is named activeworkstate.

Important

The v3 value active still works for Projects and Items. However, Invoice and Quote status values have changed completely.

Full Status Mapping

Entityv3 Valuesv4 Values (activeworkstate)
Projectsactive, complete, allactive, complete, deleted, all
Items/Tasksactive, complete, allactive, complete, deleted, all
Invoicespaid, unpaid, alldraft, submitted, authorised, paid, voided, deleted, all
Quotespending, approved, declined, allpending, sent, accepted, addedtoproject, declined, expired, deleted, all
STATUS RESPONSE EXAMPLE
v4 Response
{
  "status": "success",
  "data": [
    {
      "id": 123,
      "title": "Website Redesign",
      "activeworkstate": "active"
    }
  ],
  "meta": {
    "count": 1,
    "total": 1
  }
}

Projects

Project status is derived from Jobs.activeworkstate. When status is omitted, only active projects are returned. Pass status=all to include completed projects.

v4 StatusDescriptionv3 Equivalent
activeActive, in-progress projectactive
completeCompleted projectcomplete
deletedSoft-deleted projectNot available
allAll non-deleted, non-proposal projectsall

Proposals

Proposal projects are not exposed via the /projects endpoint. Use a dedicated proposals endpoint if available, or filter by work stage.

Backwards Compatible

The v3 values active, complete, and all work identically in v4.

cURL
# List active projects (default)
curl 'https://api.proworkflow.com/api/v4/projects?status=active' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'

# List all non-deleted projects
curl 'https://api.proworkflow.com/api/v4/projects?status=all' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'

Items / Tasks

Item (task) status is derived from Item.activeworkstate. Uses the same enum as Projects. When status is omitted, only active items are returned. Pass status=all to include completed items.

v4 StatusDescriptionv3 Equivalent
activeActive taskactive
completeCompleted taskcomplete
deletedSoft-deleted taskNot available
allAll non-deleted tasksall
cURL
# List active tasks assigned to me
curl 'https://api.proworkflow.com/api/v4/items?status=active&contacts=me' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'

# List completed tasks with project info
curl 'https://api.proworkflow.com/api/v4/items?status=complete&fields=title,project,dates' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'

Invoices Breaking

Invoice status has changed significantly. The v3 binary paid/unpaid model is replaced by a workflow-based state machine.

v4 StatusDescriptionv3 Equivalent
draftInvoice created, not yet sentNot available
submittedSent to clientunpaid
authorisedApproved / authorized for paymentunpaid
paidPayment receivedpaid
voidedCancelled / voidedNot available
deletedSoft-deletedNot available
unpaidConvenience filter — matches submitted + authorisedunpaid
allAll non-deleted invoicesall

Default

When status is omitted, all non-deleted invoices are returned (equivalent to status=all).

"unpaid" Behavior Difference

v4 retains status=unpaid as a convenience filter, but it now maps to submitted + authorised only — it does not include draft. v3 typically counted drafts as unpaid. If your integration depends on drafts being included, use status=all and filter client-side, or make a separate status=draft request.

Migrating "unpaid" filter
// v3
GET /api/v3/invoices?status=unpaid

// v4 — single call works (excludes drafts)
GET /api/v4/invoices?status=unpaid

// v4 — include drafts as well: combine results
GET /api/v4/invoices?status=draft
GET /api/v4/invoices?status=unpaid
Invoice workflow
draft → submitted → authorised → paid
                                ↘ voided
    (any state) ──────────────→ deleted

Quotes Breaking

Quote status values have expanded from the v3 set. The v3 value approved maps to accepted in v4.

v4 StatusDescriptionv3 Equivalent
pendingCreated, awaiting reviewpending
sentSent to clientNot available
acceptedClient accepted the quoteapproved
addedtoprojectQuote converted to projectNot available
declinedClient declined the quotedeclined
expiredQuote expiredNot available
deletedSoft-deletedNot available
allAll non-deleted quotesall

Renamed Value

The v3 approved status is now accepted in v4. Update any code that checks for or filters by approved.

Default

When status is omitted, all non-deleted quotes are returned (equivalent to status=all). This differs from Projects/Items, which default to active only.

Migrating quote filters
// v3
GET /api/v3/quotes?status=approved

// v4 — "approved" is now "accepted"
GET /api/v4/quotes?status=accepted

// v4 — new states available
GET /api/v4/quotes?status=sent
GET /api/v4/quotes?status=addedtoproject
GET /api/v4/quotes?status=expired
Quote workflow
pending → sent → accepted → addedtoproject
                 ↘ declined
         (any) → expired
         (any) → deleted

Work Stages New

Work Stages are custom workflow steps within each work state. Each account can define named stages per work state (e.g., "In Review" stage within the "active" state, or "Awaiting Approval" within "submitted").

How It Works

Each entity (project, item, invoice, quote) has an optional work stage field representing its current position within the broader activeworkstate. The JSON field name differs by entity:

EntityResponse FieldDescription
ProjectsworkstageidActive work stage ID (0 / omitted = no stage set)
Items, Quotes, InvoicesactiveworkstageidActive work stage ID (0 / omitted = no stage set)

Filtering

All four entities (projects, items, quotes, invoices) support a workstageid query parameter to filter by a specific work stage ID.

Managing Stages

Work stages are configured per account via the settings endpoints:

EndpointDescription
GET /settings/workstagesAll work stages
GET /settings/workstages/projectProject work stages
GET /settings/workstages/itemItem work stages
GET /settings/workstages/invoiceInvoice work stages
GET /settings/workstages/quoteQuote work stages
Work Stage Example
// Get items in a specific work stage (filter param is workstageid;
// response field for items is activeworkstageid)
GET /api/v4/items?workstageid=5&fields=title,activeworkstate,activeworkstageid

// Response
{
  "status": "success",
  "data": [
    {
      "id": 101,
      "title": "Design mockups",
      "activeworkstate": "active",
      "activeworkstageid": 5
    },
    {
      "id": 102,
      "title": "Review copy",
      "activeworkstate": "active",
      "activeworkstageid": 5
    }
  ],
  "meta": {
    "count": 2,
    "total": 2
  }
}

Authentication Changed

Authentication methods remain largely the same, with an important distinction between auth types.

API Key (Staff-level access)

API Key format is unchanged. Pass via the apikey header.

Format: xxxx-xxxx-xxxx-xxxx-PWFxxxx

Implied Staff Role

API key authentication implies staff-level access (usertype=3), since only staff members have access to API keys. No individual user identity is associated with the request.

Bearer Token (Individual user access)

Bearer tokens authenticate individual users via OAuth. The user's role (client, contractor, or staff) and permissions are determined by their account. Use this for client-facing apps where users log in with their own credentials.

OAuth app setup requires a client secret. Contact [email protected] for setup.

Authentication Precedence

If both an Authorization: Bearer header and an apikey header are sent on the same request, the Bearer token takes precedence and the API key is ignored.

API Key (same as v3)
curl 'https://api.proworkflow.com/api/v4/projects' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'
Bearer Token
curl 'https://api.proworkflow.com/api/v4/projects' \
  -H 'Authorization: Bearer eyJhbGciOi...'

New Fields & Filters New

v4 introduces several new fields and filter parameters across entities.

New Fields (all entities)

FieldTypeDescription
activeworkstateStringWorkflow state enum value (e.g. active, complete, submitted, paid)
workstageid / activeworkstageidIntegerActive work stage ID. Projects expose this as workstageid; Items, Quotes, and Invoices expose it as activeworkstageid

New Filter Parameters

ParameterEntitiesDescription
workstageidProjects, Items, Quotes, InvoicesFilter by work stage
contactsProjects, Items, Time, MessagesFilter by assigned contacts (supports me, all, and !ids negation)
contactsmodeProjects, ItemsMatch any or all specified contacts
divisionidAllFilter by division (Advanced plan)
Field Selection (v4)
// Request specific fields
GET /api/v4/items?fields=title,status,project,assignedto

// Request all fields
GET /api/v4/items?fields=all

Fields & Sub-entities New

v4 uses a per-request field selection model to keep responses small. v3 typically returned every field — v4 returns only the fields you ask for.

The fields Parameter

Pass a comma-separated list of field names. Use fields=all to request every scalar/relation field. The id field is always returned regardless of selection.

ValueBehavior
omittedEndpoint-specific default field set
name,code,statusListed fields plus id
allAll scalar and relation fields, but not sub-entity arrays

Sub-entities Are Named in fields

Sub-entity arrays — phases, time records, messages, files, folders, etc. — are never included by fields=all. To include them, name them explicitly in the fields list. Each named sub-entity adds an extra SQL join, so include them only when needed.

Sub-entity NameResourceAdds
phasesProjects, Quotes, InvoicesPhases (with their items)
timerecordsProjectsTime records
messagesProjectsMessages
filesProjectsFile attachments
foldersProjectsFile folders
invoicesProjectsLinked invoices
workstagesProjectsWork stage history
bookmarksProjectsBookmarks
projectnotesProjectsProject notes

Single-item endpoints (GET /projects/{id}) include all sub-entities by default.

fields=all does NOT mean "everything"

Even fields=all will not include phases, time records, files, or messages. Combine all with sub-entity names: fields=all,phases,timerecords.

Default fields
# Endpoint-specific default field set
GET /api/v4/projects
Specific fields
# Only id, title, and activeworkstate
GET /api/v4/projects?fields=title,status
All scalars, no sub-entities
GET /api/v4/projects?fields=all
All scalars + phases + items
# Embed phases (with their items) inline
GET /api/v4/projects?fields=all,phases
Only specific sub-entities
GET /api/v4/projects/123?fields=title,timerecords,messages

Time Records Changed

Time record endpoints now support the same field selection and filtering patterns as other entities.

Key Changes

Featurev3v4
Field selectionAll fields returnedfields param with named groups
Default date rangeLast 6 daysSame: trackedfrom=-6d&trackedto=+0d
Status filterprojectstatusprojectstatus kept; new activeworkstate filter added (uses activeworkstate enum values)
Relative dates-6d, +0dSame format supported

Available Field Groups

contact, item, project, company, dates, timetracked, notes, billable, lastmodified, all

Time Records v4
# Get time for last 30 days with project info
curl 'https://api.proworkflow.com/api/v4/time?\
fields=contact,item,project,timetracked&\
trackedfrom=-30d&trackedto=+0d' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'

# Get only billable time for my user
curl 'https://api.proworkflow.com/api/v4/time?\
fields=item,timetracked,billable&\
contacts=me&billable=billable' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'

Pagination Changed

Pagination parameters keep their v3 names. Either or both can be provided — the endpoint applies safe defaults for whatever you omit.

ParameterDefaultDescription
pagenumber1Page number (1-based)
pagesize30000Results per page (max 30000)
sortbyvariesSort field (entity-specific)
sortorderascasc or desc

Set an Explicit pagesize

When pagesize is omitted, the API allows up to 30000 records per request. For predictable performance, target ≤500 records per call and never exceed 1000. Pass a small explicit pagesize (e.g. 50) on every list request.

Response Counts

List responses include a meta object with count (items in current page) and total (total matching records).

Pagination
# Get page 2, 25 items per page
curl 'https://api.proworkflow.com/api/v4/projects?\
pagenumber=2&pagesize=25&\
sortby=title&sortorder=asc' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'

# Response includes counts in meta
{
  "status": "success",
  "data": [...],
  "meta": {
    "count": 25,
    "total": 150,
    "page": 2,
    "pagesize": 25
  }
}

Rate Limiting New

v4 enforces request rate limits per API key (or per IP for unauthenticated requests). v3 did not.

Limits

WindowLimit
30 seconds500 requests

Exceeding the limit returns HTTP 429 Too Many Requests.

Response Headers

Every response includes rate limit state:

HeaderDescription
X-RateLimit-LimitMaximum requests in the window (always 500)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetSeconds until the window resets (30)

Migration Notes

If your v3 integration runs tight loops or bulk syncs, monitor X-RateLimit-Remaining and back off as it approaches zero. Consider larger pagesize values (up to 30,000 records per request, target ≤500) to reduce request volume.

Error Format Note

Middleware-level errors (rate limit, authentication failures) return the framework default {"message": "..."} rather than the standard handler envelope {"status": "error", "data": [...]}. Check both shapes in client error handling.

Rate Limit Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 30
Content-Type: application/json
Throttled Response
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 30

{
  "message": "Rate limit exceeded"
}

Company Address Fields → Company Locations Breaking

In v3, address data was stored directly on the Company object (address1, address2, address3, city, state, zipcode, country). In v4 these fields have been removed from the Company response. Address data is now managed through the dedicated Company Location resource, which supports multiple addresses per company with richer metadata.

Breaking Change

The fields address1, address2, address3, city, state, zipcode, and country have been removed from the Company response. You must now use the Company Locations API to read or write address data.

What Changed

v3 Fieldv4 Equivalent
address1CompanyLocation.address1
address2CompanyLocation.address2
address3CompanyLocation.address3
cityCompanyLocation.city
stateCompanyLocation.state
zipcodeCompanyLocation.zipcode
countryCompanyLocation.countryid / CompanyLocation.formattedaddress

New: maincompanylocationid

The Company object now exposes maincompanylocationid — the ID of the company's primary location. Use this to look up the main address via the Company Locations endpoint.

Embedding locations in the company list

You can avoid the two-step lookup by passing includelocations=true on the company list or single-get endpoint. This embeds a locations array directly in each company object.

New Endpoints

EndpointDescription
GET /api/v4/companies/{companyid}/locationsList locations for a company
POST /api/v4/companies/{companyid}/locationsCreate a location for a company
GET /api/v4/companies/{companyid}/locations/{locationid}Get a single location
PUT /api/v4/companies/{companyid}/locations/{locationid}Update a location
DELETE /api/v4/companies/{companyid}/locations/{locationid}Delete a location

Creating / updating address data

CompanyLocation Fields

FieldTypeDescription
idIntegerUnique identifier
nameStringLocation name (e.g. "Head Office")
codeStringShort code identifier
typeStringAddress type (e.g. postal, physical)
descriptionStringDescription of the location
deliveryinstructionsStringDelivery / access instructions
phoneStringLocation phone number
phone2StringSecondary phone number
emailStringLocation email address
activeBooleanWhether the location is active
companyidIntegerParent company ID
countryidIntegerCountry ID
formattedaddressStringFull formatted address string
address1StringStreet address line 1
address2StringStreet address line 2
address3StringStreet address line 3
suburbStringSuburb
cityStringCity
stateStringState / region
zipcodeStringPostal / zip code
latitudeFloatGPS latitude
longitudeFloatGPS longitude
placeidStringGoogle Maps place ID
lastmodifiedutcStringLast modified timestamp (UTC, ISO8601)
Reading address data
Before (v3)
GET /api/v3/companies/42

// Response included inline address:
{
  "id": 42,
  "name": "Acme Corp",
  "address1": "123 Main St",
  "city": "New York",
  "state": "NY",
  "zipcode": "10001",
  "country": "USA"
}
After (v4)
// Step 1 — get the company (includes maincompanylocationid)
GET /api/v4/companies/42?fields=name,maincompanylocationid

// Response:
{
  "status": "success",
  "data": {
    "id": 42,
    "name": "Acme Corp",
    "maincompanylocationid": 7
  }
}

// Step 2 — fetch the location
GET /api/v4/companylocations/7

// Response:
{
  "status": "success",
  "data": {
    "id": 7,
    "name": "Head Office",
    "address1": "123 Main St",
    "city": "New York",
    "state": "NY",
    "zipcode": "10001",
    "formattedaddress": "123 Main St, New York NY 10001, USA"
  }
}
Embed locations (v4)
GET /api/v4/companies?fields=name,maincompanylocationid&includelocations=true

// Each company in the response includes:
{
  "id": 42,
  "name": "Acme Corp",
  "maincompanylocationid": 7,
  "locations": [
    {
      "id": 7,
      "name": "Head Office",
      "type": "postal",
      "active": true,
      "address1": "123 Main St",
      "city": "New York",
      "state": "NY",
      "zipcode": "10001",
      "formattedaddress": "123 Main St, New York NY 10001, USA"
    }
  ]
}
Create a location
v4
POST /api/v4/companies/42/locations

{
  "name": "Head Office",
  "type": "postal",
  "address1": "123 Main St",
  "city": "New York",
  "state": "NY",
  "zipcode": "10001",
  "countryid": 1,
  "active": true
}
Set as the main location
v4
// After creating the location (returns id: 7), set it as the main location:
PUT /api/v4/companies/42

{
  "maincompanylocationid": 7
}

Staff Rates Breaking

In v3, staff rates were available at two separate endpoints under quotes and invoices settings. In v4, these have been consolidated into a single endpoint.

v3 Endpointv4 Endpoint
/settings/quotes/staffrates/settings/staffrates
/settings/invoices/staffrates

New Fields

The consolidated endpoint now includes the linked hourly rate service for each staff member.

FieldTypeDescription
idIntegerStaff contact ID
firstnameStringFirst name
lastnameStringLast name
hourlyrateserviceidIntegerDefault hourly rate service ID
hourlyrateservicenameStringName of the linked hourly rate service

Rate Field Visibility

The sell rate (outprice) is included in the response for staff with the ViewSensitiveBusinessInfo permission. Staff without this permission will not see it. Pay rate and cost rate fields are also available on individual items (project items, quote items, template items) and time allocations where applicable.

Before (v3)
// Two separate endpoints
GET /api/v3/settings/quotes/staffrates
GET /api/v3/settings/invoices/staffrates
After (v4)
// Single consolidated endpoint
GET /api/v4/settings/staffrates

// Response
{
  "status": "success",
  "data": [
    {
      "id": 42,
      "firstname": "Jane",
      "lastname": "Smith",
      "hourlyrateserviceid": 3,
      "hourlyrateservicename": "Senior Developer",
      "outprice": 120.00
    }
  ],
  "meta": {
    "count": 1,
    "total": 1
  }
}

Product Libraries New

v4 introduces dedicated endpoints for each product library type, matching the categories in the ProWorkflow UI.

EndpointLibraryDescription
/settings/product/hourlyserviceHourly ServicesProducts charged at an hourly rate
/settings/product/fixedpriceFixed PriceProducts charged at a set price per unit
/settings/product/goodserviceGoods & ServicesProducts with both hourly and quantity components

Service Rates

The /settings/servicerates endpoint returns the hourly rate service definitions used to link products to billable rates.

Product Fields

Each product library returns fields appropriate to its type. Hourly service products include hourlyrateserviceid and hourlyrateservicename. Fixed price products include inUnitId and outUnitId. Goods & Services products include all of the above.

Rate Field Visibility

Labour rates (inLabourPrice, outLabourPrice) are gated by the ViewSensitiveBusinessInfo permission. They are returned by /settings/product/hourlyservice and /settings/product/goodservice only when the user holds that permission. Unit prices (inUnitPrice, outUnitPrice) on the Goods & Services library are gated the same way; on the Fixed Price library they are returned to all staff.

Product Libraries
# Hourly Services
GET /api/v4/settings/product/hourlyservice

# Fixed Price Products
GET /api/v4/settings/product/fixedprice

# Goods & Services
GET /api/v4/settings/product/goodservice

# Hourly Rate Service definitions
GET /api/v4/settings/servicerates
Hourly Service Response
{
  "status": "success",
  "data": [
    {
      "id": 10,
      "name": "Web Development",
      "code": "WEBDEV",
      "itemtypeid": 2,
      "hourlyrateserviceid": 3,
      "hourlyrateservicename": "Developer",
      "inLabourPrice": 50,
      "outLabourPrice": 100,
      "billable": true
    }
  ],
  "meta": {
    "count": 1,
    "total": 1
  }
}

Removed Endpoints Breaking

The following endpoints from v3 have been removed in v4.

Removed EndpointReplacementReason
/settings/quotes/staffrates/settings/staffratesConsolidated into single endpoint
/settings/invoices/staffrates/settings/staffratesConsolidated into single endpoint
/settings/invoices/hourlyrates/settings/serviceratesConsolidated into single endpoint
/settings/quotes/hourlyrates/settings/serviceratesConsolidated into single endpoint
/settings/fixedcostitemsProduct LibrariesReplaced by /settings/product/* endpoints
/settings/projects/customstatusesWork StagesCustom statuses replaced by Work Stages
/settings/projects/customstatuses/:idWork StagesCustom statuses replaced by Work Stages

Action Required

If your integration uses any of the removed endpoints, update your code to use the replacement endpoints listed above. Requests to removed endpoints will return a 404 Not Found response.

Custom Statuses → Work Stages

The /settings/projects/customstatuses CRUD endpoints have been removed. Custom statuses are replaced by Work Stages (activeworkstageid), which provide a more flexible workflow system that applies across all entities (projects, items, invoices, quotes) — not just projects.

See the Work Stages section above for details on the new system. Use the workstageid field and filter parameter to work with stages.

Webhooks (REST Hooks) Changed

REST hook subscriptions are managed under /settings/webhooks in v4. All webhook endpoints require staff-level access.

Endpoints

MethodPathDescription
GET/settings/webhooksList all webhook subscriptions
POST/settings/webhooksCreate a webhook subscription
GET/settings/webhooks/:webhookidGet a single subscription
PUT/settings/webhooks/:webhookidUpdate a subscription
DELETE/settings/webhooks/:webhookidDelete a subscription
GET/settings/webhooks/requestsView recent webhook delivery attempts (retained for 7 days)
List webhooks
curl 'https://api.proworkflow.com/api/v4/settings/webhooks' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'
View delivery history
# Inspect recent webhook fires for debugging
curl 'https://api.proworkflow.com/api/v4/settings/webhooks/requests' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'