Authenticate, create a project, track its reports, pull results, and embed the authenticated status viewer — with copy-paste cURL and TypeScript examples.
This is the single reference for building against the AI PreCheck API. It covers authentication, creating a project, tracking its reports to completion, pulling results, and embedding the authenticated status viewer in your own UI. The three integration patterns (Embedded, Backend-only, Standalone) all compose from the same calls documented here.
Authentication & API keys
Section titled “Authentication & API keys”Every request authenticates with an API key sent in the x-api-key header.
As a vendor you create keys per city: a key is scoped to a single city you have access to, and carries that city’s permissions. Requests also take a cityKey parameter identifying the city the call operates on. Keep keys server-side — they are never exposed to the browser or embedded in client code.
Base URLs
Section titled “Base URLs”The API is region- and environment-specific. The host follows the pattern https://echeck-api.<env>.<region>.archistar.<tld>:
| Environment | Region | Base URL |
|---|---|---|
| Production | Canada | https://echeck-api.prod.ca.archistar.ai |
| Development | Australia | https://echeck-api.dev.au.archistar.io |
Your Archistar representative confirms the correct base URL for your city. The examples below use these placeholders:
BASE_URL="https://echeck-api.prod.ca.archistar.ai"API_KEY="sk_live_..." # per-city key, kept server-sideCITY_KEY="fairview" # the city this call operates onA minimal authenticated request:
curl "$BASE_URL/cities/$CITY_KEY" \ -H "x-api-key: $API_KEY" \ -H "Accept: application/json"const BASE_URL = process.env.PRECHECK_BASE_URL!;const API_KEY = process.env.PRECHECK_API_KEY!; // server-side onlyconst CITY_KEY = "fairview";
async function api(path: string, init: RequestInit = {}) { const res = await fetch(`${BASE_URL}${path}`, { ...init, headers: { "x-api-key": API_KEY, Accept: "application/json", ...(init.body ? { "Content-Type": "application/json" } : {}), ...init.headers, }, }); if (!res.ok) throw new Error(`PreCheck ${res.status}: ${await res.text()}`); return res.json();}Discovery: which cities and permit types?
Section titled “Discovery: which cities and permit types?”curl "$BASE_URL/cities" -H "x-api-key: $API_KEY" # cities your key can accesscurl "$BASE_URL/cities/$CITY_KEY" -H "x-api-key: $API_KEY" # a city's permit types + reportsThe city config tells you the valid permitType values and which reports each one runs. You pass a single permitType when creating or processing a project — you don’t choose individual reports.
Create a project
Section titled “Create a project”There are three ways to create a project, depending on where the applicant experiences PreCheck.
Option A — file links (one call)
Section titled “Option A — file links (one call)”Use this when the drawings are already at a URL Archistar can fetch (an S3 link, a signed download URL from your DMS, etc.). Processing starts immediately.
curl -X POST "$BASE_URL/projects" \ -H "x-api-key: $API_KEY" -H "Content-Type: application/json" \ -d '{ "permitType": "single-family", "cityKey": "fairview", "address": "25 Maple Street, Fairview, 90210", "longitude": -118.32, "latitude": 34.09, "userEmail": "applicant@example.com", "userName": "Jordan Rivera", "files": [ { "type": "Architectural Drawings", "label": "drawings.pdf", "fileLink": "https://files.example.gov/25-maple/drawings.pdf" } ], "metadata": { "permitId": "PR-2026-000123" } }'const project = await api("/projects", { method: "POST", body: JSON.stringify({ permitType: "single-family", cityKey: CITY_KEY, address: "25 Maple Street, Fairview, 90210", longitude: -118.32, latitude: 34.09, userEmail: "applicant@example.com", userName: "Jordan Rivera", files: [ { type: "Architectural Drawings", label: "drawings.pdf", fileLink: "https://files.example.gov/25-maple/drawings.pdf" }, ], metadata: { permitId: "PR-2026-000123" }, // echoed back on the webhook }),});const projectId = project.id; // e.g. "P_7A3F21C9"The metadata object is echoed back to you unchanged (including on the completion webhook) — use it to correlate the project with your own permit record.
Option B — direct upload (four calls)
Section titled “Option B — direct upload (four calls)”Use this when the applicant uploads files to you and you don’t have a public URL. Create a draft project, get a pre-signed upload URL, PUT the bytes, then start processing.
-
Create a draft project (no permit type or files yet). Returns the
projectId.POST /projects curl -X POST "$BASE_URL/projects" \-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \-d '{"cityKey": "fairview","address": "25 Maple Street, Fairview, 90210","longitude": -118.32, "latitude": 34.09,"userEmail": "applicant@example.com","userName": "Jordan Rivera"}' -
Ask for a pre-signed upload URL for each file.
GET /projects/:projectId/upload-file-url curl "$BASE_URL/projects/$PROJECT_ID/upload-file-url?filename=drawings.pdf" \-H "x-api-key: $API_KEY" -
PUTthe file bytes straight to the returned URL (storage, not the API — nox-api-key).PUT <upload url> curl -X PUT --upload-file ./drawings.pdf "$UPLOAD_URL" -
Start processing, naming the permit type and the uploaded files.
POST /projects/:projectId/process curl -X POST "$BASE_URL/projects/$PROJECT_ID/process" \-H "x-api-key: $API_KEY" -H "Content-Type: application/json" \-d '{"permitType": "single-family","files": [{ "type": "Architectural Drawings", "label": "Drawings", "filename": "drawings.pdf" }]}'
// 1. create draftconst { id: projectId } = await api("/projects", { method: "POST", body: JSON.stringify({ cityKey: CITY_KEY, address: "25 Maple Street, Fairview, 90210", longitude: -118.32, latitude: 34.09, userEmail: "applicant@example.com", userName: "Jordan Rivera", }),});
// 2. + 3. upload each fileasync function upload(file: { name: string; blob: Blob }) { const { url } = await api( `/projects/${projectId}/upload-file-url?filename=${encodeURIComponent(file.name)}`, ); await fetch(url, { method: "PUT", body: file.blob }); // straight to storage}await upload(primary);
// 4. processawait api(`/projects/${projectId}/process`, { method: "POST", body: JSON.stringify({ permitType: "single-family", files: [{ type: "Architectural Drawings", label: "Drawings", filename: "drawings.pdf" }], }),});Option C — complete in Archistar (hand-off)
Section titled “Option C — complete in Archistar (hand-off)”Use this for embedded intake: create a draft project, then send the applicant to a completion link where they finish the question set and uploads inside Archistar’s UI.
# 1. create a draft project (as in Option B step 1)# 2. get a link to hand the applicant off tocurl "$BASE_URL/projects/$PROJECT_ID/complete-link" -H "x-api-key: $API_KEY"Redirect the applicant to the returned URL (or embed it). When they finish, processing starts and you receive the webhook like any other project.
Track status
Section titled “Track status”A project’s reports each move through five processing stages — Preparing → Analysing → Categorising → Assessing → Summarising — then land on a terminal status of Complete with a result (Passed / Failed) and a score.
curl "$BASE_URL/projects/$PROJECT_ID" -H "x-api-key: $API_KEY" # project + overall statuscurl "$BASE_URL/projects/$PROJECT_ID/reports" -H "x-api-key: $API_KEY" # per-report status & scores[ { "id": "R_BPIC-21", "name": "Fairview – Building Permit Intake Completeness", "status": "Complete", "result": "Passed", "score": 85, "modelVersion": "0.15" }, { "id": "R_SF3-84", "name": "Fairview – SF-3 Single Family Zoning Compliance", "status": "Complete", "result": "Passed", "score": 82, "modelVersion": "0.8" }]You can either poll these endpoints, or — preferred for production — register a webhook and be pushed each result.
Completion webhook
Section titled “Completion webhook”When a report finishes, Archistar POSTs to your configured endpoint with type=project_report_completed. It fires once per report, and carries your original metadata so you can look up the matching permit record without a polling loop.
{ "projectId": "P_7A3F21C9", "reportId": "R_BPIC-21", "status": "Complete", "result": "Passed", "score": 85, "metadata": { "permitId": "PR-2026-000123" }, "documents": [ { "documentName": "AI PreCheck Report.pdf", "documentType": "report", "documentURL": "https://echeck-api.prod.ca.archistar.ai/Documents/R_BPIC-21_report" } ]}Get results & documents
Section titled “Get results & documents”Once a report’s status is Complete:
| You want… | Call |
|---|---|
| A project’s reports + scores | GET /projects/:projectId/reports |
| One report’s detail | GET /projects/:projectId/reports/:reportId |
| A report’s uploaded files or PDF | GET /projects/:projectId/reports/:reportId/document?type=uploaded · ?type=report |
| All of a project’s documents | GET /projects/:projectId/document?type=uploaded · ?type=report |
| Filter / paginate projects | GET /projects?cityKey=…&permitType=…&take=20&skip=0&orderBy=last_activity |
curl "$BASE_URL/projects/$PROJECT_ID/reports/R_BPIC-21" -H "x-api-key: $API_KEY"curl "$BASE_URL/projects/$PROJECT_ID/reports/R_BPIC-21/document?type=report" -H "x-api-key: $API_KEY"Embed the authenticated status viewer
Section titled “Embed the authenticated status viewer”The viewer is the project page — the same view every stakeholder sees, rendered by Archistar and dropped into your UI as an <iframe>. It shows live processing progress, every report with its score, and the project’s documents.
There are two link types from GET /projects/:projectId/viewer-link:
- Signed (
?signed=1&generatedFor=<email>) — a time-limited (48 h), read-only URL for embedding as an iframe. No login required; anyone with the record sees the same view. Best for surfacing status on a permit record. - Unsigned — the standard viewer, which requires Archistar authentication and gives full reviewing capability.
-
Fetch a signed link from your backend.
generatedForrecords who it’s for.GET /projects/:projectId/viewer-link curl "$BASE_URL/projects/$PROJECT_ID/viewer-link?signed=1&generatedFor=reviewer@fairview.gov" \-H "x-api-key: $API_KEY"Response { "url": "https://viewer.archistar.ai/p/P_7A3F21C9?token=..." } -
Embed the returned URL in your application record view.
In your permit record page <iframesrc="https://viewer.archistar.ai/p/P_7A3F21C9?token=..."title="AI PreCheck — project status"style="width:100%; height:900px; border:0; border-radius:12px;"loading="lazy"referrerpolicy="no-referrer"></iframe>
Putting it together server-side — mint the link on demand so it’s always freshly signed:
// GET /permits/:id/precheck-viewer → { viewerUrl }export async function getViewerUrl(projectId: string, viewerEmail: string) { const { url } = await api( `/projects/${projectId}/viewer-link?signed=1&generatedFor=${encodeURIComponent(viewerEmail)}`, ); return url; // hand to the frontend to set as the iframe src}The four methods at a glance
Section titled “The four methods at a glance”The same calls compose into four integration methods. The Sandbox lets you step through each one.
| Method | How it runs | Calls it uses |
|---|---|---|
| Gated PreCheck | PreCheck is the last step before submit; a minimum score is required, and applicants can re-upload and re-run | Create draft → upload → process → poll reports / webhook → embed viewer; block submit until scores clear the bar |
| Ungated PreCheck | Same in-flow PreCheck, but no minimum — results are informational | Create draft → upload → process → poll reports / webhook → embed viewer; submit always allowed |
| Behind the scenes | Applicant submits your form; you create the project server-side and surface the viewer link on the permit record | Create (Option A, file links) at submit → webhook → store viewer link on the record |
| Standalone | Applicant runs PreCheck separately; you adopt an existing project by its ID | GET /projects/:projectId + reports + document + viewer-link; attach files & report to the record |