Skip to content

Quickstart

This guide takes you from credentials to working API calls: first a token, then creating a project and a work order (the write surface), then pulling time registrations (the read surface).

  • API credentials (client_id and client_secret) — see Authentication
  • A tool for making HTTP requests (curl, Postman, or your language of choice). The bash examples also use jq to read JSON responses
Terminal window
# Test (default for all examples)
export DITIO_IDENTITY_BASE="https://identity.ditio.dev"
export DITIO_API_BASE="https://core-api.ditio.dev/core"
export DITIO_REPORTING_BASE="https://core-api.ditio.dev/reporting"
  1. Get an access token for the Integration API, with scope ditioapiv3. Steps 2 and 3 use it.

    Terminal window
    TOKEN=$(curl -s -X POST "$DITIO_IDENTITY_BASE/connect/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=YOUR_CLIENT_ID" \
    -d "client_secret=YOUR_CLIENT_SECRET" \
    -d "scope=ditioapiv3" | jq -r '.access_token')
  2. Create a project. The response contains the project’s generated id — you need it in the next step.

    Terminal window
    curl -X POST "$DITIO_API_BASE/api/v4/integration/projects" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
    "companyId": "YOUR_COMPANY_ID",
    "projectNumber": "P-1001",
    "name": "Prosjekt Fjellhall",
    "active": true
    }'
  3. Create a work order in that project. That’s the minimum working setup: a project with a work order that employees can register time against.

    Terminal window
    curl -X POST "$DITIO_API_BASE/api/v4/integration/tasks" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
    "companyId": "YOUR_COMPANY_ID",
    "projectId": "PROJECT_ID_FROM_STEP_2",
    "externalId": "WO-001",
    "name": "Graving byggegrop",
    "active": true
    }'
  4. Pull time registrations. Once time has been registered (in test there’s usually existing synced data), export it through the Data Extraction API. That API needs its own token, with scope reportingapiv1.

    If the token request returns 400 / invalid_scope, your client isn’t granted reportingapiv1 — ask [email protected] to enable it. See Authentication.

    Terminal window
    REPORTING_TOKEN=$(curl -s -X POST "$DITIO_IDENTITY_BASE/connect/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=YOUR_CLIENT_ID" \
    -d "client_secret=YOUR_CLIENT_SECRET" \
    -d "scope=reportingapiv1" | jq -r '.access_token')
    curl -H "Authorization: Bearer $REPORTING_TOKEN" \
    "$DITIO_REPORTING_BASE/v1/time-registrations?FromDateTime=2026-01-01&ToDateTime=2026-01-31"

The extraction response contains a data array and — when there is more data — a continuationToken (the full response envelope is in Pagination):

{
"data": [],
"links": { "next": "..." },
"continuationToken": "eyJ..."
}

Pass the token back as ContinuationToken to get the next page, and repeat until it’s empty. See Pagination.

import os
import requests
IDENTITY_BASE = os.environ.get("DITIO_IDENTITY_BASE", "https://identity.ditio.dev")
API_BASE = os.environ.get("DITIO_API_BASE", "https://core-api.ditio.dev/core")
REPORTING_BASE = os.environ.get("DITIO_REPORTING_BASE", "https://core-api.ditio.dev/reporting")
def get_token(scope):
response = requests.post(
f"{IDENTITY_BASE}/connect/token",
data={
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"scope": scope,
},
timeout=30,
)
response.raise_for_status()
return response.json()["access_token"]
# Step 1: authenticate for the Integration API
headers = {"Authorization": f"Bearer {get_token('ditioapiv3')}"}
# Step 2: create a project
response = requests.post(
f"{API_BASE}/api/v4/integration/projects",
headers=headers,
json={
"companyId": "YOUR_COMPANY_ID",
"projectNumber": "P-1001",
"name": "Prosjekt Fjellhall",
"active": True,
},
timeout=30,
)
response.raise_for_status()
project = response.json()
# Step 3: create a work order in it
requests.post(
f"{API_BASE}/api/v4/integration/tasks",
headers=headers,
json={
"companyId": "YOUR_COMPANY_ID",
"projectId": project["id"],
"externalId": "WO-001",
"name": "Graving byggegrop",
"active": True,
},
timeout=30,
).raise_for_status()
# Step 4: the Data Extraction API needs its own token, with scope reportingapiv1
reporting_headers = {"Authorization": f"Bearer {get_token('reportingapiv1')}"}
# Pull time registrations, following pagination
all_records = []
params = {"FromDateTime": "2026-01-01", "ToDateTime": "2026-01-31"}
while True:
response = requests.get(
f"{REPORTING_BASE}/v1/time-registrations",
headers=reporting_headers, params=params, timeout=30,
)
response.raise_for_status()
page = response.json()
all_records.extend(page["data"])
continuation = page.get("continuationToken")
if not continuation:
break
params["ContinuationToken"] = continuation
print(f"Fetched {len(all_records)} time registrations")

The same four steps as a .NET console app (Program.cs):

using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
var identityBase = Environment.GetEnvironmentVariable("DITIO_IDENTITY_BASE")
?? "https://identity.ditio.dev";
var apiBase = Environment.GetEnvironmentVariable("DITIO_API_BASE")
?? "https://core-api.ditio.dev/core";
var reportingBase = Environment.GetEnvironmentVariable("DITIO_REPORTING_BASE")
?? "https://core-api.ditio.dev/reporting";
using var http = new HttpClient();
async Task<string> GetTokenAsync(string scope)
{
var response = await http.PostAsync(
$"{identityBase}/connect/token",
new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "YOUR_CLIENT_ID",
["client_secret"] = "YOUR_CLIENT_SECRET",
["scope"] = scope,
}));
response.EnsureSuccessStatusCode();
return (await response.Content.ReadFromJsonAsync<TokenResponse>())!.access_token;
}
// Step 1: authenticate for the Integration API
var apiToken = await GetTokenAsync("ditioapiv3");
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiToken);
// Step 2: create a project
var projectResponse = await http.PostAsJsonAsync(
$"{apiBase}/api/v4/integration/projects",
new
{
companyId = "YOUR_COMPANY_ID",
projectNumber = "P-1001",
name = "Prosjekt Fjellhall",
active = true,
});
projectResponse.EnsureSuccessStatusCode();
var project = await projectResponse.Content.ReadFromJsonAsync<JsonElement>();
var projectId = project.GetProperty("id").GetString();
// Step 3: create a work order in it
var taskResponse = await http.PostAsJsonAsync(
$"{apiBase}/api/v4/integration/tasks",
new
{
companyId = "YOUR_COMPANY_ID",
projectId,
externalId = "WO-001",
name = "Graving byggegrop",
active = true,
});
taskResponse.EnsureSuccessStatusCode();
// Step 4: the Data Extraction API needs its own token, with scope reportingapiv1
var reportingToken = await GetTokenAsync("reportingapiv1");
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", reportingToken);
// Pull time registrations, following pagination
var url = $"{reportingBase}/v1/time-registrations?FromDateTime=2026-01-01&ToDateTime=2026-01-31";
var recordCount = 0;
string? continuationToken = null;
do
{
var pageUrl = continuationToken is null
? url
: $"{url}&ContinuationToken={Uri.EscapeDataString(continuationToken)}";
var page = await http.GetFromJsonAsync<JsonElement>(pageUrl);
recordCount += page.GetProperty("data").GetArrayLength();
continuationToken = page.TryGetProperty("continuationToken", out var next)
&& next.ValueKind == JsonValueKind.String
? next.GetString()
: null;
} while (!string.IsNullOrEmpty(continuationToken));
Console.WriteLine($"Fetched {recordCount} time registrations");
record TokenResponse(string access_token, int expires_in, string token_type);

For a production integration, use the cached token helper in Token Expiry and the paging helper in Pagination.