Back to Blog
Engineering6 min read

Integrating Aginera into Your App, Claude, or Copilot

Aginera exposes the same takeoff engine two ways: a REST API for your backend and a remote MCP server for agents like Claude, ChatGPT and Copilot. Here's how to authenticate, upload a drawing, run a takeoff, and pull structured quantities — with copy-paste code for each path.

Kiran Karunakaran
September 3, 2026
Integrating Aginera into Your App, Claude, or Copilot

Aginera turns construction drawings into structured takeoffs — quantities, measured routes and schedules — and it exposes that engine two ways:

  • REST API (https://api.aginera.ai/partner/v1) for your own backend, CI, or batch jobs.
  • Remote MCP server (https://mcp.aginera.ai/mcp) for agents — Claude, ChatGPT, GitHub Copilot, or anything that speaks MCP.

Both hit the same services with the same permissions and the same credit billing. Pick MCP when a human or an agent is in the loop; pick REST when your code is driving.

Aginera integration architecture: your app, Claude, ChatGPT and Copilot authenticate once at auth.aginera.ai, then reach the MCP and REST surfaces, which return takeoffs, routes, schedules and exports.

Authentication in one paragraph

Every call carries Authorization: Bearer <token>. There are two ways to get that token:

  • Delegated (self-serve, recommended to start) — OAuth 2.1 authorization code + PKCE against https://auth.aginera.ai. Register a public client, a user approves once, refresh tokens keep it alive. No client secret.
  • Service — OAuth 2.0 client credentials issued by Microsoft Entra ID, for fully unattended backends. Provisioned during partner onboarding.

When a user approves your integration they see exactly what they're granting, and billable scopes are called out:

The Aginera OAuth consent screen: an application requests scopes such as View projects, Upload drawings, Run takeoffs (marked "Uses credits"), View takeoffs and Create exports, with Deny and Allow buttons.

Part 1 — Into your own app (REST)

Here is the whole path — token, project, upload, takeoff, results — as plain curl. This is the delegated flow; swap step 1 for client-credentials if you're on service mode.

1. Register a client and get a token (once)

AS=https://auth.aginera.ai

curl -s -X POST $AS/oauth/register -H "Content-Type: application/json" -d '{
  "client_name":"Acme Estimating",
  "redirect_uris":["http://localhost:8765/callback"],
  "grant_types":["authorization_code","refresh_token"],
  "response_types":["code"],
  "token_endpoint_auth_method":"none"
}'                                        # → { "client_id": "oc_…" }

# PKCE, send the user to $AS/oauth/authorize?…&code_challenge=…&scope=…,
# capture ?code= at your redirect_uri, then:
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 }

The redirect_uri is your app's callback. A hosted backend registers its public HTTPS URL (https://app.acme.com/oauth/callback); a CLI or desktop tool uses a localhost loopback on the user's machine. Aginera only ever redirects to a URI you pre-registered.

2. Create a project, upload a drawing

API=https://api.aginera.ai/partner/v1
AUTH="Authorization: Bearer $TOKEN"

PRJ=$(curl -s -X POST $API/projects -H "$AUTH" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" -d '{"name":"Riverside Offices"}' | jq -r .id)

# Request an upload slot, PUT the bytes with the returned headers, then complete
DOC=$(curl -s -X POST $API/documents -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"project_id\":\"$PRJ\",\"filename\":\"E-series.pdf\",\"source\":{\"type\":\"upload\",\"size_bytes\":$(stat -f%z E-series.pdf),\"content_type\":\"application/pdf\"}}")
DOC_ID=$(echo "$DOC" | jq -r .document.id)
curl -s -X PUT "$(echo "$DOC" | jq -r .upload.url)" \
  -H "x-ms-blob-type: BlockBlob" -H "Content-Type: application/pdf" --data-binary @E-series.pdf
curl -s -X POST $API/documents/$DOC_ID/complete -H "$AUTH"
# poll GET /documents/{id} until status == "ready"

3. Run a takeoff, read structured results

TKO=$(curl -s -X POST $API/takeoffs -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"document_id\":\"$DOC_ID\",\"discipline\":\"electrical\"}" | jq -r .id)
# poll GET /takeoffs/{id} until status == "completed"

curl -s "$API/takeoffs/$TKO/items?consolidated=true&units=imperial" -H "$AUTH"
curl -s "$API/takeoffs/$TKO/routes?system=conduit" -H "$AUTH"
curl -s "$API/takeoffs/$TKO/schedules" -H "$AUTH"
curl -s -X POST $API/exports -H "$AUTH" -H "Content-Type: application/json" \
  -d "{\"takeoff_id\":\"$TKO\",\"format\":\"xlsx\"}"

Every item carries extracted_quantity (immutable), quantity_override (yours) and effective_quantity, plus geometry and evidence in page-normalized coordinates — so you can draw the exact box the number came from.

A production client wants token refresh and polling built in:

import time, requests
AS, API = "https://auth.aginera.ai", "https://api.aginera.ai/partner/v1"

class Aginera:
    def __init__(self, token, refresh, client_id):
        self.tok, self.refresh, self.cid = token, refresh, client_id
    def _h(self): return {"Authorization": f"Bearer {self.tok}"}
    def _renew(self):
        r = requests.post(f"{AS}/oauth/token", data={
            "grant_type": "refresh_token", "refresh_token": self.refresh,
            "client_id": self.cid}).json()
        self.tok, self.refresh = r["access_token"], r.get("refresh_token", self.refresh)
    def get(self, path, **kw):
        r = requests.get(f"{API}{path}", headers=self._h(), **kw)
        if r.status_code == 401: self._renew(); r = requests.get(f"{API}{path}", headers=self._h(), **kw)
        r.raise_for_status(); return r.json()
    def wait(self, path, done=("completed", "ready"), every=30):
        while (s := self.get(path)["status"]) not in done:
            if s == "failed": raise RuntimeError(f"{path} failed")
            time.sleep(every)
        return s

Part 2 — Into Claude

Claude connects to Aginera as a custom connector over remote MCP (Streamable HTTP + OAuth). Nothing to host — Claude talks straight to mcp.aginera.ai.

Claude Desktop / claude.ai

  1. Settings → Connectors → Add custom connector.
  2. Name it Aginera, URL https://mcp.aginera.ai/mcp.
  3. Click Connect — Claude opens the Aginera sign-in, you pick your organization and Allow the scopes.
  4. Ask, in plain language:

"Take off the electrical scope from the latest drawing set for the Riverside Offices project and summarize conduit by panel."

Claude Code (CLI) — one line:

claude mcp add --transport http aginera https://mcp.aginera.ai/mcp

Claude discovers the tools (search_projects, run_takeoff, get_takeoff_items, …) and asks before any billable action, because the run_takeoff scope is flagged Uses credits on the consent screen above.

Part 3 — Into ChatGPT

ChatGPT adds Aginera as a custom MCP app:

  1. Settings → Plugins → New (the in the plugins panel).
  2. Name Aginera; Description "Turn construction drawings into takeoffs, measured routes and schedules."
  3. ConnectionServer URLhttps://mcp.aginera.ai/mcp.
  4. AuthenticationOAuth (settings are discovered automatically).
  5. Tick I understand and want to continue, click Create, then sign in and Allow.

The Aginera app then lists its Actions (add_document, run_takeoff, get_takeoff_items, …) with their input schemas, ready to call from a conversation.

Part 4 — Into GitHub Copilot / VS Code

VS Code speaks MCP too. Drop a server into .vscode/mcp.json:

{
  "servers": {
    "aginera": { "type": "http", "url": "https://mcp.aginera.ai/mcp" }
  }
}

Reload the window and open Copilot Chat in agent mode — it prompts the OAuth sign-in on first use, then Aginera's tools are available alongside your repo tools. Handy for wiring takeoff data straight into an estimating script you're already editing.

Billing, in one look

Aginera bills by artifact. New accounts start with trial credits, so your first takeoffs run without buying anything. Read-only calls (get_capabilities, get_takeoff_items, search_projects) are free; billable operations draw down credits against a published rate card:

curl -s $API/credits -H "$AUTH"
# → { "available": …, "rate_card": [
#      { "operation":"takeoff.page", "credits":2.5 },
#      { "operation":"takeoff.text_page", "credits":0.5 }, … ] }

POST /takeoffs returns an estimated_credits ceiling before any work starts, and the final charge never exceeds it — so you can show a cost, or gate on budget, before committing.

A few things that will save you time

  • Poll, or use a webhook. Ingestion and takeoffs are asynchronous. Poll GET /documents/{id} and GET /takeoffs/{id}, or register a webhook and skip the loop.
  • Idempotency-Key on writes. Retries are safe when you send one.
  • Errors are typed. Every error is RFC 9457 problem+json with a stable code, a next_step, and a request_id — quote it to support.
  • Ignore unknown fields. The API is additively versioned in the path (/partner/v1); new fields are not breaking.

Start here

Whether it's your own service, Claude, ChatGPT or Copilot on the other end, it's the same drawing-to-takeoff engine — you just choose the door.

Aginera APIconstruction takeoff APIMCP serverClaude integrationChatGPT integrationGitHub CopilotOAuth 2.1 PKCEdeveloper platform
Share this article

Ready to transform your workflow?

See how DesignOps can help your team work smarter, not harder.