REST quickstart
From credential to first takeoff in ten minutes.
Everything below talks to https://api.aginera.ai/partner/v1 with an Authorization: Bearer … token. Pick one of the two token flows in step 1 — both call the same services, permissions and billing.
1. Get a token
Option A — Delegated OAuth (self-serve, no partner setup)
OAuth 2.1 authorization code + PKCE against https://auth.aginera.ai. A user approves once, you get a refreshable token scoped to their organization. No client secret is issued — register a public client and use PKCE.
AS=https://auth.aginera.ai
# Register a public client once
curl -s -X POST $AS/oauth/register -H "Content-Type: application/json" -d '{
"client_name":"My integration",
"redirect_uris":["http://localhost:8765/callback"],
"grant_types":["authorization_code","refresh_token"],
"response_types":["code"],
"token_endpoint_auth_method":"none"
}'
# → 201 { "client_id": "oc_…", … }
# PKCE pair, then send the user to authorize
VERIFIER=$(openssl rand -base64 96 | tr -d '\n=+/' | cut -c1-64)
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=\n')
open "$AS/oauth/authorize?response_type=code&client_id=$CLIENT_ID\
&redirect_uri=http://localhost:8765/callback&code_challenge=$CHALLENGE&code_challenge_method=S256\
&scope=projects:write+documents:write+takeoffs:read+takeoffs:run+exports:create+billing:read&state=$(uuidgen)"
# Exchange the returned ?code for a token
curl -s -X POST $AS/oauth/token \
--data-urlencode grant_type=authorization_code --data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=http://localhost:8765/callback" \
--data-urlencode "client_id=$CLIENT_ID" --data-urlencode "code_verifier=$VERIFIER"
# → { "access_token":"…", "refresh_token":"…", "expires_in":3600, "token_type":"Bearer" }
Refresh with grant_type=refresh_token before expires_in. Scopes and discovery endpoints are in Authentication.
Option B — Service credentials (partner backends)
OAuth 2.0 client credentials, issued by Microsoft Entra ID. Use this for unattended backends, CI and batch jobs.
curl -s -X POST "https://login.microsoftonline.com/$AGINERA_TENANT_ID/oauth2/v2.0/token" \
-d grant_type=client_credentials -d client_id="$CLIENT_ID" -d client_secret="$CLIENT_SECRET" \
-d scope="api://$AGINERA_API_APP_ID/.default"
# → { "access_token":"…", "expires_in":3600, "token_type":"Bearer" }
$AGINERA_TENANT_ID and $AGINERA_API_APP_ID are fixed for Aginera; $CLIENT_ID / $CLIENT_SECRET are provisioned for your organization during partner onboarding — email developers@aginera.ai. Cache the token until five minutes before expires_in.
Confirm the token and see exactly what it can do:
API=https://api.aginera.ai/partner/v1
curl -s $API/clients/me -H "Authorization: Bearer $TOKEN"
# → { "mode":"…", "scopes":[…], "billing_mode":"artifact", "rate_limits":{…}, "quotas":{…} }
2. Create a project and add a drawing
curl -s -X POST $API/projects -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" -d '{"name":"Bavarian Lofts","external_ref":"BL-2026"}'
# → { "id": "prj_…", … }
Add a drawing one of two ways — give Aginera a URL to pull, or upload the bytes directly.
# By URL
curl -s -X POST $API/documents -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"project_id":"prj_…","filename":"E-series.pdf","source":{"type":"url","url":"https://files.example.com/E-series.pdf"}}'
# → 202 { "id":"doc_…","status":"ingesting" }
# By upload — request a slot, PUT the bytes with the returned headers, then complete
curl -s -X POST $API/documents -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"project_id":"prj_…","filename":"E-series.pdf","source":{"type":"upload","size_bytes":8123400,"content_type":"application/pdf"}}'
# → 201 { "document": { "id":"doc_…","status":"awaiting_upload", … },
# "upload": { "url":"https://…","method":"PUT","headers":{…},"expires_at":"…" } }
# PUT the bytes to upload.url using upload.method, sending every header from upload.headers
curl -s -X PUT "$UPLOAD_URL" -H "x-ms-blob-type: BlockBlob" --data-binary @E-series.pdf
curl -s -X POST $API/documents/doc_…/complete -H "Authorization: Bearer $TOKEN"
Poll GET /documents/{id} until status is ready (you get page_count, sheet numbers and whether each page has a text layer).
3. Run a takeoff
curl -s -X POST $API/takeoffs -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"document_id":"doc_…","discipline":"electrical"}'
# → 202 { "id":"tko_…","status":"queued","estimated_credits":80, … }
estimated_credits is the maximum that will be charged; the final charge never exceeds it. Poll GET /takeoffs/{id} until status is completed, or register a webhook. Restrict to specific sheets with "pages":[3,4,5].
New accounts start with trial credits, so your first takeoffs run without buying anything. Check GET /credits for the balance and rate card; billable operations (takeoffs:run, estimates:generate) draw down credits exactly as they do in the web app, and you top up when the balance runs low.
4. Read the results
curl -s "$API/takeoffs/tko_…/items?consolidated=true&units=imperial" -H "Authorization: Bearer $TOKEN"
curl -s "$API/takeoffs/tko_…/routes?system=conduit" -H "Authorization: Bearer $TOKEN"
curl -s "$API/takeoffs/tko_…/schedules" -H "Authorization: Bearer $TOKEN"
curl -s -X POST $API/exports -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"takeoff_id":"tko_…","format":"xlsx"}'
# → 202 { "id":"exp_…","status":"…","download":{…},"credits_charged":… }
Every item carries extracted_quantity (immutable), quantity_override (yours) and effective_quantity, plus geometry and evidence in page-normalized coordinates (origin top-left, page as displayed).