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.
curl 'https://api.proworkflow.com/api/v4/companies?fields=name,code&pagesize=10' \ -H 'apikey: your-api-key'
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.
curl 'https://api.proworkflow.com/api/v4/projects' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIs...'
curl 'https://api.proworkflow.com/api/v4/projects' \ -H 'apikey: xxxx-xxxx-xxxx-xxxx-PWFxxxx'
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:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the window (500) |
X-RateLimit-Remaining | Requests remaining in current window |
X-RateLimit-Reset | Seconds 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-Remainingheader 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.
HTTP/1.1 200 OK X-RateLimit-Limit: 500 X-RateLimit-Remaining: 485 X-RateLimit-Reset: 28
{
"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
| Parameter | Type | Description |
|---|---|---|
| pagenumber | integer | Page number to retrieve (starts at 1) |
| pagesize | integer | Number of items per page (max varies by endpoint) |
| includetotalrows | boolean | Set 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:
| Parameter | Description |
|---|---|
| sortby | Field to sort by (varies by endpoint) |
| sortorder | asc (ascending) or desc (descending) |
Common sort fields:
id- Record IDname- Name fieldcode- Code/identifiercreatedon- Creation datelastmodified- Last modification datesortorder- 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:
| Field | Description |
|---|---|
| placeafterid | ID of the record this item should be positioned after |
| placebeforeid | ID of the record this item should be positioned before |
Endpoints supporting custom ordering:
PUT /api/v4/projects/:projectid/orderPUT /api/v4/projects/items/:itemid/orderPUT /api/v4/quotes/items/:itemid/orderPUT /api/v4/invoices/items/:itemid/orderPUT /api/v4/invoices/:invoiceid/orderPUT /api/v4/quotes/:quoteid/order
# 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}'
{
"status": "success",
"message": "Project item reordered"
}
curl 'https://api.proworkflow.com/api/v4/companies?pagenumber=1&pagesize=50&sortby=name&sortorder=asc' \ -H 'apikey: your-api-key'
{
"status": "success",
"data": [
{"id": 1, "name": "Acme Corp"},
{"id": 2, "name": "Beta Inc"}
],
"meta": {
"count": 2,
"page": 1,
"pagesize": 50
}
}
curl 'https://api.proworkflow.com/api/v4/companies?pagenumber=1&pagesize=50&includetotalrows=true' \ -H 'apikey: your-api-key'
{
"status": "success",
"data": [
{"id": 1, "name": "Acme Corp"},
{"id": 2, "name": "Beta Inc"}
],
"meta": {
"count": 2,
"total": 150,
"page": 1,
"pagesize": 50
}
}
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.
| Parameter | Example | Description |
|---|---|---|
| fields | name,code,type | Comma-separated list of fields to return |
| fields | all | Return all available fields (subject to permissions) |
Notes:
- The
idfield 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:
| Parameter | Example | Description |
|---|---|---|
| search | acme | General search across default fields |
| search | !test | Negation search (exclude matches) |
| searchname | corp | Search in name field only |
| searchcode | ABC | Search in code field only |
| searchemail | @example.com | Search in email field only |
Negation Search:
- Prefix search term with
!to exclude matches - Example:
?search=!testreturns items NOT containing "test" - Works with field-specific searches too:
?searchname=!admin
Type & Status Filters
Filter by enumerated values like type, status, or state:
| Parameter | Example | Description |
|---|---|---|
| type | client | Filter by single type |
| type | client,contractor | Multiple types (OR logic) |
| status | active | Filter by status |
| status | active,pending | Multiple 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:
| Parameter | Example | Description |
|---|---|---|
| lastmodifiedfrom | 2024-01-18T13:20 | ISO8601 date/time |
| lastmodifiedfrom | 12h | Last 12 hours |
| lastmodifiedfrom | 5d | Last 5 days |
| lastmodifiedfrom | 2w | Last 2 weeks |
| lastmodifiedfrom | 1m | Last 1 month |
| lastmodifiedto | 2024-12-31 | Modified before date |
| createdfrom | 2024-01-01 | Created after date |
| createdto | 2024-12-31 | Created 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:
| Parameter | Example | Description |
|---|---|---|
| id | 1,2,3,4 | Specific IDs (comma-separated) |
| idfrom | 100 | IDs greater than or equal to value |
| idto | 200 | IDs less than or equal to value |
Relationship Filters
Filter by related entities:
| Parameter | Example | Description |
|---|---|---|
| companyid | 123 | Filter by company |
| projectid | 456 | Filter by project |
| contactid | 789 | Filter by contact |
| tagid | 1,2,3 | Has ANY of these tags (OR) |
| divisionid | 10 | Filter 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
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
| Code | Meaning | When Used |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH, DELETE |
| 201 | Created | Successful POST (resource created) |
| 204 | No Content | Successful DELETE (no response body) |
| 400 | Bad Request | Validation failed, invalid parameters |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Authenticated but insufficient permissions |
| 404 | Not Found | Resource doesn't exist or no access |
| 409 | Conflict | Resource conflict (e.g., duplicate) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server error |
| 503 | Service Unavailable | Temporary 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.
{
"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"
]
}
{
"status": "error",
"data": [
"You do not have permission to view invoices"
]
}
{
"status": "error",
"data": [
"Authentication required"
]
}
{
"status": "error",
"data": [
"Company not found"
]
}
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 ID | Type | Description |
|---|---|---|
| 1 | Client | External client users with limited access |
| 2 | Contractor | External contractors with project access |
| 3 | Staff | Internal staff with full system access |
| 4 | Other | Custom user type with configurable access |
Account Plans
Account plans determine available features:
| Plan ID | Plan | Features |
|---|---|---|
| 1 | Basic | Standard features, single division |
| 3 | Advanced | Teams, groups, multiple divisions |
Permission System
Permissions are identified by numeric IDs and control specific actions:
| Permission ID | Name | Controls |
|---|---|---|
| 2 | ViewAllWork | See all work across the account |
| 3 | ViewItem | View tasks and items |
| 4 | ViewProject | View projects |
| 7 | ViewTimelog | View time entries |
| 8 | ViewContactNote | View contact/company notes |
| 11 | ViewSensitiveBusinessInfo | View financial/budget fields |
| 12 | ViewInvoice | View invoices |
| 13 | ViewQuote | View quotes |
| 14 | ViewCustomfields | View custom fields |
| 18 | ViewUserPermissions | View user permissions |
| 23 | ViewTeamWork | See team members' work |
| 24 | ViewAllWork | Division-scoped view all |
Permission Actions
Each permission supports different actions:
view- Read access to resourcesadd- Create new resourcesedit- Update existing resourcesdelete- Delete resources
Sensitive Field Filtering
Users without ViewSensitiveBusinessInfo (Permission 11) have financial fields automatically hidden:
Affected Resources & Fields:
| Resource | Hidden Fields |
|---|---|
| Project | budget, estBillAmount, invoicedAmount, paidAmount, estProfit, costs |
| Invoice | estBillAmount, estBillAmountTax, paid, budget, amounts |
| ProjectItem | budget, billAmount, estBillAmount, labour prices |
| Contact | payrate, payratemultiplier, defaulthourlyrateid |
| Company | Financial 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
divisionidparameter 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:
| Mode | Name | Visibility |
|---|---|---|
| 0 | ViewAllWork | See all work (requires permission) |
| 1 | Default | See only own assigned work |
| 2 | ViewTeamWork | See team members' work |
{
"status": "error",
"data": [
"You do not have permission to view invoices"
]
}
// User without ViewSensitiveBusinessInfo sees: { "status": "success", "data": { "id": 123, "name": "Project Alpha", "status": "active" // budget field omitted // estBillAmount field omitted } }
// 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 Value | Behavior |
|---|---|
fields=name,code,type | Returns only specified fields (plus id) |
fields=all | Returns all available fields |
| omitted | Returns default fields for endpoint |
Important Notes:
- The
idfield 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
| Type | Format | Example | Notes |
|---|---|---|---|
| ID | int64 | 12345 | Unique identifier |
| String | UTF-8 text | "Acme Corp" | Text fields |
| Date | ISO 8601 | "2024-06-15" | Date only |
| DateTime | ISO 8601 | "2024-06-15T14:30:00" | Date and time |
| Decimal | float64 | 1250.50 | Money, percentages |
| Hours | Decimal | 1.5 | 1.5 = 1h 30m |
| Boolean | bool | true | True/false flags |
| Binary | Hex string | "0xABCDEF" | Sort order field |
| Array | JSON 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):
| Resource | Sensitive Fields |
|---|---|
| Project | budget, estBillAmount, estBillAmountTax, estInTotal, estInTotalTax, estProfit, estProfitPercentage, invoicedAmount, invoicedAmountTax, paidAmount, paidAmountTax, actualCost, actualRevenue, actualProfit |
| ProjectItem | budget, billAmount, estBillAmount, estInTotal, inLabourPrice, outLabourPrice, costPerHour, estCost, estRevenue, estProfit |
| Invoice | estBillAmount, estBillAmountTax, estIn, estInTax, paid, paidTax, budget, amounts, tax rates |
| Quote | total, totalTax, budget, itemized costs |
| Contact | payrate, payratemultiplier, defaulthourlyrateid, costPerHour, chargeoutrate |
| Timelog | cost, charge, profit, hourlyRate |
| Expense | cost, markup, total |
JSON Array Fields
Many resources include arrays of related objects:
| Field | Structure | Available 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:
| Entity | Valid Status Values |
|---|---|
| Company | active, inactive, archived |
| Contact | active, inactive, archived |
| Project | pending, active, complete, cancelled, archived |
| Task/Item | pending, inprogress, complete, cancelled |
| Invoice | draft, submitted, authorised, paid, voided |
| Quote | pending, sent, accepted, declined, expired |
Computed vs Stored Fields
Stored Fields - Directly from database, support filtering/sorting:
id,name,code,status,typecreatedon,lastmodifiedstartdate,duedate
Computed Fields - Calculated at runtime, may not support filtering:
percentcomplete- Calculated from task completiontimeremaining- Based on estimates vs actualsprofitmargin- Calculated from costs/revenuefullname- Concatenated from firstname + lastnameage- Calculated from dates
Examples
Best Practices
- Request only needed fields - Reduces payload size and improves performance
- Check for field existence - Fields may be omitted based on permissions
- Handle null values - Many fields can be null when not set
- Parse arrays properly - Array fields may be empty arrays
[]or omitted entirely - Respect field types - Don't assume string when API returns numbers
GET /api/v4/companies
# Returns: id, code, name, type (defaults)
GET /api/v4/projects?fields=name,status,budget,tags # Returns: id, name, status, budget, tags # Note: budget may be hidden if user lacks permission
GET /api/v4/contacts?fields=all # Returns all available fields for contacts # Sensitive fields still subject to permissions
{
"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:
- Reject entire batch (default) - Returns error, no items created
- 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.
{
"status": "success",
"message": "3 companies created successfully",
"data": {
"created": 3,
"ids": [101, 102, 103]
}
}
{
"status": "error",
"data": [
"Item 2: Name is required",
"Item 3: Invalid type 'invalid'. Allowed: client, contractor, staff, other"
]
}
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 } ]'
// 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 ago5d- 5 days ago2w- 2 weeks ago1m- 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:
- They are null and marked with
omitempty - User lacks permission to view them
- They weren't requested via
fieldsparameter - They are empty arrays or objects
// API returns string IDs in some cases const companyId = parseInt(response.companyid, 10);
// Parse ISO8601 date const createdDate = new Date(response.createdon); // Format for API input const isoDate = createdDate.toISOString().split('T')[0];
// 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.
Text Search
Search within text fields using various search parameters:
General Search
GET /api/v4/companies?search=acme
Searches across default searchable fields (usually name, code, description).
Field-Specific Search
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.
Negation Search
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
sortbyandsortorderfor consistent ordering - Use specific searches:
searchcode=ABCis faster thansearch=ABC
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'
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
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
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