top of page

Connecting to the UniPhi API to build your own app

11 minutes ago
5 min read

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

  1. Log in to UniPhi as an administrator.

  2. Go to Admin > Authentication > OAuth.

  3. Add a new registration and give it a name that describes the app, for example "Finance dashboard".

  4. Choose the person the app will impersonate. The app sees and does exactly what this person is allowed to see and do in UniPhi.

  5. 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_SECRET

The 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

  1. Put a small server between your users and UniPhi. Your front end calls your server, and your server holds the secret and calls UniPhi.

  2. Cache the token and refresh it before it expires. If a call returns 401, get a new token and retry once.

  3. 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.

  4. Pull large datasets on a schedule into your own store if you are building reporting, and query that store from your app.

  5. 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.

 
 
 

Related Posts

See All

Comments


Subscribe to our UniPhi newsletter

Thanks for submitting!

  • Twitter
  • Youtube
  • Linkedin

© 2024 by UniPhi

bottom of page