Quickstart
From an API key to every value on a drawing in a few minutes. You need:
- An API key. A workspace’s owner or an admin makes one in the SpecCapture dashboard, under API keys (how).
- A drawing: a PDF, TIFF, PNG, JPEG, BMP or WebP.
- One of: curl (with
jqfor the longer scripts), .NET 10, Python 3.9 or later withrequests, or Node.js 20 or later. Every sample below comes in all four; pick a tab and the docs remember it.
1. Put the key where the samples look for it
Section titled “1. Put the key where the samples look for it”Every sample reads the key from the environment variable SPECCAPTURE_API_KEY, so it never appears in code:
export SPECCAPTURE_API_KEY="sc_live_..."In PowerShell: $env:SPECCAPTURE_API_KEY = "sc_live_...".
2. Check the key
Section titled “2. Check the key”#!/usr/bin/env bashset -euo pipefailAPI=https://api.speccapture.comAUTH="Authorization: Bearer $SPECCAPTURE_API_KEY"
account=$(curl -sS --fail-with-body "$API/v1/account" \ -H "$AUTH") || { echo "$account" >&2; exit 1; }jq -r '"Workspace \(.workspaceName): \(.credits.available) credits, enough for \(.credits.sheetsAvailable) sheets"' <<<"$account"Save as check_key.sh, set SPECCAPTURE_API_KEY, and run: bash check_key.sh. Needs curl; scripts also use bash and jq.
using System.Text.Json;
using HttpClient http = new() { BaseAddress = new Uri("https://api.speccapture.com") };http.DefaultRequestHeaders.Authorization = new("Bearer", Environment.GetEnvironmentVariable("SPECCAPTURE_API_KEY"));
JsonElement account = await Json(await Call(HttpMethod.Get, "/v1/account"));Console.WriteLine($"Workspace {account.GetProperty("workspaceName")}: {account.GetProperty("credits").GetProperty("available")} credits, enough for {account.GetProperty("credits").GetProperty("sheetsAvailable")} sheets");
// Sends a request; stops with the problem's code and detail if the API refuses it.async Task<HttpResponseMessage> Call(HttpMethod method, string path, HttpContent? body = null, string? idempotencyKey = null) { using HttpRequestMessage request = new(method, path) { Content = body }; if (idempotencyKey is not null) request.Headers.Add("Idempotency-Key", idempotencyKey); HttpResponseMessage response = await http.SendAsync(request); if (!response.IsSuccessStatusCode) { JsonElement problem = await Json(response); string? detail = (problem.TryGetProperty("detail", out JsonElement text) ? text : problem.GetProperty("title")).GetString(); Console.Error.WriteLine($"{(int)response.StatusCode} {problem.GetProperty("code")}: {detail}"); Environment.Exit(1); } return response;}
// Reads an answer as JSON.static async Task<JsonElement> Json(HttpResponseMessage response) => JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;Save as check_key.cs, set SPECCAPTURE_API_KEY, and run: dotnet run check_key.cs. Needs .NET 10 or later.
import osimport sys
import requests
API = "https://api.speccapture.com"session = requests.Session()session.headers["Authorization"] = f"Bearer {os.environ['SPECCAPTURE_API_KEY']}"
def call(method, path, **kwargs): """Send a request; stop with the problem's code and detail if the API refuses it.""" response = session.request(method, API + path, **kwargs) if not response.ok: problem = response.json() sys.exit(f"{response.status_code} {problem['code']}: {problem.get('detail', problem['title'])}") return response
account = call("GET", "/v1/account").json()print(f"Workspace {account['workspaceName']}: {account['credits']['available']} credits, enough for {account['credits']['sheetsAvailable']} sheets")Save as check_key.py, set SPECCAPTURE_API_KEY, and run: python check_key.py. Needs Python 3.9+ and requests (pip install requests).
const API = "https://api.speccapture.com";const headers = { Authorization: `Bearer ${process.env.SPECCAPTURE_API_KEY}` };
const account = await call("GET", "/v1/account").then((response) => response.json());console.log(`Workspace ${account.workspaceName}: ${account.credits.available} credits, enough for ${account.credits.sheetsAvailable} sheets`);
// Sends a request; stops with the problem's code and detail if the API refuses it.async function call(method, path, init = {}) { const response = await fetch(API + path, { ...init, method, headers: { ...headers, ...init.headers } }); if (!response.ok) { const problem = await response.json(); console.error(`${response.status} ${problem.code}: ${problem.detail ?? problem.title}`); process.exit(1); } return response;}Save as check_key.mjs, set SPECCAPTURE_API_KEY, and run: node check_key.mjs. Needs Node.js 20 or later.
An answer of 401 means the key was not accepted; its code says why (Errors).
3. Submit a drawing, wait for it, and read it
Section titled “3. Submit a drawing, wait for it, and read it”#!/usr/bin/env bashset -euo pipefailAPI=https://api.speccapture.comAUTH="Authorization: Bearer $SPECCAPTURE_API_KEY"
DRAWING=bracket.pdf # the drawing to send: a PDF, TIFF, PNG, JPEG, BMP or WebP
job=$(curl -sS --fail-with-body "$API/v1/extractions" \ -H "$AUTH" \ -H "Idempotency-Key: $(openssl rand -hex 16)" \ -F "file=@$DRAWING") || { echo "$job" >&2; exit 1; }jq -r '"Job \(.jobId): \(.sheetCount) sheets, \(.credits.reserved) credits reserved"' <<<"$job"
while true; do job=$(curl -sS --fail-with-body "$API/v1/extractions/$(jq -r '.jobId' <<<"$job")" \ -H "$AUTH") || { echo "$job" >&2; exit 1; } jq -r '" \(.status): \(.progress.sheetsCompleted) of \(.sheetCount) sheets read"' <<<"$job" if [[ "$(jq -r '.status' <<<"$job")" =~ ^(completed|failed)$ ]]; then break; fi sleep 5done
while IFS= read -r summary; do if [[ "$(jq -r '.status' <<<"$summary")" == "completed" ]]; then sheet=$(curl -sS --fail-with-body "$API/v1/extractions/$(jq -r '.jobId' <<<"$job")/sheets/$(jq -r '.sheetNumber' <<<"$summary")" \ -H "$AUTH") || { echo "$sheet" >&2; exit 1; } jq -r '"Sheet \(.sheetNumber) (\(.reviewState))"' <<<"$sheet" jq -r '.fields[] as $field | " \($field.path) = \($field.value // "(not on the sheet)") [\($field.reviewState), \($field.confidence)]"' <<<"$sheet" fidone < <(jq -c '.sheets[]' <<<"$job")Save as quickstart.sh, set SPECCAPTURE_API_KEY, and run: bash quickstart.sh. Needs curl; scripts also use bash and jq.
using System.Text.Json;
using HttpClient http = new() { BaseAddress = new Uri("https://api.speccapture.com") };http.DefaultRequestHeaders.Authorization = new("Bearer", Environment.GetEnvironmentVariable("SPECCAPTURE_API_KEY"));
string drawing = args[0]; // the drawing to send: a PDF, TIFF, PNG, JPEG, BMP or WebP
using MultipartFormDataContent form = new() { { new StreamContent(File.OpenRead(drawing)), "file", Path.GetFileName(drawing) } };JsonElement job = await Json(await Call(HttpMethod.Post, "/v1/extractions", form, Guid.NewGuid().ToString()));Console.WriteLine($"Job {job.GetProperty("jobId")}: {job.GetProperty("sheetCount")} sheets, {job.GetProperty("credits").GetProperty("reserved")} credits reserved");
while (true) { job = await Json(await Call(HttpMethod.Get, $"/v1/extractions/{job.GetProperty("jobId")}")); Console.WriteLine($" {job.GetProperty("status")}: {job.GetProperty("progress").GetProperty("sheetsCompleted")} of {job.GetProperty("sheetCount")} sheets read"); if (job.GetProperty("status").GetString() is "completed" or "failed") break; await Task.Delay(TimeSpan.FromSeconds(5));}
foreach (JsonElement summary in job.GetProperty("sheets").EnumerateArray()) { if (summary.GetProperty("status").GetString() == "completed") { JsonElement sheet = await Json(await Call(HttpMethod.Get, $"/v1/extractions/{job.GetProperty("jobId")}/sheets/{summary.GetProperty("sheetNumber")}")); Console.WriteLine($"Sheet {sheet.GetProperty("sheetNumber")} ({sheet.GetProperty("reviewState")})"); foreach (JsonElement field in sheet.GetProperty("fields").EnumerateArray()) { Console.WriteLine($" {field.GetProperty("path")} = {field.GetProperty("value").GetString() ?? "(not on the sheet)"} [{field.GetProperty("reviewState")}, {field.GetProperty("confidence")}]"); } }}
// Sends a request; stops with the problem's code and detail if the API refuses it.async Task<HttpResponseMessage> Call(HttpMethod method, string path, HttpContent? body = null, string? idempotencyKey = null) { using HttpRequestMessage request = new(method, path) { Content = body }; if (idempotencyKey is not null) request.Headers.Add("Idempotency-Key", idempotencyKey); HttpResponseMessage response = await http.SendAsync(request); if (!response.IsSuccessStatusCode) { JsonElement problem = await Json(response); string? detail = (problem.TryGetProperty("detail", out JsonElement text) ? text : problem.GetProperty("title")).GetString(); Console.Error.WriteLine($"{(int)response.StatusCode} {problem.GetProperty("code")}: {detail}"); Environment.Exit(1); } return response;}
// Reads an answer as JSON.static async Task<JsonElement> Json(HttpResponseMessage response) => JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;Save as quickstart.cs, set SPECCAPTURE_API_KEY, and run: dotnet run quickstart.cs -- bracket.pdf. Needs .NET 10 or later.
import osimport sysimport timeimport uuid
import requests
API = "https://api.speccapture.com"session = requests.Session()session.headers["Authorization"] = f"Bearer {os.environ['SPECCAPTURE_API_KEY']}"
def call(method, path, **kwargs): """Send a request; stop with the problem's code and detail if the API refuses it.""" response = session.request(method, API + path, **kwargs) if not response.ok: problem = response.json() sys.exit(f"{response.status_code} {problem['code']}: {problem.get('detail', problem['title'])}") return response
drawing = sys.argv[1] # the drawing to send: a PDF, TIFF, PNG, JPEG, BMP or WebP
with open(drawing, "rb") as file: job = call( "POST", "/v1/extractions", files={"file": file}, headers={"Idempotency-Key": str(uuid.uuid4())}, ).json()print(f"Job {job['jobId']}: {job['sheetCount']} sheets, {job['credits']['reserved']} credits reserved")
while True: job = call("GET", f"/v1/extractions/{job['jobId']}").json() print(f" {job['status']}: {job['progress']['sheetsCompleted']} of {job['sheetCount']} sheets read") if job["status"] in ("completed", "failed"): break time.sleep(5)
for summary in job["sheets"]: if summary["status"] == "completed": sheet = call("GET", f"/v1/extractions/{job['jobId']}/sheets/{summary['sheetNumber']}").json() print(f"Sheet {sheet['sheetNumber']} ({sheet['reviewState']})") for field in sheet["fields"]: print(f" {field['path']} = {field['value'] or '(not on the sheet)'} [{field['reviewState']}, {field['confidence']}]")Save as quickstart.py, set SPECCAPTURE_API_KEY, and run: python quickstart.py bracket.pdf. Needs Python 3.9+ and requests (pip install requests).
import { openAsBlob } from "node:fs";import { basename } from "node:path";import { randomUUID } from "node:crypto";import { setTimeout as sleep } from "node:timers/promises";
const API = "https://api.speccapture.com";const headers = { Authorization: `Bearer ${process.env.SPECCAPTURE_API_KEY}` };
const [drawing] = process.argv.slice(2); // the drawing to send: a PDF, TIFF, PNG, JPEG, BMP or WebP
const form = new FormData();form.append("file", await openAsBlob(drawing), basename(drawing));let job = await call("POST", "/v1/extractions", { body: form, headers: { "Idempotency-Key": randomUUID() },}).then((response) => response.json());console.log(`Job ${job.jobId}: ${job.sheetCount} sheets, ${job.credits.reserved} credits reserved`);
while (true) { job = await call("GET", `/v1/extractions/${job.jobId}`).then((response) => response.json()); console.log(` ${job.status}: ${job.progress.sheetsCompleted} of ${job.sheetCount} sheets read`); if (["completed", "failed"].includes(job.status)) break; await sleep(5000);}
for (const summary of job.sheets) { if (summary.status === "completed") { const sheet = await call("GET", `/v1/extractions/${job.jobId}/sheets/${summary.sheetNumber}`).then((response) => response.json()); console.log(`Sheet ${sheet.sheetNumber} (${sheet.reviewState})`); for (const field of sheet.fields) { console.log(` ${field.path} = ${field.value ?? "(not on the sheet)"} [${field.reviewState}, ${field.confidence}]`); } }}
// Sends a request; stops with the problem's code and detail if the API refuses it.async function call(method, path, init = {}) { const response = await fetch(API + path, { ...init, method, headers: { ...headers, ...init.headers } }); if (!response.ok) { const problem = await response.json(); console.error(`${response.status} ${problem.code}: ${problem.detail ?? problem.title}`); process.exit(1); } return response;}Save as quickstart.mjs, set SPECCAPTURE_API_KEY, and run: node quickstart.mjs bracket.pdf. Needs Node.js 20 or later.
The program sends the drawing with an Idempotency-Key, so a retry can never be charged twice; asks for the job
every 5 seconds until it has finished; then prints every value of every sheet that was read, with its review
state and confidence. A value printed as needsReview waits for a person in your workspace: it is delivered as
read, but nobody has confirmed it yet.