REST quickstart

From credential to first takeoff in ten minutes.

Everything below talks to https://api.aginera.ai/partner/v1 with an Authorization: Bearer … credential. Pick the flow that matches how you're calling — all call the same services, permissions and billing.

1. Get a credential

Option A — API key (simplest, for your own backend)

If you're a signed-in Aginera org admin, create a key in Settings → API (name it, pick scopes, copy it — it's shown once) and use it directly. Nothing to exchange, nothing to refresh:

curl https://api.aginera.ai/partner/v1/projects -H "Authorization: Bearer agk_..."

Prefer this when calling the API from your own backend for your own organization. Use the OAuth flows below when you're building an agent or acting on behalf of other users.

Option B — Delegated OAuth (self-serve, for agents / other users)

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 C — Service credentials (Entra, enterprise)

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