Errors
Every error is a problem (RFC 9457 problem details) with the content type application/problem+json:
{ "type": "https://speccapture.com/docs/api/errors#not_enough_credits", "title": "Not enough credits.", "status": 402, "detail": "This file has 2 sheets, which needs 8 credits (4 a sheet), and the workspace has 4 available.", "instance": "/v1/extractions", "code": "not_enough_credits", "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "creditsNeeded": 8, "creditsAvailable": 4, "sheetCount": 2, "upgradeUrl": "https://app.speccapture.com/billing"}Branch on code. A published code never changes meaning. title and detail are written for people and may
be reworded; type links to the code’s entry below. Some problems carry facts of their own beside the code, such
as creditsNeeded above. Quote the traceId when you report a problem with a request: in the SpecCapture dashboard,
Help, then Report a problem.
#!/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 "$API/v1/extractions" \ -H "$AUTH" \ -H "Idempotency-Key: $(openssl rand -hex 16)" \ -F "file=@$DRAWING")case "$(jq -r '.code // empty' <<<"$job")" in "") ;; # no problem: the request was accepted not_enough_credits) jq -r '"This file needs \(.creditsNeeded) credits and the workspace has \(.creditsAvailable). Get more at \(.upgradeUrl)"' <<<"$job" exit 1 ;; too_many_sheets) jq -r '"Split the file: \(.detail)"' <<<"$job" exit 1 ;; *) jq -r '"\(.status) \(.code): \(.detail) (trace \(.traceId))"' <<<"$job" exit 1 ;;esac
jq -r '"Job \(.jobId) accepted"' <<<"$job"Save as submit_or_explain.sh, set SPECCAPTURE_API_KEY, and run: bash submit_or_explain.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) } };using HttpRequestMessage request = new(HttpMethod.Post, "/v1/extractions") { Content = form };request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());HttpResponseMessage response = await http.SendAsync(request);if (!response.IsSuccessStatusCode) { JsonElement problem = await Json(response); string? code = problem.GetProperty("code").GetString(); if (code == "not_enough_credits") { Console.WriteLine($"This file needs {problem.GetProperty("creditsNeeded")} credits and the workspace has {problem.GetProperty("creditsAvailable")}. Get more at {problem.GetProperty("upgradeUrl")}"); } else if (code == "too_many_sheets") { Console.WriteLine($"Split the file: {problem.GetProperty("detail")}"); } else { Console.WriteLine($"{problem.GetProperty("status")} {problem.GetProperty("code")}: {problem.GetProperty("detail")} (trace {problem.GetProperty("traceId")})"); } Environment.Exit(1);}JsonElement job = await Json(response);
Console.WriteLine($"Job {job.GetProperty("jobId")} accepted");
// Reads an answer as JSON.static async Task<JsonElement> Json(HttpResponseMessage response) => JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;Save as submit_or_explain.cs, set SPECCAPTURE_API_KEY, and run: dotnet run submit_or_explain.cs -- bracket.pdf. Needs .NET 10 or later.
import osimport sysimport uuid
import requests
API = "https://api.speccapture.com"session = requests.Session()session.headers["Authorization"] = f"Bearer {os.environ['SPECCAPTURE_API_KEY']}"
drawing = sys.argv[1] # the drawing to send: a PDF, TIFF, PNG, JPEG, BMP or WebP
with open(drawing, "rb") as file: response = session.request( "POST", API + "/v1/extractions", files={"file": file}, headers={"Idempotency-Key": str(uuid.uuid4())}, )if not response.ok: problem = response.json() if problem["code"] == "not_enough_credits": print(f"This file needs {problem['creditsNeeded']} credits and the workspace has {problem['creditsAvailable']}. Get more at {problem['upgradeUrl']}") elif problem["code"] == "too_many_sheets": print(f"Split the file: {problem['detail']}") else: print(f"{problem['status']} {problem['code']}: {problem['detail']} (trace {problem['traceId']})") sys.exit(1)job = response.json()
print(f"Job {job['jobId']} accepted")Save as submit_or_explain.py, set SPECCAPTURE_API_KEY, and run: python submit_or_explain.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";
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));const response = await fetch(API + "/v1/extractions", { method: "POST", body: form, headers: { ...headers, "Idempotency-Key": randomUUID() },});if (!response.ok) { const problem = await response.json(); if (problem.code === "not_enough_credits") { console.log(`This file needs ${problem.creditsNeeded} credits and the workspace has ${problem.creditsAvailable}. Get more at ${problem.upgradeUrl}`); } else if (problem.code === "too_many_sheets") { console.log(`Split the file: ${problem.detail}`); } else { console.log(`${problem.status} ${problem.code}: ${problem.detail} (trace ${problem.traceId})`); } process.exit(1);}const job = await response.json();
console.log(`Job ${job.jobId} accepted`);Save as submit_or_explain.mjs, set SPECCAPTURE_API_KEY, and run: node submit_or_explain.mjs bracket.pdf. Needs Node.js 20 or later.
Every code
Section titled “Every code”The codes in order of status; each one’s address on this page is the type its problems carry.
| Code | Status | What it means | What to do |
|---|---|---|---|
invalid_request |
400 | A field is missing or not the kind of value it takes, a body is not valid JSON or not the expected shape (such as a form with more than one part named file), or a parameter is out of range. detail names the field and what it takes: expiresInDays must be a whole number from 1 to 90; leave it out for 30. |
Fix the request: the same request gets the same answer. |
file_missing |
400 | The form has no part named file. |
Send the drawing as multipart/form-data with the file in a part named file (-F "file=@drawing.pdf"). |
invalid_idempotency_key |
400 | The Idempotency-Key header is empty, longer than 255 characters, or holds characters other than printable ASCII. |
Use 1 to 255 printable ASCII characters with no spaces; a UUID is a good choice. |
invalid_webhook_url |
400 | The webhook URL is not an https address on the public internet: it carries credentials, or names localhost, a .local or .internal host, a single-label host, or a private, loopback, link-local or reserved address. |
Use a public https address. To test on your own machine, put the receiver behind a tunnel with a public https name. |
api_key_missing |
401 | The request has no Authorization header. |
Send Authorization: Bearer sc_live_... on every request. |
api_key_malformed |
401 | The Authorization header is not Bearer sc_live_... or Bearer sc_test_.... |
Check the header: the word Bearer, one space, then the whole key as the dashboard showed it. |
api_key_invalid |
401 | The key is unknown, revoked or expired - one answer for all three, on purpose. | Make a new key in the dashboard and replace the old one wherever it is used. |
not_enough_credits |
402 | The workspace’s available credits do not cover the file’s sheets. Nothing was stored or reserved. Carries creditsNeeded, creditsAvailable, sheetsRemaining (the same balance in sheets), sheetCount and upgradeUrl. |
Open upgradeUrl, the dashboard’s Billing and credits page, to buy a plan or a credit pack, or send a file with fewer sheets. The same Idempotency-Key works for the retry. |
trial_ended |
402 | The workspace’s free trial has ended, and the trial’s credits with it; it has had no others. Carries trialEndedAt and upgradeUrl. |
Open upgradeUrl, the dashboard’s Billing and credits page, for how to get credits; any credits let the workspace read drawings again, and everything already read stays readable meanwhile. |
live_key_required |
403 | A test key cannot do this: it changes live configuration (webhooks, view links). | Use an sc_live_ key. |
role_not_allowed |
403 | The key’s role does not allow this: adding or deleting a webhook endpoint, or making or withdrawing a view link, needs an owner’s or an admin’s key. Carries keyRole. |
Use a key an owner or an admin created, or ask one of them to make the change. |
not_found |
404 | There is no such job, sheet, view link or webhook endpoint in this workspace. Another workspace’s id answers exactly the same, on purpose. | Check the id, and that the key belongs to the workspace that made it. |
route_not_found |
404 | No endpoint answers at this path. An unknown version (/v2/...) lands here too. |
Check the path against the API reference. |
method_not_allowed |
405 | The path exists, but not with this HTTP method. | Check the method against the API reference. |
idempotency_key_reused |
409 | The Idempotency-Key was used in the last day with a different file or template. |
Use a new key for a new request. |
idempotency_key_in_progress |
409 | An earlier request with the same key and file is still being handled. | Wait Retry-After seconds and send it again with the same key: the answer is the first request’s job. |
too_many_webhooks |
409 | The workspace already has as many webhook endpoints as it may. | Delete an endpoint you no longer use, then add the new one. |
not_finished |
409 | The job’s sheets are still being read, so there is nothing to export yet. Carries jobStatus. |
Wait until the job is completed or failed (Following a job), then download it. |
not_shareable |
409 | The sheet cannot be shared yet or at all: it has not been read, its drawing is past the workspace’s retention period (the job’s drawingPurgedAt), the workspace keeps no drawings, or it is marked export-controlled and the workspace has not kept its drawing. detail says which. |
Wait until the sheet has been read. A marked sheet can be shared once the workspace’s owner or an admin keeps its drawing in the dashboard. A drawing no longer kept, or a workspace that keeps none, has nothing to share. |
workspace_deleting |
409 | The workspace’s deletion has been asked for, so it takes no new work. | Nothing to retry: the workspace is going. |
file_too_large |
413 | The file is over the size limit, 50 MB a file, which detail states and maximumFileSizeMegabytes carries as a number (as does GET /v1/account). |
Split the file, or export it again at a lower resolution. |
unsupported_media_type |
415 | The body is not the type the endpoint takes: multipart/form-data to submit a drawing, application/json elsewhere. |
Set the content type; curl does with -F, and with -H "Content-Type: application/json". |
file_empty |
422 | The file has no bytes. | Check the path you are sending: the file may be empty, or still being written. |
not_a_drawing |
422 | The file is none of the formats SpecCapture reads, judged from its bytes rather than its name. | Export the drawing as a PDF, or save it as an image in one of the formats listed under Files. |
format_not_supported_yet |
422 | A format SpecCapture recognises but cannot read yet. No format is refused this way today; the code keeps its meaning for one that is. | Export the drawing as a PDF. |
unreadable_drawing |
422 | The file looks like a drawing format but cannot be opened: damaged, password-protected, or without pages. | Open it yourself to check; remove any password and export it again. |
too_many_sheets |
422 | The file holds more sheets than one file may, 200, which detail states and maximumSheetsPerFile carries as a number (as does GET /v1/account). |
Split the file and send the parts. |
template_not_found |
422 | The templateId sent is not one of the workspace’s templates (or it was archived), or templateVersion is not one of its published versions. Nothing was stored or reserved. |
Use an id from GET /v1/templates, drawing for every field, or leave templateId out for the workspace’s default. |
page_too_large |
422 | A page holds more pixels than one page may, 150 million: an image or a scan at too high a resolution, or a PDF page whose stated paper is enormous. detail says how large the page is; maximumPageMegapixels carries the limit as a number (as does GET /v1/account). |
Export or scan it again at a lower resolution. |
rate_limited |
429 | The key has used its allowance, which refills a request at a time, or the network address presented too many keys SpecCapture does not know. Carries retryAfterSeconds, and past a key’s allowance upgradeUrl. |
Wait Retry-After seconds - the time until the next request is allowed, not a whole minute - then send it again (Rate limits). |
internal_error |
500 | Something failed on our side. | Try again in a moment; if it keeps happening, report it with the traceId from the dashboard’s Help menu (Report a problem). |
test_mode_not_available |
501 | Test keys cannot submit or read extractions until they can replay sample results. | Use an sc_live_ key. |
storage_unavailable |
503 | The file could not be stored. A job may have been made for it (jobWithoutFileId); it fails and is refunded in full within minutes. |
Send the file again; the same Idempotency-Key works. |