Parse electronic-component datasheets into verified, page-cited facts, and read the published Coppr catalogue.
Keys come from the Coppr console. The examples use curl and Python.
At a glancev0.1.0
Base URL
https://api.copprlab.com
Key
Authorization: Bearer or X-API-Key
Scopes
jobs:write parses datasheets. parts:read reads the catalogue.
Built from
openapi.json 0.1.0, fetched 2026-11-04
01Quick start
Parse a datasheet in four calls. You need a key with the jobs:write scope. Create one in the Coppr console under API keys. The console shows a key once, when you create it.
Set your key
shell
export COPPR_API_KEY=cpr_live_XXXXXXXX
A placeholder. A real key is cpr_ followed by 43 characters. Keep it out of source control.
export JOB_ID=9e0a176b5ae74ab0b51d69580b5a3b66 # the job_id from the last step
curl -sS https://api.copprlab.com/v1/jobs/$JOB_ID \
-H "Authorization: Bearer $COPPR_API_KEY"
status is queued, running, succeeded or failed. Polling every few seconds is fine. A two-page datasheet takes about 25 minutes when the OCR service starts cold; longer sheets take longer, and jobs queue behind each other.
Every value in the result is labelled verified or needs_review, with the page it was read from (Results). The zip is the Coppr part package (The export zip).
The same in Python
The whole flow, with the API's own error body raised as an exception. It needs requests.
python
import os
import time
import requests
API = "https://api.copprlab.com"
HEADERS = {"Authorization": "Bearer " + os.environ["COPPR_API_KEY"]}
def check(response):
"""Return the response, or raise with the API's own detail and reason."""
if response.ok:
return response
try:
body = response.json()
except ValueError:
body = {"reason": "not_json", "detail": response.text[:200]}
retry = response.headers.get("Retry-After")
wait = f" (retry after {retry} s)" if retry else ""
raise RuntimeError(f"{response.status_code} {body['reason']}: {body['detail']}{wait}")
with open("RK73H.pdf", "rb") as pdf:
job = check(
requests.post(
f"{API}/v1/jobs",
headers=HEADERS,
files={"file": pdf},
data={"manufacturer": "KOA Speer", "mpn": "RK73H"},
timeout=120,
)
).json()
while True:
status = check(requests.get(API + job["status_url"], headers=HEADERS, timeout=30)).json()
if status["status"] in ("succeeded", "failed"):
break
fraction = (status.get("progress") or {}).get("fraction")
print(status["status"], status.get("step"), fraction)
time.sleep(15)
if status["status"] == "failed":
raise SystemExit(status["error"])
result = check(requests.get(f"{API}/v1/jobs/{job['job_id']}/result", headers=HEADERS, timeout=30)).json()
for fact in result["spec_values"]:
print(fact["status"], fact["parameter"], fact["min"], fact["typ"], fact["max"], fact["unit"], "page", fact["page"])
package = check(requests.get(f"{API}/v1/jobs/{job['job_id']}/export", headers=HEADERS, timeout=120))
with open("RK73H-coppr-export.zip", "wb") as out:
out.write(package.content)
02Authentication and scopes
Every /v1 route takes an API key from the developer console (copprlab.com), as Authorization: Bearer cpr_... or X-API-Key: cpr_.... A key is cpr_ followed by 43 characters; the service stores only its SHA-256. A key carries scopes:
scope
allows
parts:read
GET /v1/parts, POST /v1/search, POST /v1/requirements/validate
jobs:write
POST /v1/jobs, GET /v1/jobs/{job_id}, GET /v1/jobs/{job_id}/result, GET /v1/jobs/{job_id}/export, GET /v1/jobs/{job_id}/source
A job is visible only to the key that submitted it. A revoked key may still be honoured for up to 30 seconds (an allow is remembered that long).
Per organization, per day (UTC): 2,000 requests and 10 parse jobs; across the service, 50 parse jobs. A submission the API refuses (a bad PDF, a missing field) does not count against the job quota.
Per key: 60 requests a minute. Per client address: 120 requests a minute.
Bodies: a PDF up to 40 MB, JSON up to 256 KB, each with a declared Content-Length.
04Errors
Every error is JSON: {"detail": "a sentence for a person", "reason": "a code for a program"}. A 429 and a 503 carry Retry-After (seconds); a daily quota's is the time to the next UTC midnight.
error body
{
"detail": "a sentence for a person",
"reason": "a code for a program"
}
Branch on reason; show detail to a person. Each route lists the reasons it can return in the reference.
Reason
Status
Meaning
invalid
401
No key, a malformed key, or a key the console never issued. Send Authorization: Bearer cpr_... with a key from the developer console.
revoked
401
The key was revoked in the developer console. Create a new key in the console.
insufficient_scope
403
The key lacks the scope the route needs (parts:read or jobs:write). Use a key with that scope, or add the scope in the console.
quota_requests
429
The organization's requests for the day (UTC) are used up. Wait for Retry-After seconds (the next UTC midnight).
quota_jobs
429
The key's or the organization's parse jobs for the day (UTC) are used up. Wait for Retry-After seconds (the next UTC midnight).
service_cap
429
The service's parse jobs for the day (UTC), across every organization, are used up. Wait for Retry-After seconds (the next UTC midnight).
rate_limited
429
More than 60 requests in a minute with one key. Wait for Retry-After seconds.
address_rate_limited
429
More than 120 requests in a minute from one client address. Wait for Retry-After seconds.
too_many_bad_keys
429
Too many invalid or revoked keys from one client address; it is turned away for a minute. Check the key, then wait for Retry-After seconds.
key_check_unavailable
503
The key could not be checked (the database is unreachable). Every key is refused until it can be: the API fails closed. Retry after Retry-After seconds.
bad_request
400
A required field is missing or malformed. Fix the request; detail says what.
bad_pdf
400
The upload or the URL is not a usable PDF (not a PDF, too large, or not fetchable). A refused submission does not count against the job quota. Send a PDF datasheet.
invalid_parameters
422
A query parameter or body field has the wrong type or is missing. detail lists each problem.
not_found
404
No published part with that MPN, or no job with that id for this key.
job_not_finished
409
The job has no result yet (queued or running). Poll the job's status.
job_failed
409
The job failed; detail carries the error. Submit it again, or another datasheet.
length_required
411
A request with a body must declare its Content-Length.
body_too_large
413
The body is larger than allowed (40 MB for a PDF, 256 KB for JSON).
catalogue_error
502
The catalogue read failed. Retry; report it if it persists.
catalogue_unavailable
503
The catalogue cannot be read right now. Retry shortly.
05The job lifecycle
POST /v1/jobs returns at once with a job id and a status_url. The job waits in a queue, then runs four steps in order, and ends succeeded or failed.
queuedqueue_position says where: 1 is next.
ingestPages, text, OCR, layout and the document index.
extractTables, pins and ratings.
figuresGraphs and drawings.
verifyEvery value checked against its page.
succeededresult_url and export_url are set.
failederror says why.
How long it takes
A two-page datasheet takes about 25 minutes when the OCR service starts cold; longer sheets take longer, and jobs queue behind each other. One measured example: a 24-page datasheet took 74.5 minutes.
Two workers run one job each; other jobs wait in the queue.
A service restart fails any running job. Submit it again; nothing is half-kept.
A job never publishes to the catalogue. Publishing stays behind Coppr's release gates.
Only the key that submitted a job can read it. Any other key gets not_found.
A failed job answers job_failed from /result and /export. Submit it again, or another datasheet.
Reading the status
GET /v1/jobs/{job_id} is served from the job's own record. It gives the step running now and for how long, the steps already done with their durations, a progress estimate, and the latest events.
The step running now (ingest, extract, figures, verify), or done.
stepsrequired
array of string
Every step, in order.
queue_position
integer or null
While queued: 1 is next.
manufacturerrequired
string
mpnrequired
string
category
string or null
source_url
string or null
source_filename
string or null
The uploaded file's name, or the URL's last segment.
pages
integer or null
The datasheet's page count, once ingest has counted it.
pdf_sha256
string or null
SHA-256 of the datasheet parsed.
created_atrequired
string
started_at
string or null
finished_at
string or null
error
string or null
Why a failed job failed.
current_step
CurrentStep or null
While running: the step and its elapsed time.
completed_steps
array of StepTiming
The steps finished, with durations.
progressrequired
Progress
events
array of JobEvent
The most recent events (at most 10).
result_url
string or null
Once succeeded.
export_url
string or null
Once succeeded: the export zip, a Coppr part package (with KiCad interop files where a land pattern was read).
current_step
Field
Type
Description
steprequired
string
ingest, extract, figures or verify.
started_at
string or null
elapsed_secondsrequired
integer
completed_steps
Field
Type
Description
steprequired
string
seconds
number or null
How long the step took.
started_at
string or null
finished_at
string or null
progress
An estimate, not a promise. basis says how it was made.
Field
Type
Description
fractionrequired
number
0 to 1: an estimate from each step's typical time, scaled by the datasheet's pages; 1 once succeeded.
estimated_seconds_remaining
integer or null
An estimate; none once failed.
basisrequired
string
How the estimate is made.
events
Field
Type
Description
atrequired
string
steprequired
string
textrequired
string
What a finished step found, e.g. "found 3 tables on p.13".
06Results
GET /v1/jobs/{job_id}/result answers once the job has succeeded. Before that it returns job_not_finished; after a failure, job_failed with the error in detail. A result is yours alone and is never published to the catalogue.
It holds the parsed values, the pins, the orderable codes, each package's body outline and land-pattern pads in millimetres, and a manifest of what the export zip holds. Every value carries a status, verified or needs_review, and the page it was read from. Concepts explains what the statuses mean.
Field
Type
Description
schemarequired
string
copper-parser-api-result/v1.
mpnrequired
string
manufacturerrequired
string
category
string or null
labelsrequired
object
How many items carry each status.
by_kindrequired
object
Per kind of item (spec_value, table, graph ...), how Layer 4 settled them.
spec_valuesrequired
array of SpecValue
pinsrequired
array of object
package, number, name, status.
orderablesrequired
array of object
code, package, lifecycle, status.
packages
array of PackageGeometry
Each package read: body outline and land-pattern pads in mm, with status.
export
ExportManifest or null
What GET /v1/jobs/{job_id}/export holds.
other_itemsrequired
object
Counts of the other items (graphs, tables, drawings) by status.
Each entry of packages is one package the parse read: its body outline, and the manufacturer's recommended land pattern as pads. All lengths are in millimetres. Pad positions are measured from the package centre with y pointing down, so they can be drawn as SVG without a flip.
When a land pattern is verified
A land pattern is verified only when two independent readings of the drawing agree with the datasheet page. Otherwise it stays needs_review, and open_checks names the dimension that did not settle.
Today the rule covers two-terminal chip packages: resistors, non-polarized capacitors, and inductors with a series land-pattern table. Every other package kind keeps its land pattern at needs_review for now. Terminal positions are left unknown when the datasheet does not give them; they are never invented.
two_terminal_package (body and land pattern) or package_topology (terminals only).
statusrequired
string
verified or needs_review.
package
string or null
drawing_identifier
string or null
The size row or package name as printed.
page
integer or null
The page the package table or drawing is on.
body
Body or null
pads
array of Pad
The manufacturer's recommended land pattern; empty when the datasheet gives none the parser read.
open_checks
array of string
The checks that did not settle, named.
body
Field
Type
Description
length_mmrequired
number
Along the terminal axis (x).
width_mmrequired
number
height_mmrequired
number
The printed maximum.
shaperequired
string
rect, centred on the origin.
centrerequired
array of number
pads
Field
Type
Description
numberrequired
string
The terminal the pad lands (1, 2 for a two-terminal part: local identities).
x_mmrequired
number
Pad centre from the package centre, along the terminal axis.
y_mmrequired
number
Pad centre from the package centre, y down.
width_mmrequired
number
Size along x.
height_mmrequired
number
Size along y.
shaperequired
string
rect.
drill_mm
number or null
A through-hole pad's drill; none for a surface pad.
statusrequired
string
07The export zip
GET /v1/jobs/{job_id}/export returns a finished job as one zip: the Coppr part package. The file is named <mpn>-coppr-export.zip. Like the result, it is yours alone.
What is in it
A zip of what the parse found:
part.json: every value as printed (with its unit), pins, orderable codes, graphs (with their curves) and packages, each with its status and page anchor (page, table, printed line, bounding box in PDF points);
facts.csv: the values, one per row (section, parameter, symbol, condition, min, typ, max, rating, unit, status, page, table, printed);
README.md: the source citation (datasheet URL and SHA-256, pages, job), the Coppr part and how to bring it into Coppr, then the KiCad files, and what is missing or open;
coppr/ (the part, where the parse read a land pattern):
<mpn>.packet.json: the evidence-backed part packet Coppr ingests (copper library ingest-local), each value cited, with the parser's own verdict (producer_verification);
<mpn>.component.json: the Coppr component the pinned Coppr CLI built from it (library inspect): identity, the semantic symbol generated from pins, electrical types and functional groups (never drawn), the footprint with Coppr's verification state, and provenance. A land pattern the parse verified is a verified footprint; one with a value still open stays needs_review;
<mpn>.symbol.svg: the symbol as Coppr's own pipeline renders it (library render --glyph). The pinned CLI renders no footprints, so there is no footprint drawing;
coppr/ (the part as a symbol alone, where the parse read pins but no land pattern):
<mpn>.component-draft.json: a Coppr component draft (copper library new-component): identity and the pins, each with the electrical type and signal role (its functional group) the catalogue publishes for it. A pin nothing settled a type for is Coppr's unspecified, never a guess, and export.coppr.reason counts them;
<mpn>.component.json and <mpn>.symbol.svg, as above, with no footprint (export.coppr.footprint is none);
kicad/ (interop): a KiCad symbol (.kicad_sym) and footprint (.kicad_mod) Coppr's KiCad exporter made from the same component. A footprint still open is a draft stamped NEEDS REVIEW, and the README names what did not settle.
Without a land pattern there are no KiCad files, and without pins either no Coppr component; the README says why. No footprint and no pin type is ever invented.
GET /v1/jobs/{job_id}/source returns the exact PDF the job parsed. Its SHA-256 is the job's pdf_sha256, and exports of an uploaded datasheet cite it.
The manifest in the result
The result's export field says what the zip holds before you download it: the file names; coppr, whether the Coppr component is included and the state of its footprint (verified, needs_review, or none for a symbol-only component), with a reason for what is missing; and kicad, the same for the interop files.
The Coppr component: included, footprint (Coppr's own state of it: verified, needs_review, or none for a symbol-only component), and the reason for what is missing: the component, its footprint, its symbol drawing, or pins' electrical types.
kicadrequired
object
The KiCad files (interop): included, footprint (verified or draft), and the reason when there are none.
KiCad library files, as interop
The Coppr part package is the primary output. Where the datasheet prints a land pattern the parse read, the zip also holds KiCad library files under kicad/: a symbol (.kicad_sym) and a footprint (.kicad_mod), with the packet they were made from. They are there for interop with KiCad.
The manifest's kicad object says whether they are included, whether the footprint is verified or a draft, and why not when they are missing. A draft footprint is stamped NEEDS REVIEW inside the file, and the README says which footprints are verified and which are drafts. Without a land pattern there are no KiCad files, and the README says why.
08Concepts
Statuses
Each fact, pin and parsed value carries a status:
verified: confirmed on the datasheet page it cites (by code against the page's text layer, or by an independent second reading of the page image).
needs_review: extracted and cited, but not confirmed; check it against the cited page before relying on it.
generic_verified (catalogue only): confirmed against a family- or series-level source rather than a datasheet for this exact part number.
Every catalogue fact carries the literal it was read from (printed) and its conditions; a published part revision carries the release it came from.
The catalogue's search returns only parts with nothing left to review, unless include_review is true. Today most parts still carry at least one review row, so most appear only with include_review.
Provenance
Every catalogue fact keeps the literal exactly as printed, and its conditions: the spec type (absolute maximum, recommended operating condition or electrical characteristic), the test condition, the printed parameter name, and the variant or device it applies to. Values are converted to SI units (V, A, H, F, ohm, Hz), and the printed literal is kept alongside.
In the catalogue each value is anchored to its page: the page number, a table id, and cell bounding boxes in PDF points. An export's part.json carries that anchor for each value. GET /v1/parts does not return page anchors yet. Parse-job results give each value's page.
Parse jobs and the catalogue
A parse job reads one datasheet for you and never publishes to the catalogue. The catalogue holds the parts Coppr has published, and the catalogue routes (parts:read) read it. A published part contains:
facts, with canonical units, conditions and status;
performance curves: digitized graphs, with their axes;
pins, with names and electrical types;
packages: body dimensions, terminals, and the manufacturer's land pattern where the datasheet prints one;
orderable part numbers, with package codes and lifecycle.
GET /v1/parts returns facts, pins, categories and orderables. Curves, packages and land patterns are in the catalogue but are not served by that route yet.
Not covered yet
Drawing-only datasheets, where ratings or dimensions appear only in drawing notes. Some connectors (XT60PW-M, USB4105) are refused with "no fact".
Chinese-only datasheets, where no specification rows are read. The manufacturer's English edition works.
Packages for two-terminal passives with no dimension table. They get pins 1 and 2, but no package.
Reading a multilayer ceramic capacitor's part number from the numbering page of a catalogue, and scanned series catalogues.
09Reference
Every route, built from the API's own openapi.json. Each route lists its scope, its parameters, and the error reasons it can return; each reason links to the error table.
Parse jobs
Submit a datasheet and read back what the parser found, each value labelled verified or needs_review. Scope jobs:write. A job runs its steps in order: ingest (pages, text, OCR), extract (tables, pins, ratings), figures (graphs and drawings), verify (every value checked against its page). A two-page datasheet takes about 25 minutes when the OCR service starts cold; longer sheets take longer, and jobs queue behind each other.
POST/v1/jobsjobs:write
Submit a datasheet for parsing
A PDF upload (multipart: file, manufacturer, mpn, optional category) or a URL (JSON: datasheet_url, manufacturer, mpn, optional category). Returns at once with the job id; poll status_url. Each accepted job counts against the key's and the organization's daily job quota and the service's; a refused submission does not.
Its status, the step running and for how long, the steps done with their durations, a progress estimate and the latest events; result_url and export_url once it has succeeded. Served from the job's own record, so polling every few seconds is fine. Only the key that submitted a job can read it.
What the parse found, each value labelled verified or needs_review, with the page it was read from: values, pins, orderable codes, each package's body outline and land-pattern pads in mm, and what the export zip holds. Not published to the catalogue. (The example mixes two jobs' results to show every field.)
The exact PDF the job parsed (its SHA-256 is the job's pdf_sha256); exports cite it for an uploaded datasheet. Only the key that submitted the job can read it.
The published Coppr catalogue: parts, search, and requirement checks. Scope parts:read.
GET/v1/partsparts:read
Look up a published part by MPN
The published part's facts (with canonical units, conditions, the printed literal and status), pins, categories and orderable part numbers. The MPN is matched as published (the series or base part number, e.g. LMR16030, NRS4018T2R2MDGJ).
Parameters
Name
In
Type
Description
mpnrequired
query
string
The manufacturer part number, e.g. LMR16030.
Response 200 · application/json · Part
The published part (trimmed in this example).
Example response
{
"mpn": "LMR16030",
"manufacturer": "Texas Instruments",
"family": "LMR16030",
"description": "LMR16030 SIMPLE SWITCHER® 60-V, 3-A Step-Down Converter With 40-µA IQ",
"lifecycle_status": "unknown",
"revision": {
"id": 311,
"label": null,
"published_at": "2026-11-02T07:07:04.967699+00:00",
"verification_status": "verified"
},
"categories": [
{
"category_code": "integrated_circuit.power_management.converter.buck_regulator",
"assignment_role": "primary",
"verification_status": "needs_review"
}
],
"facts": [
{
"key": "electrical.input_voltage.operating",
"kind": "quantity",
"min": 4.3,
"typ": null,
"max": 60,
"unit": "V",
"value": null,
"status": "verified",
"printed": "This allows self start-up when the input voltage is in the operation range: 4.3 V to 60 V.",
"conditions": {
"spec_type": "recommended_operating_condition",
"test_condition": "when the EN pin is connected to the VIN pin",
"printed_parameter": "Input voltage range"
}
}
],
"pins": [
{
"pin_number": "2",
"pin_name": "VIN",
"electrical_type": "power_in",
"signal_roles": [
"power"
]
}
],
"orderables": [
{
"code": "LMR16030PDDA",
"package_code": "DDA",
"lifecycle": "active",
"temperature_grade": "-40 to 125"
}
]
}
Search published parts by category and requirements
Parts in the category whose published facts meet every requirement. Each result carries its per-requirement evaluation: matched, not matched, or not evaluable (only a typical value is printed). Check requirements with POST /v1/requirements/validate first: an unknown fact key excludes every part.
Request body application/json · SearchRequest
Field
Type
Description
category
string or null
A category code; parts in it are searched.
requirements
array of Requirement
include_review
boolean
Also return parts whose facts, categories or pins include needs_review rows (most parts today). Without it, only parts with nothing left to review are returned. Default False.
Check requirements against the catalogue's vocabulary
Whether each requirement names a defined fact key, an operator its kind allows and a unit of its dimension, with the allowed operators and the fact's canonical unit.
Request body application/json · ValidateRequest
Field
Type
Description
requirementsrequired
array of Requirement
category
string or null
Check the keys against this category's vocabulary.