API V3 Documentation
Every call of the PQFORCE REST and JavaScript API V3, by module, with its data types.
Preview: this documents PQFORCE 4.26.6, which has not been released yet.
Introduction
This article explains the core concepts of the PQFORCE API and shows how to authenticate and make your first calls. PQFORCE is a multi-tenant, cloud-based SaaS platform for project, resource and portfolio management, and virtually every capability offered by the web client is also reachable programmatically through this API. The API is a REST API served over HTTPS that exchanges data as JSON, so it can be consumed from any modern programming language or integration platform. This document describes version 3 (V3) of the API, whose endpoints all live under the /API/V3/ path.
The API can be accessed in four different ways, each suited to a particular scenario: the client API used by the PQFORCE browser client, the public API for third-party systems and integrations, the maintenance API for tenant-independent administrative tasks, and the JavaScript API available to server-internal automations. Each access kind has its own authentication mechanism, summarized in the table below and detailed in the following chapters. Because PQFORCE is multi-tenant, most requests are executed in the context of a specific tenant, which is identified by its tenant alias.
Access to data and endpoints is governed by a fine-grained permission model, and every call runs with the permissions of the user it is bound to. For security reasons, error responses deliberately contain only coded references rather than detailed messages; the Error codes chapter explains how to resolve these codes. A machine-readable description of the whole API is also available in the OpenApi format, which you can use to generate a client library automatically.
The remainder of this document is organized into three parts. It first covers the general concepts — authentication, parameters, error handling and security considerations — that apply to all endpoints. It then documents each endpoint, grouped by module, including its HTTP syntax, required permissions, parameters and result type. Finally, it describes the data types exchanged by those endpoints. If you only need to integrate a third-party system, focus on the public API chapter; if you are writing server-side automations, refer to the JavaScript API chapter.
The different kinds of REST API
The client API
This is the way how the browser accesses the PQFORCE API. It calls the initial redirect to set the tenant and does interactive login. During both calls, the session cookie will be updated. Authorization is then done through the session cookie. Note: PUT, POST and DELETE calls must be done by adding the Csrf-Token header to protect the session cookie against cross-site request forgery attacks.
The public API
This is the way how customer systems or interfaces should access the PQFORCE API. A token is created and registered. By passing the token in the Authorization: Bearer header, the call is authorized.
The maintenance API
This is the way how tenant-independent maintenance calls are being made. A user-specific certificate and its asymmetric key pair are used to secure the exchange: the request body is signed with the client's private key (and verified by the server against the certificate), while the response body is encrypted by the server with the client's public key so that only the client can read it. The base85 encoded certificate is added to the request by using the Pqforce-Certificate header. The result of a challenge that has to be solved by the client to call maintenance API endpoints must be present in the Pqforce-Challenge header.
The JavaScript API
Almost all parts of the API are accessible from within server-internal JavaScript automations. This API is internal and not accessible by HTTP.
HTTP Header presence
| Client | Public | Maintenance | |
|---|---|---|---|
Cookie |
✔️ | ||
Authorization |
❌ | ✔️ | ❌ |
Pqforce-Certificate |
❌ | ❌ | ✔️ |
Pqforce-Challenge |
❌ | ❌ | ✔️ |
Csrf-Token |
(✔️) |
✔️ = Required (✔️) = Required only for PUT, POST and DELETE calls ❌ = Forbidden
Using the public API by 3rd party systems
The API concept
PQFORCE provides a REST API over HTTPS with JSON serialization. REST means Representational State Transfer and is a stateless, well-known technology. Refer to different documentations on the internet about these kinds of APIs.
Machine-readable documentation
A documentation following the OpenApi 3.1 standard may be downloaded at https:/
Note: By adding ?modules=modulename (i.e. https:/
The authentication concept
PQFORCE uses a bearer token authentication. By doing so, PQFORCE follows a standard way for API authentication that is widely used. The token can be retrieved by the consumer of the API (i.e. your application) and must be kept secret. Your app gets also a URL which allows a user to authorize the token and therefore opens access to PQFORCE by the API consumer (i.e. your application) owning that specifc token.
For security reasons, PQFORCE also offers the possibility to unauthorize a previously authorized token to deny further access (i.e. if the token might have been stolen or if a computing device was lost).
As a token is bound to the user that authorized it, the consumer (i.e. your application) can use the whole power of PQFORCE with the permissions of that user. Changes are annotated with the user's name as if he/she initiated these changes over the web client.
Retrieving and authenticating a token
-
Getting current PQFORCE version (optional, to check compatibility):
GET API/V3/MAIN/Versions -
Retrieving a new token:
POST API/.V3/ ACM/ ApiToken/ Create?tenantAlias={tnt} You have to replace
{tnt}by the alias of your tenant. That means, if you're starting PQFORCE with the URLhttps:/, then/ cloud. pqforce. com/ mycompany mycompanywould be your tenant alias.You have to provide information about the consumer (i.e. your app). This information will show up in PQFORCE when authorizing a token. Your request should look like the example below:
{
"application": "MyApplication",
"appVersion": "0.1",
"appCompany": "MyCompany"
}
- You should receive a HTTP
201return code saying that a new token was created. You receive a JSON structure with the secret token, an authorization URL and an authorization deadline:{ "apiToken": "thisismyverysecretapitoken", "authorizationUrl": "https:// cloud. pqforce. com/ mycompany?selid=9F7F48D73CDB4678B9F33AB1CFC637A2&authtokenid=8D22AFA7CEA049EFB8981B8993ADF1C4&seltype=ApiTokenList&id=ADM&type=Module", "authorizationDeadline": "2022-11-30T13:17:27.006Z" } - Open a browser and navigate to the given authorization URL (the consumer, i.e. your application, may open the browser automatically).
- Authorize the token by checking the contents of the messagebox that appears and clicking 'Authorize':
- Your token should now appear in the list of tokens.
Now, your consumer is ready to use the token.
Note: To unauthorize, just drag the list entry (the first cell) of your token onto the waste bin.
Using the token
Following the HTTP standard, the consumer (i.e. your application) must now add the secret token to every REST-API request (HTTP) by setting the Header Authorization to Bearer {yourtoken} (replace {yourtoken} by the secret token received earlier by calling POST API/). Example HTTP request:
GET /API/V3/ACM/CurrentUser HTTP/1.1
Authorization: Bearer thisismyverysecretapitoken
WebSockets
Besides the REST endpoints, PQFORCE pushes notifications over WebSockets. A third party system gets one from POST API/: it answers PF.WebSocketInfo with the URL to connect to, which is valid for a limited time only. On the WebSocket, the server then informs you about changed objects. Every message, in either direction, is a JSON object.
Keeping a WebSocket open
The server closes a WebSocket on which no message has been exchanged for 75 seconds; proxies between you and the server may close it even sooner. The server does not send keep-alive messages itself, so a quiet connection is closed even though nothing is wrong with it. To keep the WebSocket open, your client must send an empty message, the empty JSON object {}, at regular intervals; every 30 seconds is a safe choice. The server accepts and ignores it, and an empty text frame is tolerated just as well. A text that is not JSON at all, however, makes the server close the WebSocket (see below). A client should nevertheless be prepared for the connection to drop, and reconnect with a new URL when it does.
WebSocket close codes
When the server closes a WebSocket, the close frame carries one of these codes:
| Code | Meaning |
|---|---|
1000 |
Regular closure: the server ended the WebSocket, or it confirms a close the client initiated |
1002 |
Protocol error: the client violated RFC 6455, e.g. with an unmasked frame or a continuation frame that does not continue anything |
1003 |
Unacceptable data: a message could not be parsed as JSON |
1006 |
Abnormal closure: never sent by the server, but reported by your WebSocket library when the connection ended without a close frame, e.g. on the idle timeout above, a network failure or a server restart |
1009 |
Message too big: a frame exceeded the maximum payload length of the server (64 KiB by default) |
1011 |
Server error: the server failed while serving the WebSocket, e.g. because the client did not read the notifications as fast as the server had to send them |
After any of them, request a new URL and reconnect; the URL of a closed WebSocket cannot be reused. If the reconnect itself fails, the server answers the handshake with an HTTP return code instead of a close code: 404 if the URL is unknown or has expired, and 403 if the request comes from a different origin or a different client than the one that requested the URL.
Parameters
Language
The language of a request is the first of these that is available:
- the
Accept-LanguageHTTP header sent with the request, - the language stored in the preferences of the user the token belongs to,
en.
Only the first entry of the header is read, and it is read verbatim. Send a single language tag such as de — not a quality-value list such as de;q=0.9, en;q=0.8, which is taken as the literal language de;q=0.9, matches no translation and therefore falls back to English. A tag that carries a region, such as de-CH, falls back to de and then to en when no translation exists for it.
The header applies only to the request that carries it. It never changes the stored preference of the user.
The client API resolves the language the other way round: the stored preference of the signed-in user wins, and
Accept-Languageis consulted only when that user has none.
Time zone
The time zone of a request is the first of these that is available:
- the
X-TimezoneHTTP header sent with the request, - the time zone stored in the preferences of the user the token belongs to,
- the time zone of the server.
It decides how the server turns points in time into calendar days: which day an entry is counted on, where the slot boundaries of a timeline at Day granularity fall, and so on. Two requests that differ only in this header therefore report the same data cut at different day boundaries.
Like the language, the header applies only to the request that carries it and never changes the stored preference of the user.
The client API resolves the time zone the other way round: the stored preference of the signed-in user wins, and
X-Timezoneis consulted only when that user has none.
You may specify a time zone as follows:
- If the zone ID equals
Z, the server uses UTC. - If the zone ID consists of a single letter, a
400error code will be returned. - If the zone ID starts with
+or-, an offset is used. - If the zone ID equals
GMT,UTCorUTthen the server uses UTC. - If the zone ID starts with
UTC+,UTC-,GMT+,GMT-,UT+orUT-then the zone is a prefixed offset-based zone. - All other IDs are parsed as region-based zones. Region IDs must match the regular expression
[A-Za-z][A-Za-z0-9~/._+-]+otherwise a400error code will be returned.
Assertions
Many parameters and request-body members carry an assertion — a small constraint the server enforces on the supplied value. Where present, the assertion is shown for each parameter in the Assertion column of the endpoint reference below. If a value violates its assertion, the request is rejected with a 422 return code before it is processed. Assertions are documentation only: they are deliberately not reflected as constraints in the OpenApi schema or generated client types, so you should validate values against them yourself.
Read an assertion as follows:
- Presence.
non-nullmeans the value is mandatory.null(or no assertion at all) means it is optional — any further check applies only when a value is supplied. A&combines a presence rule with a further check, e.g.non-null & matches '...'. - Numeric ranges.
>is exclusive and>=is inclusive; the value under test is a placeholder letter between the bounds.n > 0means greater than zero;100 >= n >= 0means between 0 and 100 inclusive (the maximum is written first). A dotted name such aspqforce.performance.bulk.maxis a server-configured limit, whose current value you can read (see Server limits). - Date, time and duration ranges.
date,datetimeanddurationvalues take the very same>/>=spelling, with each bound written as a quoted literal in the format that type uses everywhere else in this API:'2024-12-31' >= d >= '2024-01-01','2024-12-31T23:59:59.999Z' >= at,'PT8H' >= t > 'PT0S'. A dotted name is again a server-configured limit (see Server limits). - Patterns.
matches '<regex>'requires the value to fully match the given regular expression (a doubled''denotes a literal quote), e.g.matches '[0-9A-F]{32}'for a UUID. - Arrays. A
;separates an array-level rule from a per-element rule.pqforce.performance.bulk.max >= length > 0;non-nullmeans a non-empty array within the configured maximum size, none of whose elements may be null.
An array-valued query parameter takes either a single value (?sort=name) or several values in double quotes, separated by a comma (?sort="name:desc","code").
Sorting
Endpoints that return a list usually accept an optional sort parameter. It is an array of sort criteria, applied in the given order; each element is propertyname, propertyname:asc or propertyname:desc, and a criterion without a direction sorts ascending. The description of each sort parameter in the endpoint reference below ends with Possible properties: followed by the property names that endpoint accepts. Where it also mentions dynamic properties, the ID of a property definition with one of the sortable semantics listed there may be used as a property name as well.
Property names are case-sensitive. An unknown property is rejected with a 422 return code, as is a request with more criteria than the configured pqforce.performance.sort.max (see Server limits).
Enumerations
Enumeration values are exchanged as strings and are case-sensitive: send them exactly as they are listed with their data type below. Most enumerations use PascalCase values (e.g. Day); a few keep an older spelling. Any other spelling is rejected with a 422 return code.
Server limits
A few of the limits that apply to your requests are not fixed by the API, but configured for the instance you are talking to. They may therefore differ on a dedicated or on-premises installation and they may change with a release. Read them once at start-up and use the values you receive; do not compile them into your application. Three endpoints provide them, because the limits have three different origins:
| Limit | Endpoint | Result |
|---|---|---|
| A configuration value shared by the whole instance | GET API/ |
The value as text/plain, or a 404 return code if the key is not published |
| The requests you may send in parallel | GET API/ |
PF.ParallelRequestsLimit |
| How long a deleted object stays restorable | GET API/V3/PF/UndeleteLimit |
PF.UndeleteLimit, or a 402 return code if the undelete feature is not licensed |
Published server configuration values
GET API/ answers with the value of one configuration key, as plain text. Only the keys listed below are published; every other key is answered with a 404 return code, so this endpoint is not a way of reading arbitrary server configuration. The defaults given are those of the base configuration. Every instance is free to override any of them, which is exactly why you should read the values instead of assuming them, and a key an instance does not configure at all is not published either - a 404 return code on a key from this list therefore means "not configured here", not "wrong key".
| Key | Default | Meaning |
|---|---|---|
pqforce.performance.bulk.max |
100 |
Maximum number of elements in one bulk parameter array |
pqforce.performance.sort.max |
8 |
Maximum number of sort criteria in one request |
pqforce. |
20971520 |
Maximum size of an uploaded attachment, in bytes |
pqforce. |
500 |
Maximum number of projects in one Gantt chart |
pqforce.planningrange.from / pqforce.planningrange.to |
-2 / 2 |
Years, relative to the current one, in which allocations may be planned |
pqforce.dayrange.from / pqforce.dayrange.to |
-10 / 10 |
Years, relative to the current one, in which day based planning is available |
pqforce.dayrange.max |
731 |
Maximum number of days one request may be answered a slot per day for |
pqforce. |
- | Regular expression every new password has to match |
pqforce. |
- | The password rule in words, for display to the user, one key per available language |
pqforce. / ...Blacklist |
- | File extensions accepted respectively rejected for uploads, a whitelist wins over the blacklist |
pqforce. / ...Blacklist |
- | MIME types accepted respectively rejected for uploads |
pqforce. |
true |
Whether plain text values are checked for control characters and markup |
pqforce. |
true |
Whether JSON bodies are checked for dangerous content in strings |
pqforce.website |
- | Public web address of this instance |
pqforce.login |
- | Base URL of the PQFORCE login service |
pqforce.support |
- | Support address of this instance |
An assertion that names one of these keys is bounded by its current value: pqforce.performance.bulk.max >= length > 0 on an array parameter means the array may hold at least one element and at most as many as that key says. Validating an upload against the extension, MIME type and size keys before sending it, and a new password against the password rule before submitting it, avoids rejections you would otherwise learn about only from the server.
Parallel requests
GET API/ returns the number of requests you may have in flight at the same time; further ones are answered with a 429 return code. The limit reported is the one that applies to you as you are calling: the limit per token when you authenticate with a bearer token, and the limit per user otherwise. The same URL therefore answers differently for a third party integration and for an interactive session. An application that reads data in parallel should keep its own concurrency below this value rather than relying on retries.
Tenant configuration values
GET API/V3/PF/Config/{configId} is a different endpoint and not a source of limits: it reads a configuration value of the current tenant, addressed by a known configuration ID instead of a dotted key, and it has PUT and DELETE counterparts for changing that value. Like ServerConfig it answers a 404 return code for an identifier it does not know.
Limits inside the JavaScript API
A JavaScript program does not need a request to learn its own runtime budget: sandbox.remainingRuntimeMs() reports the time left in the current execution. See Sandbox information for the configuration keys behind it.
Error codes
Due to security considerations, PQFORCE does not return error details, but codes refering to such details. This article describes how to find information about an error behind a certain code.
HTTP return codes
For a description of HTTP error codes refer to the huge amount of documentation in the web.
The most common HTTP return codes returned by PQFORCE API V3 endpoints
| Return code | Official meaning | Detailed definition for PQFORCE |
|---|---|---|
200 |
OK | The request has succeeded |
201 |
Created | A new object has been created |
202 |
Accepted | The request was accepted, but processing has not yet been completed |
303 |
See Other | This code is only used in interactive requests for redirecting the browser to the client application pages |
304 |
Not Modified | The data in cache is still valid (sent by static requests when given an ETag in the If-None-Match header) |
307 |
Temporary Redirect | The URL used is a deprecated or alias URL for another endpoint and the Location header indicates the URL for the effective endpoint |
308 |
Permanent Redirect | The URL used is a deprecated or alias URL for another endpoint and the Location header indicates the URL for the effective endpoint |
400 |
Bad Request | The request does not fulfil the requirements (i.e. missing parameters or headers, unknown query parameters, or an optional parameter your license does not include) |
401 |
Unauthorized | The authentication with the bearer token was not successful |
402 |
Payment Required | Licensing restrictions apply (i.e. an endpoint your license does not include or an expired license) |
403 |
Forbidden | Trying to access an endpoint or an object without permission for user bound to token (this code may also be returned if an object requested does not exist) |
404 |
Not Found | Trying to access an endpoint which does not exist or an object that is not existing (may have been deleted), or there is no result to return |
415 |
Unsupported Media Type | No or wrong request body type |
418 |
I'm a teapot | As all backend developers like to drink coffee, this code is never sent back |
422 |
Unprocessable Content | Syntactic or semantic error in request (i.e. a value that cannot be read as its type, wrong JSON structure, unknown sort property, assertion or XSS HTML sanitizer violation) |
429 |
Too Many Requests | Trying to send too many requests in parallel, the applicable limit is reported by GET API/ |
500 |
Internal Server Error | Some backend internal condition (bug or misconfiguration) is preventing the backend from processing your request |
503 |
Service Unavailable | Indicates that PQFORCE backend is in maintenance mode |
In case of error, HTTP requests might return additional information as JSON (or HTML, if endpoint is supposed to return text/html). In case of JSON, the structure is defined here: PF.ErrorInformation.
Note: There are more error codes and structures that may be returned by backend framework or 3rd party network components. Please refer to the widely available documentations in world wide web.
Return codes on bulk operations
For some bulk operations, PQFORCE may provide a return code per element. These codes (if provided) can be found in the header X-Detail-Codes of the response as comma separated integer values, one per element and in the order of the elements you sent. They have the same meaning as normal HTTP return codes.
The return code of the response as a whole follows the successful elements: as long as at least one element succeeded, you receive a success code together with the response body, and the elements that failed are reported only in X-Detail-Codes. If no element succeeded, the response carries the code of the first failed element (typically 404) and no body; X-Detail-Codes is present in this case as well.
The error codes returned by PQFORCE
In most cases when a request failed, PQFORCE returns an error code in a JSON structure. With only a few exceptions, these errors are logged in the error store which can be downloaded with status viewer. The code describes the first 4 characters of the error ID.
Deprecated calls
If a call to a deprecated endpoint (or with deprecated parameters) was made, a Deprecation HTTP header is added to the response.
Using the JavaScript API
PQFORCE server uses JavaScript for automation of certain processes. JavaScript is a programming language that is one of the core technologies of the World Wide Web. JavaScript programs run within the backend of PQFORCE and provides almost the whole set of functionality the REST API does. There are two main usage scenarios for JavaScript programs:
- A special task that is run manually from time to time, doing some bulk data manipulation
- A task that is triggered upon an event (i.e. a changed project)
Main principles of JavaScript programs in PQFORCE
PQFORCE backend provides a special environment for JavaScript programs. It is sandboxed which means that there is no direct access to PQFORCE server or hardware. All interaction with PQFORCE must be done over the API provided by PQFORCE (Pqf.*.*). However, core JavaScript functionality is also provided (i.e. string, number or array functionality and also JSON, Date and Math namespaces). Additional libraries like moment are optionally available.
A special console provides logging of errors and debug output for easier functionality or bug tracking. The console is available through the three functions console.log(...), console.warn(...) and console.error(...).
Every successful API V3 call returns a result. A call that delivers data (i.e. most GET requests and some POST requests) returns that data. A call that delivers no data (i.e. PUT, DELETE and some POST requests) returns the HTTP status code of the call as a number, e.g. 201 when an object was created and 200 when an existing one was updated. Only success codes are ever returned this way; a failed call throws an error as described below.
If API requests fail, error handling can be done the standard JavaScript way by using try ... catch blocks. The error contains a return code in the form of a three-digit number in the form #000 which corresponds to the standardized HTTP return codes (see HTTP return codes). This code is followed by a detail message. However, this message may be hidden due to security restrictions in productive environments. In this case, a four letter hex code helps identifying the log entry which may be retrieved by our technical staff.
Accessing different generations of PQFORCE API
Each JavaScript source/ sandbox has an attribute specifying which API generation is provided on the PQFORCE API root object Pqf. However, it is always possible to explicitly access other versions by accessing the versioned roots PqfV2 and PqfV3.
Trigger information and impersonation
When triggered by an event, the variable trigger is available and contains an objectType, objectId and action member describing the kind of change. The code runs in the context of the user that triggered the change. This means, the same access permissions of that user apply also for code execution. The API request Pqf.acm.getCurrentUser() returns detail information about that user.
Note: If the permissionOverride flag is set on the code, the user runs with the privileges (the same access permissions) of the user that last modified the script.
Console
You may use the console object's log(), warn() and error() functions for console output.
Storage
Each script has now its own storage. That means, that a script may store arbitrary data in an easy way without affecting other scripts and without the need of administrative privileges. A key is needed to define the object's place in the store. Therefore, any number of objects may be stored in parallel.
Use store.load(key) to get data and store.save(key, data) to save data (set data to null to clear store). store.list() returns an array with all keys in use.
Note: Scripts are isolated: Even by using the same key, different scripts will not see the same data. Use global store with admin privileges to share data across scripts.
Sandbox information
The sandbox object provides runtime information about the sandbox the script is running within. The following methods are available:
sandbox.id(): Returns a string containing the sandbox / script id.sandbox.remainingRuntimeMs(): Returns the remaining runtime in milliseconds. The maximal runtime per script may be adjusted in dedicated / on-premises environments by changing thepqforce.javascript.limits.run(limit of single execution in milliseconds) andpqforce.(hourly limit in milliseconds) value.javascript. limits. hourquota sandbox.engine(): Returns a String containing the JavaScript engine version. See https:// for information about feature compatibility.mozilla. github. io/ rhino/ compat/ engines. html
Html functions
The Html namespace offers easy HTML encoding and decoding by using Html.encode(...) or Html.decode(...). This may be useful for handling descriptions of objects. Furthermore, sanitizing is available by using Html.sanitize(...).
UUIDs
The Uuid namespace offers the possibility to easily generate new random UUIDs by using Uuid.generate().
Deprecated calls
If a call to a deprecated endpoint (or with deprecated parameters) was made, a deprecation warning is logged into the JavaScriptConsole.
Security considerations
Defeating XSS attacks (HTML-sanitizing)
Apart from some special members of objects (like JavaScript code), all strings containing <, > or & are considered to contain HTML. Therefore, they must be HTML-encoded (before sending, if they're not yet HTML) or HTML-decoded (after receiving, if only plain text is desired). Members that do not need to be sanitized (i.e. JavaScript code) is marked with an insecure-flag. These members are not encoded.
JavaScript and public API endpoints
The following endpoint definitions apply to the following version of PQFORCE API:
| Name | Value |
|---|---|
| API generation | V3 |
| API version | 4.26.6 |
| API fingerprint | 1cQT-Y9Eh-AqNv-ae4s-2Qvy-UyTx-6JVs |
Look for newest documentation at: YouTrack dashboard or TeamCity.
- ACM 60
- ACT 51
- ALC 51
- ATM 22
- CHT 2
- CLF 26
- CLS 5
- DGN 1
- EXP 10
- FCO 101
- LCY 40
- LIC 1
- MAIN 7
- MSG 17
- MTG 27
- NAV 1
- PF 110
- PI 14
- PM 297
- RES 99
- RSK 18
- STG 63
- TDP 18
API data contracts
ACM