Skip to content
TruTecDevelopers
Get an API key

Partner API

For software platforms that resell or embed TruTec: create and manage customer workspaces, bill sessions, mint embed sessions, and test with sandbox dev keys.

View as Markdown

Note

Partner API (for software platforms). These endpoints are for software companies that resell or embed TruTec inside their own product for their own customers. A normal contractor workspace key cannot call them. Every endpoint on this page needs a key with the Partner permission, which TruTec grants to approved partners. Other keys get 403 API key lacks partner management permission.

If you are a contractor building tools for your own business, you do not need this page. Use your workspace key with the rest of the API.

How partner access works#

A partner key belongs to the partner's own TruTec account. It can act on two kinds of customer workspaces:

Kind partner_access_type How it is created What the partner can do
Child workspace child POST /v1/partner/firms Everything: plans, billing, keys, settings, data
Linked workspace linked The customer approves a link to their existing TruTec workspace Read and write data, bill sessions. Plans, keys, billing portal and measurement settings stay with the customer

Choosing the workspace on each call#

When a partner key calls any other endpoint in this API (jobs, measurements, webhooks, exports and so on), it must say which customer workspace to use. Pass one of these, in the query string or JSON body:

Parameter Meaning
firm_id The TruTec workspace id
linked_workspace_token The token returned by the link flow for a linked workspace
partner_user_id Your user id, if that user has been linked to a TruTec workspace

Without one, the call fails with 400 linked_workspace_token, firm_id, or partner_user_id is required when using a partner API key. A workspace you are not connected to returns 404 Firm not found.

Dev keys and sandbox workspaces#

Partners can test without touching live customers or live billing.

  • A sandbox workspace (is_sandbox: true) is a separate test workspace. Its data never mixes with live workspaces. Its name starts with [DEV-<your account id>].
  • A dev key (is_dev: true) is a partner key that can only reach sandbox workspaces.

What a dev key can do:

  • Create workspaces. Every workspace it creates is a sandbox workspace, and that workspace's own key is also a dev key.
  • List, read and manage sandbox workspaces, and call the rest of the API against them (with firm_id).
  • Run billing flows in Stripe test mode: checkout pages accept Stripe test cards, and no real money moves. If test mode is not available, plan changes are applied directly with no checkout.
  • Create more dev keys.

What a dev key cannot do:

  • See or touch a live workspace. Any live firm_id returns 403 Dev key can only access sandbox firms. GET /v1/partner/firms and the embed workspace picker hide live workspaces.
  • Create live workspaces or live keys.
  • Charge real money. Sandbox workspaces never create card charges for sessions or overage.
  • Delete records. Dev keys do not have the Delete records permission.

Be aware:

  • Partner-wide measurement defaults (PUT /v1/partner/measurement-defaults) are shared by your whole account. A dev key that changes them changes the defaults for live workspaces you create later. With apply_to_existing, a dev key only updates sandbox workspaces.
  • A dev key has a usage limit (default 1,000 billable sessions). After that, its calls return 429.
  • Sandbox sessions have a second limit for your whole account: 500 new sessions in any 30 days, across all your sandbox workspaces and keys. After that, new sessions return 429 sandbox_limit_reached until older ones age out. Refunding a sandbox session does not free a slot. Need more for testing? Contact TruTec.
  • GET /v1/partner/health tells you which kind of key you are using.

A live partner key can also reach sandbox workspaces. You can create one with "sandbox": true on POST /v1/partner/firms. Sessions there count toward the same 30-day sandbox limit.

Note

Dev keys and sandbox workspaces exist only for partner accounts. A contractor's own workspace keys (created in Settings → API keys) always act on that live workspace. To test safely, ask TruTec for a separate test workspace and use its keys.

Check your key#

GET /v1/partner/health

Permission: Partner

curl https://trutec.ai/api/v1/partner/health \
  -H "X-API-Key: sk_your_api_key_here"
{"ok": true, "stripe_mode": "test", "key_type": "dev"}

key_type is dev or live. stripe_mode is test for dev keys and live otherwise.

Create a dev key#

POST /v1/partner/dev-key

Permission: Partner

Name Type Required Description
name string No Default Partner Dev Key
usage_limit integer No Max billable sessions. Default 1000
curl -X POST https://trutec.ai/api/v1/partner/dev-key \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name": "Staging"}'
{
  "id": 640,
  "name": "Staging",
  "key_prefix": "sk_Xy7pQ2aB",
  "is_active": true,
  "is_dev": true,
  "usage_count": 0,
  "usage_limit": 1000,
  "usage_remaining": 1000,
  "last_used_at": null,
  "created_at": "2026-10-12T15:00:00",
  "revoked_at": null,
  "key": "sk_Xy7pQ2aB...",
  "mode": "sandbox"
}

key is shown once. Store it securely.

Customer workspaces#

Create a workspace#

POST /v1/partner/firms

Permission: Partner

Creates a child workspace and its first API key.

Name Type Required Description
name string Yes Company name. Must be unique across TruTec
plan string No trial, silver, gold, platinum or diamond. Default gold
email string No Billing email for the Stripe customer
success_url string For paid plans Where Stripe Checkout sends the customer after paying
cancel_url string For paid plans Where Stripe Checkout sends the customer if they cancel
sandbox boolean No true creates a sandbox workspace with no Stripe at all
key_name string No Name for the workspace's API key
usage_limit integer No Usage limit for the workspace's key. Default unlimited (1000 for sandbox)
measurement_settings object No Measurement presets for this workspace. See Measurement presets

How the plan is set up:

  • trial: active at once as trialing. No checkout. Includes a small number of free sessions.
  • Paid plans with a live key: the workspace is pending until the customer pays at checkout_url.
  • Paid plans with a dev key: the same, in Stripe test mode.
  • sandbox: true: active at once, no Stripe.
curl -X POST https://trutec.ai/api/v1/partner/firms \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Northgate Paving",
    "plan": "gold",
    "email": "[email protected]",
    "success_url": "https://app.example.com/trutec/success",
    "cancel_url": "https://app.example.com/trutec/cancel"
  }'
{
  "firm": {
    "id": 5120,
    "name": "Northgate Paving",
    "is_sandbox": false,
    "partner_access_type": "child",
    "partner_managed": true,
    "linked_direct": false,
    "subscription_plan": "gold",
    "subscription_status": "pending",
    "pending_subscription_plan": null,
    "cancel_requested": false,
    "stripe_customer_id": "cus_Q1w2E3r4T5",
    "api_enabled": true,
    "measurements_used_this_period": 0,
    "measurements_limit": 30,
    "measurements_remaining": 30,
    "total_measurements_ever": 0,
    "current_period_end": null,
    "created_at": "2026-10-12T15:02:11.204512"
  },
  "api_key": {
    "id": 641,
    "name": "Northgate Paving API Key",
    "key_prefix": "sk_9fKs2LmQ",
    "is_active": true,
    "is_dev": false,
    "usage_count": 0,
    "usage_limit": null,
    "usage_remaining": null,
    "last_used_at": null,
    "created_at": "2026-10-12T15:02:11.310021",
    "revoked_at": null,
    "key": "sk_9fKs2LmQ..."
  },
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "mode": "live"
}

mode is live, test (dev key, Stripe test mode) or sandbox (no Stripe). The workspace key can read and write jobs and measurements. It cannot delete records or use partner endpoints. It is shown once.

Errors:

  • 400 name is required, Invalid plan. Must be one of: ..., success_url and cancel_url are required, measurement_settings must be an object
  • 409 A company with this name already exists. existing_firm_id is set when that workspace is already yours
  • 500 Failed to create billing session: ...

List workspaces#

GET /v1/partner/firms

Permission: Partner

Your child and linked workspaces, newest first. Dev keys see sandbox workspaces only.

Name Type Required Description
limit integer No Default 50, max 100
offset integer No Default 0
curl "https://trutec.ai/api/v1/partner/firms?limit=50" \
  -H "X-API-Key: sk_your_api_key_here"
{
  "data": [{"id": 5120, "name": "Northgate Paving", "partner_access_type": "child", "subscription_plan": "gold", "subscription_status": "active", "...": "..."}],
  "pagination": {"total": 1, "limit": 50, "offset": 0}
}

Linked workspaces include a linked_workspace_token. Trial workspaces include a trial object: eligible, scope, limit, used, remaining.

Get a workspace#

GET /v1/partner/firms/{firm_id}

Permission: Partner

Returns the workspace object above plus api_key: the workspace's active key (without the secret) for child workspaces, null for linked ones.

Errors: 404 Firm not found, 403 Dev key can only access sandbox firms.

Rotate a workspace key#

POST /v1/partner/firms/{firm_id}/rotate-key

Permission: Partner

Revokes every active key on the child workspace and issues one new key. Not available for linked workspaces.

Name Type Required Description
name string No Name for the new key
usage_limit integer No Usage limit for the new key
is_dev boolean No Only allowed for sandbox workspaces. Dev keys always create dev keys
curl -X POST https://trutec.ai/api/v1/partner/firms/5120/rotate-key \
  -H "X-API-Key: sk_your_api_key_here"

Returns 201 with the key object and key (shown once). Errors: 400 is_dev keys can only be created for sandbox firms, 400 is_dev must be a boolean when provided, 403 for linked workspaces.

Plans and billing#

Plan names are trial, silver, gold, platinum and diamond. These endpoints manage child workspaces only. Linked workspaces return 403: the customer manages their own billing in TruTec.

Change plan#

POST /v1/partner/firms/{firm_id}/change-plan

Permission: Partner

For an active paid workspace.

Name Type Required Description
plan string Yes silver, gold, platinum or diamond
success_url, cancel_url string When a checkout is needed Needed when moving to or from Silver
  • Between monthly plans (Gold, Platinum, Diamond): applied at once. Upgrades are prorated and invoiced now. Downgrades are not prorated. checkout_url is null.
  • To or from Silver: returns a checkout_url. The plan changes after the customer completes checkout. Until then pending_subscription_plan shows the new plan.
curl -X POST https://trutec.ai/api/v1/partner/firms/5120/change-plan \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"plan": "platinum"}'
{"firm": {"id": 5120, "subscription_plan": "platinum", "...": "..."}, "checkout_url": null, "previous_plan": "gold"}

Errors (400): plan is required, Firm is already on the gold plan, Firm subscription is not active. Use /reactivate for lapsed firms., Cannot change plan for a trial firm. Use /reactivate to upgrade from trial., Cannot change plan while cancellation is scheduled. ..., success_url and cancel_url are required for this plan change.

Reactivate or upgrade from trial#

POST /v1/partner/firms/{firm_id}/reactivate

Permission: Partner

Restarts billing for a lapsed workspace, undoes a scheduled cancellation, or moves a trial workspace to a paid plan.

Name Type Required Description
plan string For trial workspaces Paid plan to start. Defaults to the current plan otherwise
success_url, cancel_url string When a checkout is needed Stripe Checkout redirects

If a cancellation is scheduled on a live subscription, it is undone at once (checkout_url: null, reactivated: true). Otherwise the response has a checkout_url for the customer.

Errors: 400 Firm subscription is already active, 400 plan is required when upgrading a trial firm, 400 Invalid plan. ....

Billing portal#

POST /v1/partner/firms/{firm_id}/billing-portal

Permission: Partner

Returns a Stripe billing portal link where the customer can update their card or cancel.

Name Type Required Description
return_url string Yes Where the portal sends the customer back to
{"portal_url": "https://billing.stripe.com/p/session/...", "firm": {"id": 5120, "...": "..."}}

Errors: 400 return_url is required, 400 No Stripe billing account found for this firm.

Sessions: how usage is billed#

Partner usage is billed per session. A session is one takeoff or job in your product, identified by your own session_id.

  • A session is billed once. Sending the same session_id again returns the original charge with deduplicated: true.
  • Detection calls with a partner key need a session_id and bill the session on first use. Inside the embedded editor, the first detection in a session bills it.
  • Child workspaces get a few free sessions first (shown in trial). Trial-plan workspaces then stop with 402 trial_exhausted until they move to a paid plan.
  • Silver workspaces pay per session. Monthly plans include a number of reports per period and bill overage per report after that. See Limits & billing for prices.
  • Linked workspaces are billed under the customer's own TruTec plan.
  • Sandbox workspaces never create card charges. They share a limit of 500 new sessions per 30 days for your account (see Dev keys and sandbox workspaces).

Charge a session#

POST /v1/partner/firms/{firm_id}/charge

Permission: Partner

Bills one session yourself, for example when your user opens a takeoff.

Name Type Required Description
session_id string Yes for trial, Silver and linked workspaces Your idempotency key for this session
partner_user_id string No Your user id, for reporting
partner_customer_id string No Your customer id, for reporting
curl -X POST https://trutec.ai/api/v1/partner/firms/5120/charge \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"session_id": "job-88231", "partner_user_id": "u-417", "partner_customer_id": "c-0091"}'
{
  "success": true,
  "usage": {"count": 12, "limit": null, "remaining": null},
  "measurement_usage": {"used_this_period": 12, "limit": 30, "total_ever": 47, "partner_access_type": "child", "billing_mode": "partner_session"},
  "billing": {
    "session_id": "job-88231",
    "status": "charged",
    "trial_applied": false,
    "partner_user_id": "u-417",
    "partner_customer_id": "c-0091",
    "deduplicated": false,
    "already_refunded": false,
    "charge_source": "legacy_charge_endpoint",
    "partner_access_type": "child",
    "billing_mode": "partner_session",
    "trial": {"eligible": true, "scope": "firm", "limit": 2, "used": 2, "remaining": 0}
  }
}

When a card charge was made, the response also has overage: charged, amount_cents, payment_intent_id.

Billing errors carry a machine-readable reason:

Status reason Meaning
400 session_id_required Send a session_id
402 trial_exhausted Free sessions used up. Upgrade with /reactivate
402 billing_required, missing_payment_method, payment_failed The customer's payment details need attention
402 subscription_inactive Linked workspace billing is not active
403 plan_required, upgrade_required Linked workspace plan does not cover this
429 API key usage limit exceeded
429 sandbox_limit_reached Your sandbox workspaces used their 500 sessions for the last 30 days. sandbox_usage has used, limit and window_days

Refund a session#

POST /v1/partner/firms/{firm_id}/refund-charge

Permission: Partner

Reverses a session charge, for example when a takeoff failed. Any card payment for that session is refunded, and usage counters go back down. Refunding the same session twice is safe: the second call returns already_refunded: true.

Name Type Required Description
session_id string Yes for linked workspaces The session to refund
feedback string No Why it was refunded, up to 2,000 characters. Shared with the TruTec team
curl -X POST https://trutec.ai/api/v1/partner/firms/5120/refund-charge \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"session_id": "job-88231", "feedback": "Imagery was out of date for this site."}'

Returns the same shape as a charge, with billing.status: "refunded" and feedback_received: true when feedback was saved.

Errors: 404 Billing session not found, 409 Billing session is not charged, 402 Payment refund failed: ..., 400 No charged usage to refund.

Linking an existing TruTec workspace#

Use this when your customer already has a TruTec workspace. The customer signs in to TruTec and approves the link. The first link for a workspace must be approved by a workspace owner.

POST /v1/partner/link/start

Permission: Partner

Name Type Required Description
partner_user_id string Yes Your id for the user who is linking
return_url string Yes http(s) URL to send the user back to
partner_customer_id string No Your customer id
email_hint string No Shown to the user on the TruTec page
state string No Echoed back to return_url
{"link_url": "https://trutec.ai/...", "expires_in": 3600}

Send the user to link_url (valid for one hour). After approval TruTec redirects to return_url?status=success&code=...&state=....

Exchange the code#

POST /v1/partner/link/exchange

Permission: Partner

Send {"code": "..."} within 10 minutes.

{
  "linked": true,
  "firm": {"id": 4471, "name": "Lakeside Asphalt", "partner_access_type": "linked", "linked_direct": true, "...": "..."},
  "linked_workspace_token": "eyJwYXJ0bmVy...",
  "user": {"id": 9021, "email": "[email protected]", "username": "lakeside-owner"},
  "partner_user_id": "u-417",
  "partner_customer_id": "c-0091"
}

Store firm.id or linked_workspace_token and pass it on later calls. Errors: 400 Invalid or expired code, 404 Linked workspace not found.

Embedding the takeoff editor#

Your backend mints a short-lived URL. Your front end loads it in an iframe. Never send your API key to the browser.

Mint an embed session#

POST /v1/partner/embed-token or POST /v1/partner/firms/{firm_id}/embed-token

Permission: Partner

Creates (or resumes) a job in the customer's workspace and returns a signed URL valid for 24 hours. Minting does not bill anything.

Name Type Required Description
session_id string Yes Your stable id for this takeoff. The same id resumes the same job. external_id is an alias
firm_id integer No The workspace. If left out, TruTec works it out (see below)
partner_customer_id string No Your customer id. Remembered so later sessions find the workspace
partner_user_id string No Your user id. The job is owned by the linked TruTec user when known
name string No Job name. Default Parking Measurement
address string No Site address

Without firm_id, the workspace is chosen from: a workspace remembered for this partner_customer_id, then the workspace linked to partner_user_id, then your only connected workspace.

curl -X POST https://trutec.ai/api/v1/partner/embed-token \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"session_id": "job-88231", "partner_customer_id": "c-0091", "partner_user_id": "u-417", "name": "Northgate Plaza", "address": "1200 Commerce Dr, Austin, TX"}'
{
  "embed_url": "https://trutec.ai/measurements/embed/parking?token=...",
  "expires_in": 86400,
  "job_id": 4812,
  "project_id": 3307,
  "firm_id": 5120,
  "workspace_name": "Northgate Paving",
  "session_id": "job-88231",
  "external_id": "job-88231"
}

Mint a new URL each time the user opens the editor. Do not store embed URLs.

Errors:

  • 400 reason: session_id_required
  • 409 reason: workspace_ambiguous: nothing was written. The response has candidates (firm_id, name) and a workspace_picker_url. Pass firm_id, or load workspace_picker_url in the iframe so the customer picks once.
  • 404 reason: no_connected_workspace
  • 403 Dev key can only access sandbox firms

Workspace picker#

POST /v1/partner/embed-token/choose-workspace

Permission: None. The picker token in the body is the credential.

The page at workspace_picker_url calls this for you. You normally do not call it directly. Body: token, firm_id. The token works once: a second use returns 410 picker_token_used. A customer already mapped to another workspace returns 409 workspace_already_mapped; mint with an explicit firm_id to move them. On success it returns the same object as minting an embed session.

Messages from the iframe#

The editor talks to your page with postMessage. Check event.origin === "https://trutec.ai".

Message from TruTec When
trutec:autosave The editor saved
trutec:elements Reply to trutec:request-elements
trutec:measurement Reply to trutec:request-save
trutec:save-exit Reply to trutec:request-save-exit
trutec:elements-error A request failed (error, request_id)
Message you send Effect
trutec:request-elements Read the current measurements without saving
trutec:request-save Save, then reply with trutec:measurement
trutec:request-save-exit Save, then reply with trutec:save-exit

Include a request_id and it is echoed back. Reply messages carry data with project_id, measurement_id and the measurement summary. Read the saved result server-side with GET /v1/measurements/{id}?firm_id= (Measurements).

Measurement presets#

Presets set how the takeoff editor labels and shows items in your customers' workspaces. Keys: counter_labels (object of category to label), counter_display and view_display (object of category to display mode), custom_item_defs (list), enabled_categories (list of category keys), counter_unit_rules (object of category to {basis, divisor, label}), stall_centerline_enabled (boolean). Send null or empty to clear a key. Invalid categories or modes return 400 with the valid values.

Get partner defaults#

GET /v1/partner/measurement-defaults

Permission: Partner

Returns {"measurement_defaults": {...}}: the presets copied into every workspace you create.

Set partner defaults#

PUT /v1/partner/measurement-defaults

Permission: Partner

Send one or more preset keys. Add "apply_to_existing": true to also update your existing child workspaces now (sandbox ones only, for a dev key).

curl -X PUT https://trutec.ai/api/v1/partner/measurement-defaults \
  -H "X-API-Key: sk_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"counter_labels": {"handicap": "ADA Spaces"}, "apply_to_existing": true}'
{"measurement_defaults": {"counter_labels": {"handicap": "ADA Spaces"}, "...": "..."}, "applied_to_existing_firm_ids": [5120, 5121]}

400 Provide at least one of: ... when no preset key is sent.

Get or update one workspace's presets#

GET /v1/partner/firms/{firm_id}/measurement-settings PATCH /v1/partner/firms/{firm_id}/measurement-settings

Permission: Partner

GET returns {"firm_id": 5120, "measurement_settings": {...}}. PATCH takes the same preset keys and returns the updated presets. PATCH is not available for linked workspaces (403).