ProWorkflow ProWorkflow

ProWorkflow API v4

Build powerful integrations with ProWorkflow's Nexus project management platform.

Getting Started

The API lives at:

https://api.proworkflow.com/api/v4

You'll need an API key or JWT token to authenticate - check the Authentication section for details.

How Responses Work

Every response comes back as JSON. You'll always get a status field that tells you if things worked ("success") or not ("error").

The actual data you requested lives in the data field. For lists of things, you'll also get a meta object with counts and pagination info:

// Getting a list of companies
{
  "status": "success",
  "data": [...],      // Your companies
  "meta": {
    "count": 10,      // How many in this response
    "total": 250      // Total available
  }
}

// Getting a single project
{
  "status": "success",
  "data": {
    "id": 123,
    "name": "Website Redesign"
  }
}

// Something went wrong
{
  "status": "error",
  "data": [
    "What went wrong"
  ]
}

Making Requests

For GET and DELETE requests, just add parameters to the URL.

For POST and PUT requests, send JSON in the body with Content-Type: application/json.

What You Can Do

The API gives you access to everything in ProWorkflow:

  • Companies & Contacts
  • Projects & Tasks
  • Time Tracking & Expenses
  • Invoices & Quotes
  • Custom Fields & Tags
  • Reports & Settings

Most endpoints let you:

  • Filter results with query parameters
  • Choose which fields to return
  • Page through large datasets
  • Sort by different fields
  • Create multiple items at once

Check out the endpoint documentation for specifics on what each one can do.

AI Integration — Want to connect an AI assistant to ProWorkflow? The MCP Server exposes 40 tools across 8 resources using the Model Context Protocol, letting AI clients list, create, update, and delete records via JSON-RPC.

Examples & Tools — See the Examples page for scripts, apps, and tools built on the API.

Get Companies
cURL
curl 'https://api.proworkflow.com/api/v4/companies?fields=name,code&pagesize=10' \
  -H 'apikey: your-api-key'
Create Project
cURL
curl -X POST 'https://api.proworkflow.com/api/v4/projects' \
  -H 'Content-Type: application/json' \
  -H 'apikey: your-api-key' \
  -d '{"name": "New Project", "companyid": 123}'

Authentication

There are a few ways to authenticate. Which one you use determines what permissions apply to your requests.

Authentication Methods

1. JWT Bearer Token (OAuth 2.0)

Pass a JWT in the Authorization header. This gives you user-level access — permissions, division scope, and sensitive field visibility all apply based on the user's settings.

JWTs are obtained via the OAuth 2.0 Authorization Code flow using the ProWorkflow identity server. Contact [email protected] to register an OAuth app and receive a client_id and client_secret.

Authorization URL:

https://identity.proworkflow.com/auth
  ?client_id=<client_id>
  &redirect_uri=<redirect_uri>
  &response_type=code
  &state=<random_state>
  &scope=openid email
Parameter Description
client_id Your OAuth app's client ID, provided by ProWorkflow
redirect_uri The URL your app redirects to after the user authorises
response_type Always code (Authorization Code flow)
state A random string you generate; verify it on the callback to prevent CSRF
scope Space-separated scopes — use openid email at minimum

After the user authorises, exchange the returned code for an access token at your token endpoint, then pass the token as a Bearer header on API requests.

2. API Key

Pass your API key as a header. This is account-level access — no permission restrictions, all sensitive fields visible, all divisions accessible. Best for integrations and automation.

Format: xxxx-xxxx-xxxx-xxxx-PWFxxxx (optional suffix: xxxx-xxxx-xxxx-xxxx-PWFxxxx-suffix)

API Key vs JWT

API Key — account-level access, bypasses all permission checks, all sensitive fields visible, all divisions accessible.

JWT — user-level access, respects user permissions, may hide sensitive fields, scoped to the user's division.

Setting Authentication

Try It Out

Use the Try it out panel in the sidebar to set your credentials, then use the Try It forms on any endpoint to make live requests.

JWT Bearer Token
cURL
curl 'https://api.proworkflow.com/api/v4/projects' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...'
API Key
cURL
curl 'https://api.proworkflow.com/api/v4/projects' \
  -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'
OAuth Authorization URL
cURL
curl -G 'https://identity.proworkflow.com/auth' \
  --data-urlencode 'client_id=your-client-id' \
  --data-urlencode 'redirect_uri=https://yourapp.com/callback' \
  --data-urlencode 'response_type=code' \
  --data-urlencode 'state=random-csrf-state' \
  --data-urlencode 'scope=openid email'

Rate Limiting

The API enforces rate limiting to ensure fair usage and system stability.

Rate Limit

500 requests per 30-second window per API key or authenticated user.

  • Rate limits are applied per API key for API key authentication
  • Rate limits are applied per user for JWT authentication
  • Each authentication method has its own independent rate limit

Response Headers

Every API response includes rate limit information in the headers:

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

Rate Limit Exceeded

When you exceed the rate limit, the API returns HTTP status 429 Too Many Requests. You must wait for the window to reset before making additional requests.

Best Practices

  • Monitor the X-RateLimit-Remaining header to avoid hitting limits
  • Implement exponential backoff when you receive 429 responses
  • Cache responses when possible to reduce API calls
  • Use field selection to minimize payload sizes
  • Batch operations where supported

Record Limits

The API will return a maximum of 30,000 records. If your request exceeds this limit then only the first 30,000 records will be returned. We strongly suggest you design your application and requests such that you will not come close to this limit. For optimal performance you should plan your requests to return no more than 1,000 records and typically no more than 500.

If your usage of the API is impacting on response times for other customers then we reserve the right to limit or remove your access to the API (we would, of course, contact you first to try to avoid taking that step).

You can limit the number of items returning and improve performance by using the paging options and we encourage you to use these options to deliver a better experience to your users.

Rate Limit Headers
Response Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 485
X-RateLimit-Reset: 28
429 Response
429 Too Many Requests
{
  "status": "error",
  "data": [
    "Rate limit exceeded. Try again in 30 seconds."
  ]
}

Pagination

Most collection endpoints are paginated. Pass pagenumber and pagesize to get a specific page. By default the total record count is not calculated — pass includetotalrows=true to include meta.total in the response.

Pagination Parameters

ParameterTypeDescription
pagenumberintegerPage number to retrieve (starts at 1)
pagesizeintegerNumber of items per page (max varies by endpoint)
includetotalrowsbooleanSet to true to run the count query and include meta.total in the response (default: false)

Important: Both or Neither

You must provide both pagenumber and pagesize together, or neither. Providing only one will result in a validation error.

Default Behavior

  • Without pagination parameters: Returns all matching records (up to endpoint limit)
  • With pagination parameters: Returns specified page with metadata

Response Metadata

Paginated responses include a meta object with pagination details:

{
  "status": "success",
  "data": [...],
  "meta": {
    "count": 10,      // Items in this response
    "total": 250,     // Total matching records
    "page": 1,        // Current page number
    "pagesize": 10    // Items per page
  }
}

Total Count

The total field in the meta object reflects the true total count of all records matching your query filters, not just the items in the current page. This allows you to:

  • Calculate the total number of pages: Math.ceil(total / pagesize)
  • Show accurate progress indicators
  • Know when you've retrieved all records

Opting in with includetotalrows

By default, the count query is not executed and total is omitted from the response meta. To include it, pass includetotalrows=true:

GET /api/v4/projects/items?pagenumber=1&pagesize=10&includetotalrows=true
{
  "status": "success",
  "data": [...],
  "meta": {
    "count": 10,
    "total": 13875,
    "page": 1,
    "pagesize": 10
  }
}

When includetotalrows is omitted or false, the total field is not returned and the count query is skipped, which can significantly improve response times on large datasets:

{
  "status": "success",
  "data": [...],
  "meta": {
    "count": 10,
    "page": 1,
    "pagesize": 10
  }
}

Performance Tip

Only set includetotalrows=true when you need the total (e.g. to display a page count). Skipping the count query can make paginated requests noticeably faster on endpoints with many matching records.

Sorting

Control the order of results with sorting parameters:

ParameterDescription
sortbyField to sort by (varies by endpoint)
sortorderasc (ascending) or desc (descending)

Common sort fields:

  • id - Record ID
  • name - Name field
  • code - Code/identifier
  • createdon - Creation date
  • lastmodified - Last modification date
  • sortorder - Custom drag-and-drop position (see below)

Custom Ordering

Some entities (projects, items, invoices, quotes) support drag-and-drop ordering via a dedicated endpoint. Use sortby=sortorder to return results in their custom order, and use the /order sub-endpoint to change position:

PUT /api/v4/{resource}/:id/order

Provide exactly one placement field in the request body:

FieldDescription
placeafteridID of the record this item should be positioned after
placebeforeidID of the record this item should be positioned before

Endpoints supporting custom ordering:

  • PUT /api/v4/projects/:projectid/order
  • PUT /api/v4/projects/items/:itemid/order
  • PUT /api/v4/quotes/items/:itemid/order
  • PUT /api/v4/invoices/items/:itemid/order
  • PUT /api/v4/invoices/:invoiceid/order
  • PUT /api/v4/quotes/:quoteid/order
Reorder a Project Item
cURL
# Move item 42 to appear after item 41
curl -X PUT 'https://api.proworkflow.com/api/v4/projects/items/42/order' \
  -H 'apikey: your-api-key' \
  -H 'Content-Type: application/json' \
  -d '{"placeafterid": 41}'
Reorder Response
200 OK
{
  "status": "success",
  "message": "Project item reordered"
}
First Page
cURL
curl 'https://api.proworkflow.com/api/v4/companies?pagenumber=1&pagesize=50&sortby=name&sortorder=asc' \
  -H 'apikey: your-api-key'
Paginated Response
200 OK
{
  "status": "success",
  "data": [
    {"id": 1, "name": "Acme Corp"},
    {"id": 2, "name": "Beta Inc"}
  ],
  "meta": {
    "count": 2,
    "page": 1,
    "pagesize": 50
  }
}
First Page with Total Count
cURL
curl 'https://api.proworkflow.com/api/v4/companies?pagenumber=1&pagesize=50&includetotalrows=true' \
  -H 'apikey: your-api-key'
Paginated Response with Total
200 OK
{
  "status": "success",
  "data": [
    {"id": 1, "name": "Acme Corp"},
    {"id": 2, "name": "Beta Inc"}
  ],
  "meta": {
    "count": 2,
    "total": 150,
    "page": 1,
    "pagesize": 50
  }
}
Iterate All Pages
JavaScript
async function getAllCompanies() {
  let page = 1;
  const pageSize = 100;
  const allCompanies = [];

  while (true) {
    // Request total count on the first page only
    const withTotal = page === 1 ? '&includetotalrows=true' : '';
    const response = await fetch(
      `/api/v4/companies?pagenumber=${page}&pagesize=${pageSize}${withTotal}`
    );
    const result = await response.json();

    allCompanies.push(...result.data);

    // Stop when we've retrieved all records
    if (allCompanies.length >= result.meta.total || result.data.length < pageSize) {
      break;
    }

    page++;
  }

  return allCompanies;
}

Query Parameters Reference

A quick reference for the filtering and field selection parameters supported across GET endpoints. Parameters combine with AND logic unless noted.

Field Selection

Control which fields are returned in the response to optimize payload size.

ParameterExampleDescription
fieldsname,code,typeComma-separated list of fields to return
fieldsallReturn all available fields (subject to permissions)

Notes:

  • The id field is always included automatically
  • Invalid field names are silently ignored
  • Field names are case-insensitive
  • Default fields vary by endpoint when not specified

Search & Text Filtering

Search within text fields using these parameters:

ParameterExampleDescription
searchacmeGeneral search across default fields
search!testNegation search (exclude matches)
searchnamecorpSearch in name field only
searchcodeABCSearch in code field only
searchemail@example.comSearch in email field only

Negation Search:

  • Prefix search term with ! to exclude matches
  • Example: ?search=!test returns items NOT containing "test"
  • Works with field-specific searches too: ?searchname=!admin

Type & Status Filters

Filter by enumerated values like type, status, or state:

ParameterExampleDescription
typeclientFilter by single type
typeclient,contractorMultiple types (OR logic)
statusactiveFilter by status
statusactive,pendingMultiple statuses (OR logic)

Common Values:

  • Company types: client, contractor, staff, other
  • Project status: active, complete, cancelled, archived
  • Task status: pending, inprogress, complete

Date & Time Filters

Filter by dates using absolute or relative time:

ParameterExampleDescription
lastmodifiedfrom2024-01-18T13:20ISO8601 date/time
lastmodifiedfrom12hLast 12 hours
lastmodifiedfrom5dLast 5 days
lastmodifiedfrom2wLast 2 weeks
lastmodifiedfrom1mLast 1 month
lastmodifiedto2024-12-31Modified before date
createdfrom2024-01-01Created after date
createdto2024-12-31Created before date

Relative Time Units:

  • n - minutes (e.g., 30n = 30 minutes)
  • h - hours (e.g., 24h = 24 hours)
  • d - days (e.g., 7d = 7 days)
  • w - weeks (e.g., 4w = 4 weeks)
  • m - months (e.g., 3m = 3 months)

UTC Variants: Some endpoints support UTC versions: lastmodifiedutcfrom, lastmodifiedutcto

ID Filters

Filter by specific IDs or ID ranges:

ParameterExampleDescription
id1,2,3,4Specific IDs (comma-separated)
idfrom100IDs greater than or equal to value
idto200IDs less than or equal to value

Relationship Filters

Filter by related entities:

ParameterExampleDescription
companyid123Filter by company
projectid456Filter by project
contactid789Filter by contact
tagid1,2,3Has ANY of these tags (OR)
divisionid10Filter by division (Advanced plans)

Combining Parameters

This query finds:

  • Client projects
  • That are active OR pending
  • Modified in the last 7 days
  • NOT containing "test" in searchable fields
  • Tagged with tag ID 1, 2, OR 3
  • Returns only specified fields
  • First 25 results
  • Sorted by last modified date, newest first
Complex Query
cURL
curl 'https://api.proworkflow.com/api/v4/projects?' \
  'type=client&' \
  'status=active,pending&' \
  'lastmodifiedfrom=7d&' \
  'search=!test&' \
  'tagid=1,2,3&' \
  'fields=name,code,status,budget&' \
  'pagenumber=1&pagesize=25&' \
  'sortby=lastmodified&sortorder=desc' \
  -H 'apikey: your-api-key'

Error Handling

All error responses have "status": "error" and a data array with one or more messages describing what went wrong.

HTTP Status Codes

CodeMeaningWhen Used
200OKSuccessful GET, PUT, PATCH, DELETE
201CreatedSuccessful POST (resource created)
204No ContentSuccessful DELETE (no response body)
400Bad RequestValidation failed, invalid parameters
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but insufficient permissions
404Not FoundResource doesn't exist or no access
409ConflictResource conflict (e.g., duplicate)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error
503Service UnavailableTemporary maintenance/overload

Error Response Structure

All error responses follow this format:

{
  "status": "error",
  "data": [
    "Error message 1",
    "Error message 2"
  ]
}

The data array contains one or more descriptive error messages explaining what went wrong.

Validation Errors
400 Bad Request
{
  "status": "error",
  "data": [
    "Name is required",
    "Invalid value for parameter 'type': 'invalid'. Allowed values: client, contractor, staff, other",
    "Invalid date format for 'lastmodifiedfrom'. Use ISO8601 or relative time (12h, 5d)",
    "Both 'pagenumber' and 'pagesize' must be provided together"
  ]
}
Permission Denied
403 Forbidden
{
  "status": "error",
  "data": [
    "You do not have permission to view invoices"
  ]
}
Authentication Required
401 Unauthorized
{
  "status": "error",
  "data": [
    "Authentication required"
  ]
}
Not Found
404 Not Found
{
  "status": "error",
  "data": [
    "Company not found"
  ]
}
Error Handling
JavaScript
async function makeApiRequest(url, options) {
  try {
    const response = await fetch(url, options);
    const data = await response.json();

    // Check for error status in response
    if (data.status === "error") {
      throw new Error(data.data.join(", "));
    }

    // Handle specific HTTP status codes
    if (!response.ok) {
      switch (response.status) {
        case 401:
          // Redirect to login
          break;
        case 429:
          // Implement retry with backoff
          break;
        case 500:
          // Log error and show user message
          break;
      }
    }

    return data;
  } catch (error) {
    console.error("API Error:", error);
    throw error;
  }
}

Permissions & Access Control

Access control works differently depending on how you authenticate. API keys bypass all permission checks. JWT tokens enforce the full user permission model.

Authentication Impact on Permissions

Critical Difference

API Key Authentication: Bypasses ALL permission checks - full account access JWT Authentication: Enforces user-specific permissions and restrictions

User Types

Every user has a type that determines their general access level:

Type IDTypeDescription
1ClientExternal client users with limited access
2ContractorExternal contractors with project access
3StaffInternal staff with full system access
4OtherCustom user type with configurable access

Account Plans

Account plans determine available features:

Plan IDPlanFeatures
1BasicStandard features, single division
3AdvancedTeams, groups, multiple divisions

Permission System

Permissions are identified by numeric IDs and control specific actions:

Permission IDNameControls
2ViewAllWorkSee all work across the account
3ViewItemView tasks and items
4ViewProjectView projects
7ViewTimelogView time entries
8ViewContactNoteView contact/company notes
11ViewSensitiveBusinessInfoView financial/budget fields
12ViewInvoiceView invoices
13ViewQuoteView quotes
14ViewCustomfieldsView custom fields
18ViewUserPermissionsView user permissions
23ViewTeamWorkSee team members' work
24ViewAllWorkDivision-scoped view all

Permission Actions

Each permission supports different actions:

  • view - Read access to resources
  • add - Create new resources
  • edit - Update existing resources
  • delete - Delete resources

Sensitive Field Filtering

Users without ViewSensitiveBusinessInfo (Permission 11) have financial fields automatically hidden:

Affected Resources & Fields:

ResourceHidden Fields
Projectbudget, estBillAmount, invoicedAmount, paidAmount, estProfit, costs
InvoiceestBillAmount, estBillAmountTax, paid, budget, amounts
ProjectItembudget, billAmount, estBillAmount, labour prices
Contactpayrate, payratemultiplier, defaulthourlyrateid
CompanyFinancial summaries, credit information

API Key Exception

When using API key authentication, ALL sensitive fields are visible regardless of permission settings, as API keys represent account-level access.

Division & Team Filtering

For Advanced plan accounts with multiple divisions:

Division Access:

  • Users are assigned to a specific division
  • Can only see/modify resources in their division
  • divisionid parameter filters results
  • Cross-division access requires special permissions

Team Access:

  • Users can be part of teams within divisions
  • ViewTeamWork (Permission 23) allows seeing team members' work
  • Without team permission, users see only their own assigned work

Contact Filter Modes

The API uses contact filter modes to control work visibility:

ModeNameVisibility
0ViewAllWorkSee all work (requires permission)
1DefaultSee only own assigned work
2ViewTeamWorkSee team members' work
Permission Check Failed
403 Forbidden
{
  "status": "error",
  "data": [
    "You do not have permission to view invoices"
  ]
}
Sensitive Fields Hidden
200 OK
// User without ViewSensitiveBusinessInfo sees:
{
  "status": "success",
  "data": {
    "id": 123,
    "name": "Project Alpha",
    "status": "active"
    // budget field omitted
    // estBillAmount field omitted
  }
}
Division Filtered Results
cURL
// User in division 5 only sees their division's data
curl 'https://api.proworkflow.com/api/v4/projects' \
  -H 'Authorization: Bearer <jwt-token>'

// Returns only projects in user's division

Field Reference

The API provides flexible field selection to optimize response payloads. Use the fields parameter to control which fields are returned.

Field Selection

Parameter ValueBehavior
fields=name,code,typeReturns only specified fields (plus id)
fields=allReturns all available fields
omittedReturns default fields for endpoint

Important Notes:

  • The id field is always included automatically
  • Field names are case-insensitive
  • Unknown field names are silently ignored
  • Some fields require specific permissions to view

Common Field Types

TypeFormatExampleNotes
IDint6412345Unique identifier
StringUTF-8 text"Acme Corp"Text fields
DateISO 8601"2024-06-15"Date only
DateTimeISO 8601"2024-06-15T14:30:00"Date and time
Decimalfloat641250.50Money, percentages
HoursDecimal1.51.5 = 1h 30m
BooleanbooltrueTrue/false flags
BinaryHex string"0xABCDEF"Sort order field
ArrayJSON array[{...}]Related objects

Sensitive Financial Fields

Certain financial fields are restricted based on permissions. Users without ViewSensitiveBusinessInfo permission will not see these fields:

Permission-Restricted Fields

The following fields are hidden from users without financial permissions (except when using API key authentication):

ResourceSensitive Fields
Projectbudget, estBillAmount, estBillAmountTax, estInTotal, estInTotalTax, estProfit, estProfitPercentage, invoicedAmount, invoicedAmountTax, paidAmount, paidAmountTax, actualCost, actualRevenue, actualProfit
ProjectItembudget, billAmount, estBillAmount, estInTotal, inLabourPrice, outLabourPrice, costPerHour, estCost, estRevenue, estProfit
InvoiceestBillAmount, estBillAmountTax, estIn, estInTax, paid, paidTax, budget, amounts, tax rates
Quotetotal, totalTax, budget, itemized costs
Contactpayrate, payratemultiplier, defaulthourlyrateid, costPerHour, chargeoutrate
Timelogcost, charge, profit, hourlyRate
Expensecost, markup, total

JSON Array Fields

Many resources include arrays of related objects:

FieldStructureAvailable On
tags[{id, name, color}]Companies, Contacts, Projects, Items, Invoices, Quotes
contacts[{id, firstname, lastname, email}]Companies, Projects
staff[{id, firstname, lastname, email}]Projects, Items
assignedto[{id, firstname, lastname}]Tasks, Items
customfields[{id, name, value, type}]All major entities
attachments[{id, filename, size, url}]Projects, Items, Invoices
roles[{id, name, color}]Contacts

Status Values by Entity

The status field represents different states for each entity type:

EntityValid Status Values
Companyactive, inactive, archived
Contactactive, inactive, archived
Projectpending, active, complete, cancelled, archived
Task/Itempending, inprogress, complete, cancelled
Invoicedraft, submitted, authorised, paid, voided
Quotepending, sent, accepted, declined, expired

Computed vs Stored Fields

Stored Fields - Directly from database, support filtering/sorting:

  • id, name, code, status, type
  • createdon, lastmodified
  • startdate, duedate

Computed Fields - Calculated at runtime, may not support filtering:

  • percentcomplete - Calculated from task completion
  • timeremaining - Based on estimates vs actuals
  • profitmargin - Calculated from costs/revenue
  • fullname - Concatenated from firstname + lastname
  • age - Calculated from dates

Examples

Best Practices

  1. Request only needed fields - Reduces payload size and improves performance
  2. Check for field existence - Fields may be omitted based on permissions
  3. Handle null values - Many fields can be null when not set
  4. Parse arrays properly - Array fields may be empty arrays [] or omitted entirely
  5. Respect field types - Don't assume string when API returns numbers
Default Fields
Request
GET /api/v4/companies
# Returns: id, code, name, type (defaults)
Selected Fields
Request
GET /api/v4/projects?fields=name,status,budget,tags
# Returns: id, name, status, budget, tags
# Note: budget may be hidden if user lacks permission
All Fields
Request
GET /api/v4/contacts?fields=all
# Returns all available fields for contacts
# Sensitive fields still subject to permissions
Response with Arrays
JSON
{
  "id": 42,
  "name": "Website Redesign",
  "status": "active",
  "tags": [
    {"id": 1, "name": "Urgent", "color": "FF5733"},
    {"id": 5, "name": "Frontend", "color": "2196F3"}
  ],
  "staff": [
    {"id": 10, "firstname": "John", "lastname": "Doe"},
    {"id": 15, "firstname": "Jane", "lastname": "Smith"}
  ]
}

Batch Operations

Most POST endpoints accept either a single object or an array. Sending an array creates multiple records in one request.

Batch Creation Pattern

Single Item:

POST /api/v4/companies
{
  "name": "Acme Corp",
  "code": "ACM",
  "type": "client"
}

Array:

POST /api/v4/companies
[
  {
    "name": "Acme Corp",
    "code": "ACM",
    "type": "client"
  },
  {
    "name": "Beta Inc",
    "code": "BETA",
    "type": "contractor"
  }
]

Supported Endpoints

Most creation endpoints support batch operations:

  • /companies - Create multiple companies
  • /contacts - Create multiple contacts
  • /projects - Create multiple projects
  • /items - Create multiple items
  • /timelogs - Create multiple time entries
  • /expenses - Create multiple expenses
  • /invoices - Create multiple invoices
  • /quotes - Create multiple quotes

Response Format

Batch operations return a summary of the operation:

Partial Success Handling

If some items fail validation while others succeed, the API may:

  1. Reject entire batch (default) - Returns error, no items created
  2. Process valid items (some endpoints) - Creates valid items, reports errors

Check endpoint documentation for specific behavior.

Batch Update Pattern

Some endpoints support batch updates using filters:

PUT /api/v4/items/batch
{
  "filter": {
    "projectid": 123,
    "status": "pending"
  },
  "update": {
    "status": "inprogress",
    "assignedto": 456
  }
}

Examples

Batch Delete Operations

Some endpoints support batch deletion using ID lists:

DELETE /api/v4/items?ids=1,2,3,4,5

Or using filters:

DELETE /api/v4/items
{
  "filter": {
    "projectid": 123,
    "status": "cancelled"
  }
}

Soft Deletes

All delete operations are soft deletes. Records are marked as Deleted='true' but remain in the database for audit and recovery purposes.

Batch Success
201 Created
{
  "status": "success",
  "message": "3 companies created successfully",
  "data": {
    "created": 3,
    "ids": [101, 102, 103]
  }
}
Batch Validation Error
400 Bad Request
{
  "status": "error",
  "data": [
    "Item 2: Name is required",
    "Item 3: Invalid type 'invalid'. Allowed: client, contractor, staff, other"
  ]
}
Create Multiple Contacts
cURL
curl -X POST 'https://api.proworkflow.com/api/v4/contacts' \
  -H 'Content-Type: application/json' \
  -H 'apikey: your-api-key' \
  -d '[
    {
      "firstname": "John",
      "lastname": "Doe",
      "email": "[email protected]",
      "companyid": 123
    },
    {
      "firstname": "Jane",
      "lastname": "Smith",
      "email": "[email protected]",
      "companyid": 123
    }
  ]'
Batch with Relationships
JSON
// Create project with tasks in one request
{
  "name": "Website Redesign",
  "companyid": 123,
  "tasks": [
    {
      "name": "Design Mockups",
      "estimatedhours": 20
    },
    {
      "name": "Frontend Development",
      "estimatedhours": 40
    },
    {
      "name": "Testing",
      "estimatedhours": 10
    }
  ]
}

Data Types & Models

Field types and formats used across the API.

Nullable Fields

Many fields can be null, indicated by omission from the response when using omitempty:

{
  "id": 1,
  "name": "Project Alpha"
  // budget field omitted if null
}

Common Nullable Fields:

  • Optional text fields (description, notes)
  • Foreign keys (companyid, projectid when not required)
  • Calculated fields (totals, percentages)
  • Date fields (completeddate, duedate)

Date & Time Handling

Date Formats:

  • ISO8601: 2024-01-18T13:20:00 (local time)
  • ISO8601 with timezone: 2024-01-18T13:20:00Z (UTC)
  • Date only: 2024-01-18

Relative Time (for filters):

  • 12h - 12 hours ago
  • 5d - 5 days ago
  • 2w - 2 weeks ago
  • 1m - 1 month ago

Timezone Handling

Dates without timezone indicators are interpreted as account local time. Use UTC variants of parameters (e.g., lastmodifiedutcfrom) for timezone-agnostic filtering.

Enumerated Types

Many fields accept only specific values:

Company/Contact Types:

"type": "client" | "contractor" | "staff" | "other"

Project Status:

"status": "active" | "complete" | "cancelled" | "archived" | "pending"

Task Status:

"status": "pending" | "inprogress" | "complete" | "cancelled"

Priority Levels:

"priority": 1 | 2 | 3 | 4 | 5  // 1=Highest, 5=Lowest

Billing Types:

"billtype": 0 | 1 | 2 | 3
// 0=Non-billable, 1=Hourly, 2=Fixed, 3=Recurring

Money & Financial Fields

Financial values are represented as decimals with up to 2 decimal places:

{
  "budget": 50000.00,
  "spent": 12500.50,
  "remaining": 37499.50,
  "hourlyrate": 150.00
}

Important: Some financial fields may be hidden based on user permissions. See Permissions documentation.

Special Field Types

Color Fields:

"color": "4A90E2"  // 6-character hex color without #

Email Fields:

"email": "[email protected]"  // Validated email format

URL Fields:

"website": "https://example.com"  // Valid URL format

Composite Objects

Address Object:

"address": {
  "line1": "123 Main St",
  "line2": "Suite 100",
  "city": "New York",
  "state": "NY",
  "postalcode": "10001",
  "country": "USA"
}

Contact Name Object:

"contact": {
  "id": 123,
  "firstname": "John",
  "lastname": "Doe",
  "fullname": "John Doe",
  "email": "[email protected]"
}

Field Omission Rules

Fields are omitted from responses when:

  1. They are null and marked with omitempty
  2. User lacks permission to view them
  3. They weren't requested via fields parameter
  4. They are empty arrays or objects
String to Integer
JavaScript
// API returns string IDs in some cases
const companyId = parseInt(response.companyid, 10);
Date Parsing
JavaScript
// Parse ISO8601 date
const createdDate = new Date(response.createdon);

// Format for API input
const isoDate = createdDate.toISOString().split('T')[0];
Handle Nullable Fields
JavaScript
// Safely access nullable fields
const budget = response.budget ?? 0;
const description = response.description ?? "";

// Check if field exists
if ('budget' in response) {
  // Field was included but might be null
}

Search & Filtering

This page covers the search and date range options. For the full parameter reference see Query Parameters.

Search within text fields using various search parameters:

GET /api/v4/companies?search=acme

Searches across default searchable fields (usually name, code, description).

GET /api/v4/contacts?searchname=john
GET /api/v4/[email protected]
GET /api/v4/companies?searchcode=ABC

Search within specific fields for more targeted results.

Prefix any search with ! to exclude matches:

GET /api/v4/projects?search=!test

Returns all projects that DON'T contain "test".

Search Behavior

  • Searches are case-insensitive
  • Partial matches are supported (SQL LIKE %term%)
  • Multiple search parameters combine with AND
  • Special characters should be URL-encoded

Filter Combinations

Filters work together to progressively narrow results:

Tag Filtering

Filter items by tags:

# Has ANY of these tags (OR logic)
GET /api/v4/projects?tagid=1,2,3

# Returns projects tagged with tag 1 OR 2 OR 3

Note: Currently no support for "has ALL tags" filtering.

Date Range Filtering

Filter by date ranges using absolute or relative dates:

Absolute Dates

GET /api/v4/projects?createdfrom=2024-01-01&createdto=2024-12-31
GET /api/v4/items?duefrom=2024-06-01&dueto=2024-06-30

Relative Time

# Items modified in last 24 hours
GET /api/v4/items?lastmodifiedfrom=24h

# Items created in last week
GET /api/v4/items?createdfrom=1w

# Items due in next 30 days
GET /api/v4/items?dueto=30d

Relative Time Units:

  • n - minutes (30n = 30 minutes)
  • h - hours (24h = 24 hours)
  • d - days (7d = 7 days)
  • w - weeks (2w = 2 weeks)
  • m - months (3m = 3 months)

Nested Resource Filtering

Access filtered subsets via nested endpoints:

# Get contacts for a specific company
GET /api/v4/companies/123/contacts

# Get active projects for a company
GET /api/v4/companies/123/projects?status=active

# Get items assigned to a contact
GET /api/v4/contacts/456/items

Performance Tips

  • Index-friendly filters (fast): id, status, type, companyid, projectid
  • Full-text search (slower): search, searchname, searchdescription
  • Combine with sorting: Add sortby and sortorder for consistent ordering
  • Use specific searches: searchcode=ABC is faster than search=ABC
Combined Filters
cURL
curl 'https://api.proworkflow.com/api/v4/projects?' \
  'type=client&' \                    # Client projects
  'status=active&' \                  # That are active
  'search=website&' \                 # Containing "website"
  'tagid=5&' \                        # Tagged with ID 5
  'lastmodifiedfrom=7d' \             # Modified in last 7 days
  -H 'apikey: your-api-key'
Find Overdue Tasks
Request
GET /api/v4/items?
  status=pending,inprogress&
  dueto=0d&
  sortby=duedate&
  sortorder=asc

# Tasks that are not complete
# With due date before today
# Sorted by due date, oldest first
Recent Client Activity
Request
GET /api/v4/projects?
  type=client&
  lastmodifiedfrom=7d&
  fields=name,status,lastmodified,client&
  pagenumber=1&
  pagesize=20

# Client projects only
# Modified in last 7 days
# Select specific fields
# First 20 results
High-Value Unpaid Invoices
Request
GET /api/v4/invoices?
  status=submitted,authorised&
  search=!paid&
  sortby=total&
  sortorder=desc

# Not yet paid invoices
# Excluding those with "paid" in description
# Sorted by total, highest first