Connecting to the UniPhi API to build your own app
UniPhi has a REST API that gives you the same data you see on screen: projects, budgets, contracts, cash flow, timesheets, resource plans, issues, documents and more. This article shows you how to register an app, get an access token and make your first calls, so you can build your own dashboards, integrations or internal tools on top of your UniPhi data.
You will need a UniPhi administrator to complete step 1. The rest is for the developer building the app.
Before you start
Your UniPhi address. Every customer has their own site, for example https://yourcompany.uniphi-software.com or https://yourcompany.uniphi.com.au. The API lives on the same address under /api. The examples below use yourcompany; replace it with your own.
Administrator access to UniPhi, to register the app.
Somewhere to run server-side code. Your app's secret must stay on a server. Do not call the UniPhi API directly from a web page or mobile app, because anyone could read the secret from it.
Step 1: Register your app in UniPhi
Log in to UniPhi as an administrator.
Go to Admin > Authentication > OAuth.

Add a new registration and give it a name that describes the app, for example "Finance dashboard".
Choose the person the app will impersonate. The app sees and does exactly what this person is allowed to see and do in UniPhi.
Save the registration and copy the client ID and client secret.
Tip: create a dedicated person for each app (for example "Finance Dashboard API") instead of impersonating a real staff member. Give that person access to only the projects and modules the app needs. The app keeps working when staff leave, and you can see clearly which changes the app made.
Treat the client secret like a password. Store it in your server's configuration or a secrets vault, never in source code, a spreadsheet or an email.
Step 2: Get an access token
Your app swaps its client ID and secret for an access token using the OAuth 2.0 client credentials grant. Send a form-encoded POST to /oauth/token:
curl -X POST https://yourcompany.uniphi-software.com/oauth/token \
-d grant_type=client_credentials \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRETThe response is JSON:
{
"access_token": "eyJ0eXAi...",
"token_type": "bearer",
"expires_in": 604800
}expires_in is the token's life in seconds. Keep the token in memory and reuse it for every call until shortly before it expires, then request a new one. Do not request a new token for every API call.
Step 3: Call the API
Send the token in the Authorization header of every request:
curl https://yourcompany.uniphi-software.com/api/Project?page=1&pageSize=50 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"You get back a JSON array of projects. A single record is available by ID, for example /api/Project/498.
Example in Python
import requests
BASE = "https://yourcompany.uniphi-software.com"
token = requests.post(f"{BASE}/oauth/token", data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
}).json()["access_token"]
projects = requests.get(
f"{BASE}/api/Project",
params={"page": 1, "pageSize": 50},
headers={"Authorization": f"Bearer {token}"},
).json()
for p in projects:
print(p["ID"], p["Name"])Example in C#
using System.Net.Http.Headers;
using System.Text.Json;
var http = new HttpClient { BaseAddress = new Uri("https://yourcompany.uniphi-software.com") };
var tokenResponse = await http.PostAsync("/oauth/token", new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = clientId,
["client_secret"] = clientSecret,
}));
tokenResponse.EnsureSuccessStatusCode();
using var tokenJson = JsonDocument.Parse(await tokenResponse.Content.ReadAsStringAsync());
var token = tokenJson.RootElement.GetProperty("access_token").GetString();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var projects = await http.GetStringAsync("/api/Project?page=1&pageSize=50");Step 4: Find the endpoints you need
Your UniPhi site publishes its own interactive API reference. Log in to UniPhi in your browser, then open:
The reference lists every resource, its parameters and the shape of the data. The machine-readable specification, which you can import into tools such as Postman, is at /swagger/docs/v1.
Some of the most used resources:
Resource | What it returns |
/api/Project | Projects, with sector, type, location and current phase |
/api/BudgetVersion, /api/BudgetSnapshot | Budget versions and the budget by account code |
/api/ChartOfAccounts | Charts of accounts and their codes |
/api/Contract, /api/ContractDeliverable | Contracts and their line items |
/api/Cashflow | Budget, forecast and actual by period |
/api/Timesheet | Timesheet entries with hours and rates |
/api/ResourcePlan | Forecast hours, cost and charge by person and project |
/api/Issue, /api/IssueComment | Issues and their comment threads |
/api/Person, /api/Organisation | People and organisations |
How the API behaves
Lists are plain JSON arrays. There is no wrapper object and no total count.
Paging varies by resource. Many resources accept page and pageSize. Some return everything in one response, which can be tens of thousands of rows. Check the reference for each resource and allow a generous timeout for the large ones.
Filter on the server where you can. Most resources accept filters such as projectID, and date-based resources accept startDate and endDate. Filtering in the request is much faster than downloading everything and filtering in your app.
Unrecognised parameters are ignored. If a filter seems to have no effect, check its spelling against the reference. You will not get an error.
Dates are ISO format, for example 2026-07-01.
Related records are nested. A timesheet row, for example, carries its project as { "ID": 498, "Name": "..." }.
Permissions follow the impersonated person. If a project is missing from your results, check that person's access in UniPhi.
Creating and updating records
The API also writes. Use POST to create, PUT with the record's ID to update and DELETE to remove. Send JSON with a Content-Type: application/json header:
curl -X POST https://yourcompany.uniphi-software.com/api/Organisation \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "Name": "Acme Constructions Pty Ltd" }'A successful create returns the new record, including its ID. If a required field is missing you get a 400 response with a message naming the field.
Writes go straight into your live data and are recorded against the impersonated person. Build and test against a test project first, and ask us if you would like a separate test site.
Recommended design for your app
Put a small server between your users and UniPhi. Your front end calls your server, and your server holds the secret and calls UniPhi.
Cache the token and refresh it before it expires. If a call returns 401, get a new token and retry once.
Cache reference data such as projects, charts of accounts and people. It changes slowly, so there is no need to fetch it on every page load.
Pull large datasets on a schedule into your own store if you are building reporting, and query that store from your app.
Use one registration per app. You can then withdraw one app's access without affecting the others.
Troubleshooting
What you see | Likely cause |
400 from /oauth/token | The client ID or secret is wrong, or the request body is not form-encoded. |
401 from an /api call | The token is missing, expired or malformed. The header must read Authorization: Bearer followed by a space and the token. |
You are redirected to the login page | The address is not under /api, or you opened the API reference without logging in to UniPhi first. |
Results are empty or records are missing | The impersonated person does not have access to those projects or modules. |
A filter has no effect | The parameter name is not one the resource supports, so it was ignored. |
400 on a create or update | A required field is missing. The message names it. |
500 | Usually a parameter value the resource does not accept. Check the reference for the allowed values. |
Need help?
If you are stuck, raise a support ticket and include the address you called, the HTTP status you received and the response body. Never include your client secret or an access token in a ticket. If vibe coding, all you need is step 1 and your AI agent will help you from there.



Comments