Getting started: REST service¶
In ten minutes you run the REST service over a CSV file on your machine, call it with curl or PowerShell, check a view, render it into an Excel file and download it. You need Node 22 or later; for the container image, see Container image.
1. Install and start it¶
Make a folder with the example data of the command line and an output folder:
mkdir pivotkit-rest && cd pivotkit-rest
npx -y pivotkit-cli example
mkdir reports
Start the service with one source, sales, on 127.0.0.1:3333 (no sign-in on loopback):
npx -y pivotkit-server serve --source sales=sales.csv --output reports
The log says started with the address. Leave it running and open a second terminal in the same
folder.
2. Look at the data¶
curl -s http://127.0.0.1:3333/api/v1/sources
curl -s http://127.0.0.1:3333/api/v1/sources/sales
$api = "http://127.0.0.1:3333/api/v1"
Invoke-RestMethod "$api/sources"
(Invoke-RestMethod "$api/sources/sales").fields | Format-Table name, type, level, distinct
Each field has a level. By default every field is summary: you get its name, type and counts, not
its values. The operator decides more per field (step 5).
3. Check a view¶
A view is the report's layout as JSON (View file format). Send one with a misspelt field:
curl -s -H "Content-Type: application/json" \
-d '{"source":"sales","view":{"rows":["Regoin"],"values":[{"field":"Revenue","agg":"sum"}]}}' \
http://127.0.0.1:3333/api/v1/views/validate
$body = @{ source = "sales"; view = @{ rows = @("Regoin"); values = @(@{ field = "Revenue"; agg = "sum" }) } } | ConvertTo-Json -Depth 10
(Invoke-RestMethod "$api/views/validate" -Method Post -ContentType "application/json" -Body $body).findings
The answer says "valid": false and a finding with "suggestion": "Region": the name meant.
4. Render it¶
With the name fixed, render the report into the output folder:
curl -s -H "Content-Type: application/json" \
-d '{"source":"sales","view":{"rows":["Region"],"values":[{"field":"Revenue","agg":"sum"}]},"format":"xlsx"}' \
http://127.0.0.1:3333/api/v1/views/render
ls reports
$body = @{ source = "sales"; view = @{ rows = @("Region"); values = @(@{ field = "Revenue"; agg = "sum" }) }; format = "xlsx" } | ConvertTo-Json -Depth 10
(Invoke-RestMethod "$api/views/render" -Method Post -ContentType "application/json" -Body $body).message
Get-ChildItem reports
The answer's message says where the file is. Check ok in a script: a view with errors also
answers 200, with ok: false, its findings and no file. The answer also says withheld: Region
and Revenue are summary fields, so the numbers stay in the file and are not part of the answer.
5. Tokens, a profile and downloads¶
Stop the service (Ctrl+C) and write a configuration file pivotkit.json: Region is shown, two
tokens, one of them with a profile that may download files.
{
"sources": { "sales": { "file": "sales.csv", "title": "Sales", "policy": { "fields": { "Region": "show" } } } },
"output": { "dir": "reports" },
"policy": { "profiles": { "export": { "results": true } } },
"auth": { "mode": "token", "tokens": [
{ "name": "scripts", "env": "SCRIPTS_TOKEN" },
{ "name": "export", "env": "EXPORT_TOKEN", "profile": "export" } ] }
}
Make two tokens and start it again:
export SCRIPTS_TOKEN="$(node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))")"
export EXPORT_TOKEN="$(node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))")"
npx -y pivotkit-server check-config --config pivotkit.json
npx -y pivotkit-server serve --config pivotkit.json
$env:SCRIPTS_TOKEN = node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
$env:EXPORT_TOKEN = node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
npx -y pivotkit-server check-config --config pivotkit.json
npx -y pivotkit-server serve --config pivotkit.json
check-config prints what the service exposes. In the second terminal (with the same two variables
set), a call without a token is now 401, and only the export token may download the file:
curl -s -w ' %{http_code}\n' http://127.0.0.1:3333/api/v1/sources # 401
VIEW='{"source":"sales","view":{"rows":["Region"],"values":[{"field":"Revenue","agg":"sum"}]},"format":"xlsx"}'
curl -s -w ' %{http_code}\n' -H "Authorization: Bearer $SCRIPTS_TOKEN" -H "Content-Type: application/json" -d "$VIEW" \
http://127.0.0.1:3333/api/v1/views/render/file # 403 NOT_ALLOWED
curl -s -o revenue.xlsx -H "Authorization: Bearer $EXPORT_TOKEN" -H "Content-Type: application/json" -d "$VIEW" \
http://127.0.0.1:3333/api/v1/views/render/file
$view = @{ source = "sales"; view = @{ rows = @("Region"); values = @(@{ field = "Revenue"; agg = "sum" }) }; format = "xlsx" } | ConvertTo-Json -Depth 10
$headers = @{ Authorization = "Bearer $env:EXPORT_TOKEN" }
Invoke-RestMethod "$api/views/render/file" -Method Post -ContentType "application/json" -Body $view -Headers $headers -OutFile revenue.xlsx
An error is a problem document with a stable code and a detail that says what to do; with
PowerShell 7, Invoke-RestMethod throws and the document is in $_.ErrorDetails.Message.
From PowerShell Universal¶
In a PSU script or endpoint, keep the token in a secret (here $Secret:PivotkitToken) and pass the
view a user built:
$api = "https://reports.example.com/api/v1"
$headers = @{ Authorization = "Bearer $Secret:PivotkitToken" }
$body = @{ source = "sales"; view = $View; format = "xlsx"; name = "Revenue by region" } | ConvertTo-Json -Depth 20
try {
Invoke-RestMethod "$api/views/render/file" -Method Post -ContentType "application/json" -Body $body -Headers $headers -OutFile $Path
} catch {
$problem = $_.ErrorDetails.Message | ConvertFrom-Json
Write-Error "$($problem.code): $($problem.detail)"
}
Next¶
- pivotkit-server (REST): every route, profiles, sign-in with OAuth, CORS, limits, errors.
- The OpenAPI document of your service:
GET /openapi.jsonwith a token. - Container image: the same service in Docker or Kubernetes.
- Getting started: MCP server: the same sources for an AI client.