CircuitHub

API Reference

Complete reference for the CircuitHub Platform REST API.

Base URL: https://api.circuithub.com/v1

All endpoints require authentication via Bearer token unless otherwise noted.

curl -H "Authorization: Bearer $CIRCUITHUB_API_KEY" https://api.circuithub.com/v1/...

Health

GET /health

Check API health. No authentication required.

// Response: 200 OK
{ "status": "ok" }

Authentication

GET /auth/cli

Initiate CLI login flow. Redirects to the callback URL with a JWT token.

ParameterInTypeDescription
redirect_uriquerystringRequired. Must be http://localhost:* or http://127.0.0.1:*

Response: 302 Found — redirects to {redirect_uri}?token={JWT}

The JWT is valid for 24 hours.

This endpoint is used internally by circuithub auth login. You don't need to call it directly.


Workspaces

GET /workspaces

List workspaces the authenticated user belongs to.

Response: 200 OK

{
  "workspaces": [
    {
      "id": 1,
      "slug": "acme-electronics",
      "name": "Acme Electronics"
    }
  ]
}
FieldTypeDescription
idintegerWorkspace ID
slugstringURL-safe identifier
namestringDisplay name

Projects

GET /projects

List projects accessible to the authenticated user.

ParameterInTypeDescription
workspacequerystringRequired. Workspace slug

Response: 200 OK

{
  "projects": [
    {
      "id": 12345,
      "name": "power-supply-board",
      "description": "24V to 5V buck converter",
      "createdAt": "2025-01-10T08:00:00Z",
      "updatedAt": "2025-03-01T14:30:00Z",
      "exportClassification": "EAR99",
      "latestRevisionId": 99001,
      "latestRevisionNumber": 3
    }
  ]
}

GET /projects/:projectId

Get a single project by ID.

Response: 200 OK

{
  "id": 12345,
  "name": "power-supply-board",
  "description": "24V to 5V buck converter",
  "createdAt": "2025-01-10T08:00:00Z",
  "updatedAt": "2025-03-01T14:30:00Z",
  "exportClassification": "EAR99",
  "latestRevisionId": 99001,
  "latestRevisionNumber": 3
}

POST /projects/upload-urls

Request presigned S3 upload URLs. Call this before creating a project or revision. You can request URLs for multiple files at once — for example, a design file and a BOM CSV.

Request body:

{
  "workspace": "acme-electronics",
  "files": [
    { "filename": "my-board.cvg", "size": 48210 },
    { "filename": "bom.csv", "size": 1024 }
  ]
}
FieldInTypeDescription
workspacebodystringRequired. Workspace slug
filesbodyarrayRequired. Files to upload
files[].filenamebodystringRequired. Filename
files[].sizebodyintegerRequired. File size in bytes

Response: 200 OK

{
  "uploads": [
    {
      "filename": "my-board.cvg",
      "uploadUrl": "https://circuithub-uploads.s3.amazonaws.com/...",
      "objectKey": "uploads/abc123/my-board.cvg",
      "validTill": "2025-03-15T11:00:00Z",
      "headers": [
        ["Content-Type", "application/octet-stream"],
        ["x-amz-acl", "bucket-owner-full-control"]
      ]
    },
    {
      "filename": "bom.csv",
      "uploadUrl": "https://circuithub-uploads.s3.amazonaws.com/...",
      "objectKey": "uploads/abc123/bom.csv",
      "validTill": "2025-03-15T11:00:00Z",
      "headers": [
        ["Content-Type", "application/octet-stream"],
        ["x-amz-acl", "bucket-owner-full-control"]
      ]
    }
  ]
}

Upload each file by sending a PUT request to its uploadUrl with the headers from the response and the file as the request body.

POST /projects

Create a new project from previously uploaded files. Include all files (design files and optional BOM CSV) in the files array.

Request body:

{
  "name": "power-supply-board",
  "workspace": "acme-electronics",
  "files": [
    { "objectKey": "uploads/abc123/my-board.cvg", "filename": "my-board.cvg" },
    { "objectKey": "uploads/abc123/bom.csv", "filename": "bom.csv" }
  ]
}
FieldInTypeDescription
namebodystringRequired. Project name
workspacebodystringRequired. Workspace slug
filesbodyarrayRequired. Uploaded file references
files[].objectKeybodystringRequired. S3 object key from upload-urls response
files[].filenamebodystringRequired. Original filename

Response: 201 Created

{
  "projectId": 12345,
  "revisionId": 98999,
  "statusUrl": "/v1/projects/12345/status"
}

GET /projects/:projectId/status

Check the import status of a project.

Response: 200 OK

{
  "projectId": 12345,
  "status": "importing",
  "message": null
}
FieldTypeDescription
projectIdintegerProject ID
statusstring"importing", "complete", or "failed"
messagestring | nullError message when status is "failed"

GET /projects/:projectId/revisions

List all revisions for a project.

Response: 200 OK

{
  "revisions": [
    {
      "id": 99001,
      "number": 3,
      "createdAt": "2025-03-01T14:30:00Z",
      "importedAt": "2025-03-01T14:35:00Z",
      "edaTool": "kicad",
      "edaError": null
    }
  ]
}

POST /projects/:projectId/revisions

Create a new revision on an existing project from previously uploaded files.

Request body:

{
  "files": [
    { "objectKey": "uploads/def456/my-board-v2.kicad_pcb", "filename": "my-board-v2.kicad_pcb" },
    { "objectKey": "uploads/def456/bom.csv", "filename": "bom.csv" }
  ]
}
FieldInTypeDescription
filesbodyarrayRequired. Uploaded file references
files[].objectKeybodystringRequired. S3 object key from upload-urls response
files[].filenamebodystringRequired. Original filename

Response: 201 Created

{
  "projectId": 12345,
  "revisionId": 99002,
  "statusUrl": "/v1/projects/12345/status"
}

GET /projects/:projectId/bom

Get the Bill of Materials for a project. Returns the latest revision's BOM by default.

ParameterInTypeDescription
revisionqueryintegerRevision ID. Defaults to the latest revision if omitted

Response: 200 OK

{
  "revisionId": 99001,
  "bomLines": [
    {
      "id": 1001,
      "value": "100nF",
      "footprint": "C_0402",
      "description": "Ceramic capacitor",
      "referenceDesignators": ["C1", "C2", "C3", "C4"],
      "status": "resolved",
      "resolvedPartMpn": "GCM155R71C104KA55D",
      "resolvedPartDescription": "100nF 16V X7R 0402",
      "resolvedManufacturer": "Murata"
    }
  ],
  "summary": {
    "total": 4,
    "resolved": 2,
    "unresolved": 1,
    "errored": 1
  }
}
FieldTypeDescription
revisionIdintegerRevision the BOM belongs to

BOM line fields:

FieldTypeDescription
idintegerBOM line ID
valuestring | nullComponent value
footprintstring | nullComponent footprint
descriptionstring | nullComponent description
referenceDesignatorsstring[]Reference designators
statusstring"resolved" | "unresolved" | "error"
resolvedPartMpnstring | nullMatched MPN
resolvedPartDescriptionstring | nullMatched part description
resolvedManufacturerstring | nullMatched manufacturer

Summary fields:

FieldTypeDescription
totalintegerTotal BOM lines
resolvedintegerMatched to a purchasable part
unresolvedintegerNo match found
erroredintegerResolution failed

PUT /bom/:bomLineId/part

Set the part for a BOM line. Use the BOM line id from GET /projects/:projectId/bom and a part id from GET /parts. The project is derived from the BOM line — no project ID is needed.

Request body:

{ "partId": 5001 }
FieldInTypeDescription
partIdbodyintegerRequired. Part ID from GET /parts

Response: 200 OK

{
  "partId": 5001,
  "mpn": "LM358DR",
  "manufacturer": "Texas Instruments"
}
FieldTypeDescription
partIdintegerPart ID that was set on the BOM line
mpnstringManufacturer part number
manufacturerstringManufacturer name

PUT /bom/:bomLineId/placements/:ref/dnp

Mark or unmark a placement as Do Not Place (DNP). A DNP placement will not be assembled or purchased. The project is derived from the BOM line — no project ID is needed.

Request body:

{ "dnp": true }
FieldInTypeDescription
dnpbodybooleanRequired. true to mark as DNP, false to unmark
ParameterInTypeDescription
refpathstringRequired. Reference designator (e.g. "C3")

Response: 200 OK

{
  "ref": "C3",
  "dnp": true
}
FieldTypeDescription
refstringReference designator
dnpbooleanCurrent DNP state

Quotes

Quoting is coming soon. The endpoints below describe the planned API surface — they are not yet available.

POST /quotes

Request a quote. Provide exactly one of projectId (quotes the latest revision) or revisionId.

Returns a cached quote if one is still valid for the same revision, otherwise generates a new one.

Request body:

{ "projectId": 12345 }
// or
{ "revisionId": 99001 }
FieldInTypeDescription
projectIdbodyintegerProject to quote — uses the latest revision
revisionIdbodyintegerSpecific revision to quote

Response: 200 OK

{
  "id": 50001,
  "projectId": 12345,
  "revisionId": 99001,
  "revisionNumber": 3,
  "createdAt": "2025-03-15T10:00:00Z",
  "expiresAt": "2025-03-15T11:00:00Z",
  "offers": [
    {
      "quantity": 10,
      "leadTimeDays": 10,
      "estimatedShipDate": "2025-03-28T00:00:00Z",
      "boardPrice": 230.00,
      "partsPrice": 310.50,
      "assemblyPrice": 250.00,
      "totalPrice": 790.50,
      "unitPrice": 79.05,
      "currency": "USD"
    }
  ]
}

Quote fields:

FieldTypeDescription
idintegerQuote ID
projectIdintegerProject this quote is for
revisionIdintegerRevision used for pricing
revisionNumberintegerHuman-readable revision number
createdAtstringISO 8601 creation timestamp
expiresAtstringISO 8601 expiry — quotes are valid for one hour
offersarrayPricing options at different quantities and lead times

Offer fields:

FieldTypeDescription
quantityintegerNumber of assembled boards
leadTimeDaysintegerBusiness days from order to shipment
estimatedShipDatestringISO 8601 estimated ship date
boardPricenumberBare PCB fabrication cost (USD)
partsPricenumberElectronic components cost (USD)
assemblyPricenumberAssembly labor cost (USD)
totalPricenumberTotal cost (USD)
unitPricenumberPer-board cost (USD)
currencystringAlways "USD"

GET /quotes/:quoteId/breakdown

Get a detailed cost breakdown for a specific quantity and lead time.

ParameterInTypeDescription
quantityqueryintegerRequired. Board quantity matching an offer
leadTimeDaysqueryintegerRequired. Lead time matching an offer

Response: 200 OK

{
  "quoteId": 50001,
  "quantity": 10,
  "leadTimeDays": 10,
  "total": 790.50,
  "currency": "USD",
  "lines": [
    {
      "type": "part",
      "description": "100nF 0402",
      "bomLineId": 1001,
      "resolvedPartMpn": "GCM155R71C104KA55D",
      "manufacturer": "Murata",
      "supplier": "Digi-Key",
      "orderQuantity": 44,
      "unitPrice": 0.02,
      "lineTotal": 0.88
    }
  ]
}

Line item fields:

FieldTypeDescription
typestringLine type — "part" for BOM components, other types may be added
descriptionstringHuman-readable description
bomLineIdinteger | nullBOM line ID (present when type is "part")
resolvedPartMpnstring | nullSelected manufacturer part number (present when type is "part")
manufacturerstring | nullPart manufacturer (present when type is "part")
supplierstring | nullDistributor (e.g. "Digi-Key", "Mouser")
orderQuantityintegerUnits ordered (includes attrition and price-break rounding)
unitPricenumberCost per unit (USD)
lineTotalnumberTotal cost for this line (USD)

Orders

GET /orders

List orders for a workspace.

ParameterInTypeDescription
workspacequerystringRequired. Workspace slug

Response: 200 OK

{
  "orders": [
    {
      "id": 8001,
      "urn": "urn:circuithub:order:8001",
      "name": "ORD-8001",
      "status": "in_progress",
      "quantity": 10,
      "createdAt": "2025-03-01T14:00:00Z",
      "updatedAt": "2025-03-02T11:00:00Z",
      "quotedShippingDate": "2025-03-15T00:00:00Z",
      "estimatedShippingDate": "2025-03-14T00:00:00Z",
      "shippedDate": null,
      "trackingNumber": null,
      "shippingService": null,
      "value": 790.50,
      "paymentStatus": "paid",
      "poNumber": "PO-2025-042",
      "projectId": 12345,
      "projectName": "Power Supply Board",
      "revisionId": 99001,
      "revisionNumber": 3
    }
  ]
}

Order fields:

FieldTypeDescription
idintegerOrder ID
urnstringOrder URN
namestringOrder name/number
statusstringOrder status (e.g. in_progress, completed)
quantityintegerNumber of boards ordered
createdAtstringISO 8601 creation timestamp
updatedAtstringISO 8601 last update timestamp
quotedShippingDatestringQuoted shipping date, ISO 8601
estimatedShippingDatestring | nullEstimated shipping date, ISO 8601
shippedDatestring | nullActual ship date, ISO 8601
trackingNumberstring | nullShipment tracking number
shippingServicestring | nullShipping carrier/service
valuenumberOrder value in USD
paymentStatusstringPayment status
poNumberstringPurchase order number
projectIdintegerProject this order belongs to
projectNamestringProject name
revisionIdintegerProject revision used for this order
revisionNumberintegerHuman-readable revision number

GET /orders/:orderId

Get a single order by ID.

Response: 200 OK

{
  "id": 8001,
  "urn": "urn:circuithub:order:8001",
  "name": "ORD-8001",
  "status": "in_progress",
  "quantity": 10,
  "createdAt": "2025-03-01T14:00:00Z",
  "updatedAt": "2025-03-02T11:00:00Z",
  "quotedShippingDate": "2025-03-15T00:00:00Z",
  "estimatedShippingDate": "2025-03-14T00:00:00Z",
  "shippedDate": null,
  "trackingNumber": null,
  "shippingService": null,
  "value": 790.50,
  "paymentStatus": "paid",
  "poNumber": "PO-2025-042",
  "projectId": 12345,
  "projectName": "Power Supply Board",
  "revisionId": 99001,
  "revisionNumber": 3
}

Parts

GET /parts

Search for parts by MPN or description.

ParameterInTypeDescription
qquerystringRequired. Search query

Response: 200 OK

{
  "query": "LM358",
  "exactMatches": [
    {
      "id": 5001,
      "mpn": "LM358DR",
      "manufacturer": "Texas Instruments",
      "description": "Dual Operational Amplifier",
      "available": 12500,
      "moq": 1
    }
  ],
  "inexactMatches": [
    {
      "id": 5010,
      "mpn": "LM358ADR",
      "manufacturer": "Texas Instruments",
      "description": "Dual Op-Amp, Improved",
      "available": 4200,
      "moq": 1
    }
  ]
}
FieldTypeDescription
querystringOriginal search query
exactMatchesarrayExact MPN matches (case-insensitive)
inexactMatchesarrayFuzzy/related matches

Part fields:

FieldTypeDescription
idintegerPart ID
mpnstringManufacturer part number
manufacturerstringManufacturer name
descriptionstring | nullPart description
availableintegerCurrent stock availability
moqinteger | nullMinimum order quantity

Issues

Issues are coming soon. The endpoints below describe the planned API surface — they are not yet available.

GET /issues

List issues for a workspace.

ParameterInTypeDescription
workspacequerystringRequired. Workspace slug
orderqueryintegerFilter by order ID (optional)
statusquerystringFilter by status: open, closed (optional)

POST /orders/:orderId/issues

Create a new issue on an order.

ParameterInTypeDescription
orderIdpathintegerRequired. Order ID to file the issue against
FieldInTypeDescription
titlebodystringRequired. Short summary of the issue
commentbodystringOptional initial comment attached to the issue

See the Issues guide for full examples and response shapes.


Billing

Billing is coming soon. The endpoints below describe the planned API surface — they are not yet available.

All billing endpoints are scoped to a workspace.

GET /workspaces/:workspaceSlug/billing/invoices

List invoices for a workspace.

ParameterInTypeDescription
statusquerystringFilter: open, paid_in_full, approved_for_posting, fully_applied (optional)
fromquerystringStart date, ISO 8601 (optional)
toquerystringEnd date, ISO 8601 (optional)

Response: 200 OK

{
  "invoices": [
    {
      "id": 50124,
      "name": "INV-4835",
      "type": "charge",
      "recordType": "invoice",
      "orderId": 8001,
      "transactionDate": "2025-03-01T00:00:00Z",
      "dueDate": "2025-03-20T00:00:00Z",
      "totalNoTax": 80.00,
      "tax": 5.00,
      "total": 85.00,
      "amountPaid": 0.00,
      "amountUnpaid": 85.00,
      "status": "open",
      "description": "Additional testing services"
    }
  ]
}

GET /workspaces/:workspaceSlug/billing/invoices/:invoiceId

Get a single invoice by ID.

Response: 200 OK

{
  "id": 50124,
  "name": "INV-4835",
  "type": "charge",
  "recordType": "invoice",
  "orderId": 8001,
  "transactionDate": "2025-03-01T00:00:00Z",
  "dueDate": "2025-03-20T00:00:00Z",
  "totalNoTax": 80.00,
  "tax": 5.00,
  "total": 85.00,
  "amountPaid": 0.00,
  "amountUnpaid": 85.00,
  "status": "open",
  "description": "Additional testing services"
}

GET /workspaces/:workspaceSlug/billing/invoices/:invoiceId/pdf

Download an invoice as PDF.

Response: 200 OK with Content-Type: application/pdf

GET /workspaces/:workspaceSlug/billing/statement

Generate a PDF statement for a date range.

ParameterInTypeDescription
startDatequerystringRequired. Start date, ISO 8601
endDatequerystringRequired. End date, ISO 8601

Response: 200 OK with Content-Type: application/pdf

GET /workspaces/:workspaceSlug/billing/payments

List payments for a workspace. Includes both applied NetSuite payments and pending Stripe payments.

Response: 200 OK

{
  "payments": [
    {
      "id": 60001,
      "name": "PMT-1192",
      "date": "2025-02-16T00:00:00Z",
      "amount": 1240.00,
      "method": "card",
      "invoices": ["INV-4821"],
      "orders": [{ "id": 8001, "projectName": "Power Supply Board" }],
      "status": "applied"
    }
  ]
}

GET /workspaces/:workspaceSlug/billing/payment-methods

List payment methods configured for a workspace.

Response: 200 OK

{
  "paymentMethods": [
    {
      "id": "pm_abc123",
      "type": "card",
      "brand": "visa",
      "last4": "4242",
      "isDefault": true
    },
    {
      "id": "net_301",
      "type": "net_terms",
      "terms": "net_30",
      "isDefault": false
    }
  ]
}

GET /workspaces/:workspaceSlug/billing/balance

Get credit balance and limit for a workspace. Returns null if no credit is enabled.

Response: 200 OK

{
  "balance": 2450.00,
  "creditLimit": 10000.00
}

Error responses

All endpoints return errors in this format:

{
  "error": "Project not found",
  "code": "NOT_FOUND"
}
CodeMeaning
NOT_FOUNDRequested resource does not exist
VALIDATION_ERRORRequest payload or query parameters were invalid
INVALID_REDIRECT_URIredirect_uri is not an absolute localhost URL
FILE_TOO_LARGEUpload exceeded the maximum allowed size
INVALID_UPLOAD_SIZEUpload size was invalid
INVALID_OBJECT_KEYUploaded object key did not match the workspace upload prefix
FORBIDDENAuthenticated user does not have permission to access the resource
UNAUTHORIZEDMissing or invalid authentication
RATE_LIMITEDToo many requests
CREATION_FAILEDProject or revision creation failed after validation
INTERNAL_ERRORUnexpected server error

On this page