Specta API, as shipped in the download
Specta REST API & Webhooks (v3.0)
Specta ships a JSON REST API and signed webhooks so supplier catalogs can be queued, watched and exported without anyone sitting in front of the screen.
Specta uses your own LLM API key. Extracted data stays on your server; each document goes only to the provider you configure (with a local Ollama model, it never leaves your server).
Authentication
Create a key in API & Webhooks (in the sidebar). Send it on every call:
Authorization: Bearer apk_...
The key is shown once at creation and stored as a SHA-256 hash. Revoke any time.
Endpoints
| Method & path | What it does |
|---|---|
GET /api/ping | Verify a key |
GET /api/batches?status= | Batches with roll-up status and per-status counts |
GET /api/batches/{id} | One batch with every queued file and its state |
GET /api/catalogs?status= | The catalogs |
POST /api/catalogs | Queue a supplier document (multipart/form-data) |
GET /api/exports?catalog_id=&preset=&format= | Export a catalog |
GET /api/openapi.json | OpenAPI 3 spec (no auth) |
A key acts with the role of the person it belongs to: a viewer's key reads, and POST /api/catalogs answers it 403 with the permission it lacks (catalog.edit).
Batches are the async part
A batch is the one genuinely long-running thing here: you queue twenty supplier PDFs, the run screen chews through them one file at a time, and you go and do something else. GET /api/batches gives you each batch's rolled-up status — processing, attention, extracted, reviewed or empty — plus the counts behind it (queued, extracting, extracted, failed, reviewed, total, done, remaining).
That status is the app's own roll-up, not a second opinion: attention means at least one file failed, reviewed means every file has been signed off, and a batch is never reported done while anything is still queued.
Queue a document
curl -X POST https://your-install/api/catalogs \
-H "Authorization: Bearer apk_..." \
-F "file=@supplier-2026.pdf" -F "supplier=Acme Ltd" -F "schema_id=3"
multipart/form-data, because JSON cannot carry a PDF. Same 20 MB cap, same MIME allow-list and same template fallback as the upload form — and, like the form, **the MIME type is read from the file's bytes, never from the header the client sends**.
Extraction stays a separate, explicit step. Queuing a document does not spend anything; extraction spends your LLM credit, and nothing here should spend that on your behalf without you asking. Trigger it from the catalog screen or the batch run.
Export
GET /api/exports?catalog_id=12&preset=woocommerce
GET /api/exports?catalog_id=12&format=json
preset is plain, woocommerce or shopify. Add reviewed=1 to export only the products a person has reviewed (3.2.0). This serves the same bytes the export buttons serve — including a template's saved column mapping when one exists for that platform, at the same precedence — so a nightly job and a human clicking Export cannot produce two different files for the same catalog.
format=json returns the products as structured data instead, each carrying needs_review computed from the same confidence threshold the review screen uses.
Since 3.2.0 each product also carries image_url, variant_group_id (0 when it is not in a confirmed group), written_state ("", generated or approved) and written: the title and description written from its confirmed facts, only once a person has approved them (null before). The MCP tools return the same fields.
Webhooks
Add receiver URLs in API & Webhooks. Events:
batch.completed— a batch leavesprocessing, whether it finished cleanly or with failurescatalog.extracted— a catalog has been read: a single upload's extraction, or each file a batch reads. The payload carries the catalog and itsproductscount
batch.completed fires exactly once, on the transition out of processing. The run screen polls after every file; an alert that arrives twenty times is an alert nobody reads. A batch that ends with failures still completes — attention is a finished state, not a limbo — and the payload carries the full summary so a receiver can decide whether anyone needs waking up.
Each delivery is an HTTP POST with a JSON body and:
X-Specta-Event: batch.completed
X-Specta-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the endpoint's secret>
Verify the signature before trusting a payload. Delivery is one attempt with a short timeout; the Recent deliveries log shows every attempt and its response code. Design receivers to be idempotent.
Email alerts
Set an SMTP server and a notification address in Settings to get a mail when a batch finishes, including how many files failed and a link straight to the batch.
Backup
/backup.json exports every table with both the SMTP password and your LLM API key redacted.
Why there is no product import
Products in Specta exist only as extraction output, scored field by field against a template and carrying a confidence value that drives the review heat-map. There is no manual product-entry screen to mirror, and importing a CSV of finished products would bypass the template + confidence model that is the entire reason the product exists. The import path here is a supplier document; the schema is the thing you configure.
Limits, honestly
- The API acts at user level; there is no separate scope system.
- List endpoints cap at 200 rows.
- One delivery attempt per webhook event (log + idempotent receivers, not a retry queue).
- Extraction and review stay in the UI — both are judgment calls that spend money or attention.
- Extraction quality is your model's, not Specta's. The confidence scores tell you where to look.
Own It 3.0 — the agent endpoint and the rest
MCP — Specta for an AI assistant
Specta speaks MCP (Model Context Protocol) on one route, so an assistant can review a normalized catalog, find what is incomplete and pull export-ready rows — without anyone writing an integration.
POST /mcp
Authorization: Bearer <your API key> ← the SAME revocable key the REST API uses
Content-Type: application/json
GET /mcp answers 405 with Allow: POST, so a misconfigured client is told what to do rather than left guessing at a 404.
| Tool | Writes? | What it does |
|---|---|---|
list_catalogs | no | Catalogs with status, supplier and schema |
catalog_detail | no | One catalog, its schema columns and a completeness summary |
list_items | no | Products; incomplete_only returns just the ones missing required attributes |
item_detail | no | One product: every attribute, its confidence, and what is missing |
export_rows | no | The catalog as export-ready rows, mapped through the schema's preset |
batch_status | no | Per-file progress of a batch run |
Connect it to an assistant
Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.
Every client needs the same three facts, and nothing in the handshake is vendor-specific:
| The address | https://your-install/mcp |
| The key | header Authorization: Bearer apk_xxxx |
| The transport | MCP over streamable HTTP, stateless |
Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:
claude mcp add --transport http specta https://your-install/mcp \
--header "Authorization: Bearer apk_xxxx"
ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):
{
"type": "mcp",
"server_label": "specta",
"server_url": "https://your-install/mcp",
"authorization": "apk_xxxx",
"require_approval": "never"
}
**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"*. Pasted as-is, a block written for another client will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.
A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:
curl -X POST https://your-install/mcp \
-H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Specta implements the protocol, not an integration with a particular vendor.
No tool can start an extraction
This is the refusal the endpoint is designed around, and it is deliberate rather than missing.
An extraction run calls your AI provider on your key. An agent that could trigger one could spend real money without a person deciding to — a retry loop, an over-eager assistant re-running a catalog "to be helpful", or simply a misunderstanding. Starting a run is a decision with a price attached, so it stays a button in the browser.
batch_status is read-only on purpose: an agent can watch a run it did not start, report on it and tell you when it finishes. Every tool in the table above is declared readOnly.
Completeness is first-class over the API
catalog_detail returns the same roll-up the Completeness screen shows:
"completeness": {
"total": 120, "complete": 96, "incomplete": 24,
"required_columns": ["Product name", "SKU", "Width"],
"missing_by_column": { "width": {"label": "Width", "missing": 18} }
}
list_items with incomplete_only: true returns only the products that are not ready, each with a missing_required list. That is the shape an assistant needs to be useful here: "these 24 items are missing a width, and here they are."
A value of 0 counts as supplied. Only empty and whitespace-only values are treated as missing — a required column with a legitimate zero is not a gap.
Roles
| Role | Can |
|---|---|
viewer | Read and export catalogs, items and completeness |
member | …plus upload, run extractions, correct products, edit schemas |
admin | …plus settings, branding, restore, the team |
An API key can do exactly what its owner can do. Because no tool starts a run, a member's key and a viewer's key differ here only in what they may read and export.
Spec sheet (PDF)
GET /products/{id}/spec.pdf
One page per item: the normalized attributes, your logo and accent. Required attributes that are not specified are listed as not specified rather than omitted, so the gap is visible to whoever reads the sheet.
Since 3.2.0 the spec sheet uses the product's approved written title and description when it has them.
Catalog sheet (PDF)
GET /catalogs/{id}/sheet.pdf
The reviewed products of a catalog in one PDF: a section per product (a confirmed variant group is one section with a table of its variants: SKU, each option, price), the approved written description or else the supplier's own, the specifications, and a closing line naming how many unreviewed products were left out. Browser session; any role that can export.
Marketplace feeds
POST /catalogs/{id}/feed (target=google|meta, link, brand, condition, availability)
A Google Merchant Center feed (tab-separated) or a Meta catalog feed (CSV) of the reviewed products that have a price and an image URL. Browser session with its CSRF token; any role that can export. The settings are remembered for the catalog when the role may edit it.
Completeness export
GET /catalogs/{id}/incomplete.csv
Every product missing a required attribute, with the list of what it is missing — the working file for whoever is filling the gaps.
Supplier lists on a schedule
php bin/fetch-suppliers.php
Run from your own cron (once a day is enough). For each supplier profile whose schedule is due it downloads the list from the profile's https:// address and files a new catalog only when the file changed; extraction stays a click in the browser. There is no HTTP endpoint for it: a fetch reaches out to another server, so it runs where you can see it.
Scheduled backup
GET /backup/run?t=<token>
Mint the token in Backup & restore. Fourteen files are kept and older ones pruned. Both JSON backups — the scheduled one and the download — strip the same credentials: your AI provider key, SMTP and OIDC secrets, and every password and API-key hash. The .sqlite download is the whole install.
Connect it to Zapier, Make or n8n
Specta talks to other tools without a plugin: it sends a webhook (an HTTPS message) the moment something happens, and it accepts calls to the API above. Zapier, Make and n8n can use both.
Before you start
- In Specta, open API & Webhooks. Mint an API key (choose Read only if the other tool only reads), and add a webhook when you have its URL (below).
- Use your install's full address, for example
https://specta.yourcompany.com.
When something happens in Specta: send it to another tool
- In the other tool, create the trigger that gives you a URL to receive webhooks: - Zapier: the "Webhooks by Zapier" app, trigger Catch Hook (Webhooks by Zapier needs a paid Zapier plan). - Make: module Webhooks → Custom webhook. - n8n: the Webhook node.
- Copy that URL into Specta under API & Webhooks, and choose the event or events below.
- Make the event happen once in Specta, so the other tool receives a sample and you can map its fields.
| Event | Fires when |
|---|---|
batch.completed | A batch leaves , whether it finished cleanly or with failures |
catalog.extracted | A catalog has been read: a single upload's extraction, or each file a batch reads. The payload carries the catalog and its count |
Every delivery carries X-Specta-Signature, an HMAC-SHA256 signature over the raw body. Zapier and Make accept the delivery without checking it, so keep the webhook URL private. In n8n you can check it in a Code node before using the data.
When something happens elsewhere: act in Specta
- Zapier: "Webhooks by Zapier", action POST (or Custom Request).
- Make: module HTTP → Make a request.
- n8n: the HTTP Request node.
Send the request to your install's address plus the path from the Endpoints table above, with the header Authorization: Bearer <your API key> and a JSON body. A read-only key cannot write.
The endpoints that change something:
POST /api/catalogs: Queue a supplier document (multipart/form-data)
Example recipes
- batch.completed → a row in Google Sheets: webhook trigger on
batch.completed(A batch leaves , whether it finished cleanly or with failures), then the Google Sheets "Create row" action with the fields you want. - catalog.extracted → a message in Slack or Teams: webhook trigger on
catalog.extracted(A catalog has been read: a single upload's extraction, or each file a batch reads. The payload carries the catalog and its count), then the Slack or Microsoft Teams "Send message" action. - A new form entry or row elsewhere → Specta: the other tool's trigger (for example a new Google Sheets row or a form submission), then an HTTP action calling
POST /api/catalogs(Queue a supplier document (multipart/form-data)).
These recipes use only the events and endpoints documented above. We have not run them inside Zapier, Make or n8n ourselves; Specta's own automated tests check its API.