Overview
This article explains how to use the Project Insight REST API to import user PTO (Paid Time Off) records from an Excel file. This endpoint allows you to bulk import PTO data for multiple users efficiently.
API Endpoint
URL:
https://{workspace}.projectinsight.net/api/user-pto/pto-import-from-excelContent-Type:
multipart/form-data
Authentication
All API requests require authentication using an API token. For information on obtaining and using your API token, please see: Your API Token
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
automaticallyFitPTOHours |
boolean | No | When true, automatically adjusts PTO hours to fit within valid ranges. Default: false
|
deleteExistingPTOsBetweenDates |
boolean | No | When true, deletes existing PTO records within the specified date range before importing. Default: false
|
deleteStartDate |
datetime | No | Start date for deletion range (only used when deleteExistingPTOsBetweenDates is true). Format: YYYY-MM-DDTHH:mm:ss
|
deleteEndDate |
datetime | No | End date for deletion range (only used when deleteExistingPTOsBetweenDates is true). Format: YYYY-MM-DDTHH:mm:ss
|
Request Body
The request body must contain the Excel file as a multipart/form-data upload. The file should be in .xlsx format.
Excel File Format
Template example file attached at bottom of article
Your Excel file should contain the PTO data formatted according to Project Insight's import template. Ensure that:
- Column headers match the expected field names
- Date formats are consistent
- User identifiers (email or user ID) are valid
Code Examples
import os
import requests
# pip install requests
workspace = "your-workspace"
api_token = "your-api-token"
# Example Excel file to upload
file_path = r"C:\temp\pto-import.xlsx"
# Endpoint
api_url = f"https://{workspace}.projectinsight.net/api/user-pto/pto-import-from-excel"
# Optional query string parameters
params = {
"automaticallyFitPTOHours": "false",
"deleteExistingPTOsBetweenDates": "false",
# Optional date filters (only used if deleteExistingPTOsBetweenDates = true)
# "deleteStartDate": "2026-01-01T00:00:00",
# "deleteEndDate": "2026-12-31T23:59:59",
}
with open(file_path, "rb") as f:
files = {
# field name expected by the server : (filename, file object, MIME type)
"file": (
os.path.basename(file_path),
f,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
}
headers = {
"api-token": api_token
# Do NOT manually set Content-Type
# requests will generate:
# Content-Type: multipart/form-data; boundary=...
}
response = requests.post(
api_url,
headers=headers,
params=params, # query string parameters
files=files, # multipart file upload
timeout=60
)
print(f"Status: {response.status_code}")
print("Response Headers:", response.headers)
try:
print("JSON Response:", response.json())
except ValueError:
print("Raw Response:", response.text)
const fs = require('fs');
const path = require('path');
const FormData = require('form-data');
// npm install form-data node-fetch
// For Node.js 18+, fetch is built-in
const workspace = 'your-workspace';
const apiToken = 'your-api-token';
// Example Excel file to upload
const filePath = 'C:/temp/pto-import.xlsx';
// Build the URL with query parameters
const baseUrl = `https://${workspace}.projectinsight.net/api/user-pto/pto-import-from-excel`;
const params = new URLSearchParams({
automaticallyFitPTOHours: 'false',
deleteExistingPTOsBetweenDates: 'false',
// Optional date filters (only used if deleteExistingPTOsBetweenDates = true)
// deleteStartDate: '2026-01-01T00:00:00',
// deleteEndDate: '2026-12-31T23:59:59',
});
const apiUrl = `${baseUrl}?${params.toString()}`;
// Create form data with the file
const form = new FormData();
form.append('file', fs.createReadStream(filePath), {
filename: path.basename(filePath),
contentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
// Make the request
fetch(apiUrl, {
method: 'POST',
headers: {
'api-token': apiToken,
...form.getHeaders()
},
body: form
})
.then(async (response) = {
console.log(`Status: ${response.status}`);
const data = await response.json();
console.log('JSON Response:', data);
})
.catch((error) = {
console.error('Error:', error.message);
});
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string workspace = "your-workspace";
string apiToken = "your-api-token";
// Example Excel file to upload
string filePath = @"C:\temp\pto-import.xlsx";
// Build the URL with query parameters
string baseUrl = $"https://{workspace}.projectinsight.net/api/user-pto/pto-import-from-excel";
var queryParams = new System.Collections.Generic.Dictionary<string, string>
{
{ "automaticallyFitPTOHours", "false" },
{ "deleteExistingPTOsBetweenDates", "false" }
// Optional date filters (only used if deleteExistingPTOsBetweenDates = true)
// { "deleteStartDate", "2026-01-01T00:00:00" },
// { "deleteEndDate", "2026-12-31T23:59:59" }
};
string queryString = string.Join("&",
queryParams.Select(kvp => $"{kvp.Key}={Uri.EscapeDataString(kvp.Value)}"));
string apiUrl = $"{baseUrl}?{queryString}";
using (var client = new HttpClient())
using (var form = new MultipartFormDataContent())
using (var fileStream = File.OpenRead(filePath))
{
// Add API token header
client.DefaultRequestHeaders.Add("api-token", apiToken);
// Add file to form data
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
form.Add(fileContent, "file", Path.GetFileName(filePath));
// Send request
client.Timeout = TimeSpan.FromSeconds(60);
var response = await client.PostAsync(apiUrl, form);
Console.WriteLine($"Status: {(int)response.StatusCode}");
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {responseBody}");
}
}
}
curl -X POST "https://your-workspace.projectinsight.net/api/user-pto/pto-import-from-excel?automaticallyFitPTOHours=false&deleteExistingPTOsBetweenDates=false" \
-H "api-token: your-api-token" \
-F "file=@/path/to/pto-import.xlsx;type=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
$workspace = "your-workspace"
$apiToken = "your-api-token"
$filePath = "C:\temp\pto-import.xlsx"
# Build URL with query parameters
$baseUrl = "https://$workspace.projectinsight.net/api/user-pto/pto-import-from-excel"
$queryParams = @{
automaticallyFitPTOHours = "false"
deleteExistingPTOsBetweenDates = "false"
# Optional date filters (only used if deleteExistingPTOsBetweenDates = true)
# deleteStartDate = "2026-01-01T00:00:00"
# deleteEndDate = "2026-12-31T23:59:59"
}
$queryString = ($queryParams.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "&"
$apiUrl = "$baseUrl`?$queryString"
# Read file and create multipart form
$fileName = Split-Path $filePath -Leaf
$fileBytes = [System.IO.File]::ReadAllBytes($filePath)
$fileEnc = [System.Text.Encoding]::GetEncoding('ISO-8859-1').GetString($fileBytes)
$boundary = [System.Guid]::NewGuid().ToString()
$LF = "`r`n"
$bodyLines = @(
"--$boundary",
"Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"",
"Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet$LF",
$fileEnc,
"--$boundary--$LF"
) -join $LF
$headers = @{
"api-token" = $apiToken
}
try {
$response = Invoke-RestMethod -Uri $apiUrl -Method Post -ContentType "multipart/form-data; boundary=$boundary" -Body $bodyLines -Headers $headers -TimeoutSec 60
Write-Host "Response:" $response
}
catch {
Write-Host "Status:" $_.Exception.Response.StatusCode.value__
Write-Host "Error:" $_.Exception.Message
}
Response
Success Response
If authenticated correctly a 200 OK status will be returned but you will need to look at the body of the respnose to see if there was any issues during import.
Error Responses
| Status Code | Description |
|---|---|
400 |
Bad Request - Invalid file format or parameters |
401 |
Unauthorized - Invalid or missing API token |
403 |
Forbidden - Insufficient permissions |
500 |
Internal Server Error |
Tips and Best Practices
automaticallyFitPTOHours - Enable this option if you want the system to automatically adjust hours that exceed daily limits.deleteExistingPTOsBetweenDates, ensure you have a backup of existing PTO records before proceeding.
If you have questions or need assistance, please contact Project Insight Support.
Comments
0 comments
Please sign in to leave a comment.