HomeDocsChangelog
Documentation

CADLens API

The CADLens API turns DWG, DXF, and DWF files into structured JSON your code can render, diff, and route.

This is a REST API. Authenticate with a bearer API key, upload a file, poll the job until it finishes (or wait for a webhook), then read a JSON document with vector entities, layers, drawing metadata, and a rendered PNG preview.

All endpoints live under the /v1 path prefix. Stable formats: DWG, DXF, DWF. Beta formats: PDF, DWFx, DGN (V7). File size limit is 10 MB on the free plan, up to 100 MB on paid plans. The file type is verified by magic bytes, not the extension.

How it works#

  1. Upload. POST a file to /v1/parse. You get a job back with status PENDING.
  2. Wait. Either poll /v1/jobs/{jobId} or register a webhook.
  3. Read. When status is COMPLETED, GET /v1/jobs/{jobId}/result for the vector JSON and preview.
i
No SDK required. CADLens is HTTP and JSON. The examples on this page are curl, Node 18+ fetch, Python, and Go; port them to whatever stack you run.
Base URL
https://api.cadlens.co/v1
Authentication
Authorization: Bearer cadl_...

Quickstart#

From zero to your first vectorized response in about two minutes. No SDK to install.

1. Get an API key#

Request beta access at cadlens.co. Once approved (within 48 h), sign in to the dashboard to create and copy your cadl_… key — the full key is shown only once. Stash it in your environment as CADLENS_KEY. See Authentication for the rules.

2. POST your file#

Multipart upload to /v1/parse with a file field (up to your plan limit — 10 MB on free). The response is a job — in the default async mode its status is PENDING until the worker picks it up.

3. Wait for the result#

Either poll GET /v1/jobs/{jobId} until status === "COMPLETED", or skip polling and use webhooks. Then GET /v1/jobs/{jobId}/result to read the parsed JSON and preview URL.

!
Upload limit is 10 MB on the free plan (higher on paid plans). Accepted formats: DWG, DXF, DWF (stable) and PDF, DWFx, DGN V7 (beta). See errors for the codes returned when a file is rejected.
Set your key
export CADLENS_KEY="cadl_••••••••••••••••••••••••••••••••••••••••••••••••"
Send a file
curl -X POST https://api.cadlens.co/v1/parse \
  -H "Authorization: Bearer $CADLENS_KEY" \
  -F "[email protected]" \
  -F "mode=async"
Wait & read
curl https://api.cadlens.co/v1/jobs/job_lH9k2c \
  -H "Authorization: Bearer $CADLENS_KEY"

Authentication#

Every request to the parse and jobs endpoints uses a bearer API key in the Authorization header:

Authorization: Bearer cadl_<48 hex characters>

Keys are created in the dashboard and start with the prefix cadl_. The full key is shown once at creation — store it securely; only the first few characters (the key prefix) are visible afterwards. Revoking a key in the dashboard takes effect immediately. A request with a missing, malformed, revoked, or expired key returns 401.

!
Never call CADLens from a browser. Anything you POST from fetch() in the front-end leaks your key. Proxy through your backend.
Authenticated request
curl https://api.cadlens.co/v1/jobs/job_lH9k2c \
  -H "Authorization: Bearer cadl_•••••"

Parse a file#

POST/v1/parse

Upload a CAD file as multipart/form-data. Every parse returns both vector JSON and a rendered PNG preview. In the default async mode the response is 202 with a job_id and status PENDING — poll GET /v1/jobs/{jobId} or use a webhook until the job is COMPLETED.

Accepted formats: DWG, DXF, DWF (stable) and PDF, DWFx, DGN V7 (beta). Maximum file size depends on your plan — 10 MB on the free tier, up to 100 MB on paid plans. The type is verified by magic bytes, not the file extension.

Body#

FieldTypeDescription
file
required
fileMultipart file field. The CAD drawing to parse.
webhookUrl
optional
string<url>Per-call webhook to POST when the job completes or fails. Overrides any saved endpoint.
mode
optional
enum`async` returns 202 + job_id immediately. `sync` holds the request until the worker finishes (or returns 202 on timeout). · default async
Request
curl -X POST https://api.cadlens.co/v1/parse \
  -H "Authorization: Bearer $CADLENS_KEY" \
  -F "[email protected]" \
  -F "mode=async"
Response
{
  "job_id": "job_lH9k2c",
  "status": "PENDING",
  "fileName": "floor-plan.dwg",
  "fileSize": 184320,
  "createdAt": "2026-05-21T10:42:11.000Z"
}

Get a job#

GET/v1/jobs/{jobId}

Read the current status of a job. Idempotent and not counted against your quota — safe to poll. status is one of PENDING, PROCESSING, COMPLETED, or FAILED. imageUrl is a signed PNG URL once COMPLETED, otherwise null.

Path parameters#

FieldTypeDescription
jobId
required
stringJob ID returned from `/v1/parse` (prefix `job_`).
Request
curl https://api.cadlens.co/v1/jobs/job_lH9k2c \
  -H "Authorization: Bearer $CADLENS_KEY"
Response
{
  "id": "job_lH9k2c",
  "uuid": "job_lH9k2c",
  "status": "COMPLETED",
  "fileName": "floor-plan.dwg",
  "fileSize": 184320,
  "createdAt": "2026-05-21T10:42:11.000Z",
  "startedAt": "2026-05-21T10:42:12.000Z",
  "completedAt": "2026-05-21T10:42:19.000Z",
  "errorMsg": null,
  "imageUrl": "https://s3.amazonaws.com/cadlens-object/previews/job_lH9k2c.png?X-Amz-Expires=3600&..."
}

Get the result#

GET/v1/jobs/{jobId}/result

Returns the parsed document: vectorJson (a flat array of entities), layersJson, metadata, and a signed imageUrl. Available only once the job is COMPLETED — calling it earlier returns 400 "Job result is not yet available".

Path parameters#

FieldTypeDescription
jobId
required
stringJob ID returned from `/v1/parse` (prefix `job_`).
Request
curl https://api.cadlens.co/v1/jobs/job_lH9k2c/result \
  -H "Authorization: Bearer $CADLENS_KEY"
Response
{
  "jobId": "job_lH9k2c",
  "status": "COMPLETED",
  "metadata": {
    "filename": "floor-plan.dwg",
    "format": "DWG",
    "dwgVersion": "AC1032",
    "units": "mm",
    "boundingBox": {
      "minX": 0, "minY": 0, "maxX": 12000, "maxY": 7200,
      "width": 12000, "height": 7200
    }
  },
  "layersJson": [
    { "name": "WALL", "color": 7, "colorHex": "#FFFFFF", "lineType": "CONTINUOUS", "isVisible": true, "entityCount": 412 },
    { "name": "DOOR", "color": 3, "colorHex": "#00FF00", "lineType": "CONTINUOUS", "isVisible": true, "entityCount": 38 }
  ],
  "vectorJson": [
    { "type": "LINE", "id": "1A4", "layer": "WALL", "start": { "x": 0, "y": 0 }, "end": { "x": 12000, "y": 0 }, "colorIndex": 7 },
    { "type": "LWPOLYLINE", "id": "1B2", "layer": "WALL", "vertices": [{ "x": 0, "y": 0 }, { "x": 12000, "y": 0 }, { "x": 12000, "y": 7200 }], "closed": true, "colorIndex": 7 }
  ],
  "imageUrl": "https://s3.amazonaws.com/cadlens-object/previews/job_lH9k2c.png?X-Amz-Expires=3600&...",
  "createdAt": "2026-05-21T10:42:19.000Z"
}

Get the preview image#

GET/v1/jobs/{jobId}/image

Returns a JSON object with imageUrl — a signed S3 URL to the rendered PNG preview, valid for one hour. The same URL is also included in the job and result responses. Available only once the job is COMPLETED.

Path parameters#

FieldTypeDescription
jobId
required
stringJob ID returned from `/v1/parse` (prefix `job_`).
Request
curl https://api.cadlens.co/v1/jobs/job_lH9k2c/image \
  -H "Authorization: Bearer $CADLENS_KEY"
Response
{
  "imageUrl": "https://s3.amazonaws.com/cadlens-object/previews/job_lH9k2c.png?X-Amz-Expires=3600&..."
}

List jobs#

GET/v1/jobs

Returns the jobs created by the calling API key — up to the 100 most recent, newest first. Takes no query parameters.

Request
curl https://api.cadlens.co/v1/jobs \
  -H "Authorization: Bearer $CADLENS_KEY"
Response
{
  "jobs": [
    {
      "id": "job_lH9k2c",
      "uuid": "job_lH9k2c",
      "status": "COMPLETED",
      "fileName": "floor-plan.dwg",
      "fileSize": 184320,
      "createdAt": "2026-05-21T10:42:11.000Z",
      "startedAt": "2026-05-21T10:42:12.000Z",
      "completedAt": "2026-05-21T10:42:19.000Z",
      "errorMsg": null,
      "imageUrl": "https://s3.amazonaws.com/.../job_lH9k2c.png?X-Amz-Expires=3600&..."
    }
  ]
}

Delete a job#

DELETE/v1/jobs/{jobId}

Permanently deletes the job and its stored artifacts (vector JSON and the rendered PNG). Returns 204 No Content on success, or 404 if the job does not exist for your API key. This cannot be undone.

Path parameters#

FieldTypeDescription
jobId
required
stringJob ID returned from `/v1/parse` (prefix `job_`).
Request
curl -X DELETE https://api.cadlens.co/v1/jobs/job_lH9k2c \
  -H "Authorization: Bearer $CADLENS_KEY"
Response
(empty body)

API Playground#

A worked example of a single parse, end to end. Follow the three steps alongside — upload a file, poll the job until it's COMPLETED, then read the result.

  1. Upload. POST /v1/parse with your file returns a job_id and status PENDING.
  2. Poll. GET /v1/jobs/{jobId} until the status is COMPLETED (or FAILED).
  3. Read. GET /v1/jobs/{jobId}/result returns the vector JSON, layers, metadata, and a preview URL.
i
This is a read-only example. To run requests interactively against your own account, open the Playground in your dashboard after creating an API key.
1 · Upload
curl -X POST https://api.cadlens.co/v1/parse \
  -H "Authorization: Bearer $CADLENS_KEY" \
  -F "[email protected]" \
  -F "mode=async"
{
  "job_id": "job_lH9k2c",
  "status": "PENDING",
  "fileName": "floor-plan.dwg",
  "fileSize": 184320,
  "createdAt": "2026-05-21T10:42:11.000Z"
}
2 · Poll until done
curl https://api.cadlens.co/v1/jobs/job_lH9k2c \
  -H "Authorization: Bearer $CADLENS_KEY"
{
  "id": "job_lH9k2c",
  "uuid": "job_lH9k2c",
  "status": "COMPLETED",
  "fileName": "floor-plan.dwg",
  "fileSize": 184320,
  "createdAt": "2026-05-21T10:42:11.000Z",
  "startedAt": "2026-05-21T10:42:12.000Z",
  "completedAt": "2026-05-21T10:42:19.000Z",
  "errorMsg": null,
  "imageUrl": "https://s3.amazonaws.com/cadlens-object/previews/job_lH9k2c.png?X-Amz-Expires=3600&..."
}
3 · Read the result
curl https://api.cadlens.co/v1/jobs/job_lH9k2c/result \
  -H "Authorization: Bearer $CADLENS_KEY"
{
  "jobId": "job_lH9k2c",
  "status": "COMPLETED",
  "metadata": {
    "filename": "floor-plan.dwg",
    "format": "DWG",
    "dwgVersion": "AC1032",
    "units": "mm",
    "boundingBox": {
      "minX": 0, "minY": 0, "maxX": 12000, "maxY": 7200,
      "width": 12000, "height": 7200
    }
  },
  "layersJson": [
    { "name": "WALL", "color": 7, "colorHex": "#FFFFFF", "lineType": "CONTINUOUS", "isVisible": true, "entityCount": 412 },
    { "name": "DOOR", "color": 3, "colorHex": "#00FF00", "lineType": "CONTINUOUS", "isVisible": true, "entityCount": 38 }
  ],
  "vectorJson": [
    { "type": "LINE", "id": "1A4", "layer": "WALL", "start": { "x": 0, "y": 0 }, "end": { "x": 12000, "y": 0 }, "colorIndex": 7 },
    { "type": "LWPOLYLINE", "id": "1B2", "layer": "WALL", "vertices": [{ "x": 0, "y": 0 }, { "x": 12000, "y": 0 }, { "x": 12000, "y": 7200 }], "closed": true, "colorIndex": 7 }
  ],
  "imageUrl": "https://s3.amazonaws.com/cadlens-object/previews/job_lH9k2c.png?X-Amz-Expires=3600&...",
  "createdAt": "2026-05-21T10:42:19.000Z"
}

Jobs & polling#

Parsing is asynchronous. Every POST /v1/parse returns a job with a status that walks one of two paths:

PENDING  →  PROCESSING  →  COMPLETED  ┐
                                      ├─ terminal
PENDING  →  PROCESSING  →  FAILED     ┘

Polling. Call GET /v1/jobs/{jobId} until status is COMPLETED or FAILED. Use exponential backoff starting around 250ms and capped at ~4s — the example alongside does this. Polling does not count against your usage quota. Prefer a webhook if you'd rather not poll.

Glossary#

Job
A single parse request. Created by POST /v1/parse and identified by a job_ ID.
Status
One of PENDING, PROCESSING, COMPLETED, or FAILED.
Terminal state
A status that no longer changes — COMPLETED or FAILED. Stop polling once reached.
Polling
Repeatedly checking a job's status until it reaches a terminal state.
Webhook
An alternative to polling: CADLens calls your URL when the job progresses. See Webhooks.
Preview
A rendered PNG of the drawing, available via a signed URL once the job is COMPLETED.

For the exact fields of a job and result object, see the Get job and Get result reference.

Poll a job
curl https://api.cadlens.co/v1/jobs/job_lH9k2c \
  -H "Authorization: Bearer $CADLENS_KEY"

Result schema#

When a job is COMPLETED, its result describes the drawing in four parts: the vectors (every geometric entity), the layers they belong to, file-level metadata, and a rendered preview image. The shape alongside shows the top-level keys.

Glossary#

Vector JSON
The list of geometric entities (lines, arcs, text, blocks…) extracted from the drawing. See Vector entity types.
Layer
A named group entities belong to, with a colour, line type, and visibility — mirroring the source CAD layers.
Metadata
Drawing-level information: file name, source format, drawing units, and overall extent.
Bounding box
The rectangular extent of the drawing (its overall width and height in drawing units).
Units
The drawing's measurement unit — millimetres, centimetres, metres, inches, feet, or unknown.
Preview image
A rendered PNG of the drawing, returned as a signed URL valid for one hour.

For the exact field names and types, see the Get result reference.

Result, at a glance
{
  "jobId": "job_...",
  "status": "COMPLETED",
  "vectorJson": [ /* drawing entities */ ],
  "layersJson": [ /* layers */ ],
  "metadata": { /* file & drawing info */ },
  "imageUrl": "https://.../preview.png",
  "createdAt": "..."
}

Vector entity types#

The vectorJson array holds the drawing's geometry. Every entity records the layer it belongs to, its colour, and a small set of points describing its shape — all coordinates are 2D points in the drawing's own units. The entity type mirrors the original CAD object. The common types you'll encounter:

Glossary#

Line
A straight segment between two points.
Polyline
A connected sequence of points forming an open or closed shape; segments can bulge into arcs.
Arc
A portion of a circle, defined by a centre, radius, and start/end angles.
Circle
A full circle, defined by a centre and radius.
Ellipse
An oval, defined by a centre, major axis, and axis ratio.
Spline
A smooth curve through a set of control points (NURBS).
Text
Single-line or multi-line annotation, with its position and size.
Block insert
A placed reference to a reusable, named group of geometry (a “block”).

For each type's exact fields, see the example responses in the Get result reference.

Example entity (line)
{
  "type": "LINE",
  "id": "1A4",
  "layer": "WALL",
  "start": { "x": 0, "y": 0 },
  "end": { "x": 12000, "y": 0 },
  "colorIndex": 7
}
Example entity (polyline)
{
  "type": "LWPOLYLINE",
  "id": "1B2",
  "layer": "WALL",
  "vertices": [
    { "x": 0, "y": 0 },
    { "x": 12000, "y": 0 }
  ],
  "closed": true
}

Webhooks#

Skip polling. Pass webhookUrl when you call /v1/parse, or save an endpoint in the dashboard (up to two per account). CADLens then POSTs a JSON event as the job progresses and finishes. The request carries User-Agent: CADLens-Webhook/1.0 and a JSON body.

Events#

job.processing
The worker has started parsing the file.
job.completed
The result is ready — the event carries entity/layer counts, the preview URL, and the parsed JSON.
job.failed
The parse failed — the event carries the failure reason.

Delivery#

Your endpoint should respond 2xx within 10 seconds. A non-2xx (or timeout) is retried up to 3 times with exponential backoff (~2s, 4s, 8s, capped at 30s); after that the delivery is recorded as exhausted.

Verifying signatures#

Deliveries to a saved endpoint are signed. Each request carries X-CADLens-Signature: t=<ts>,v1=<hmac> and a X-CADLens-Timestamp header. Recompute the HMAC-SHA256 of <timestamp>.<raw request body> using your endpoint's signing secret (shown once when you create the endpoint) and compare it to the v1 value; reject requests whose timestamp is too old to stop replays.

i
As a belt-and-suspenders practice, on job.completed re-fetch GET /v1/jobs/{jobId}/result with your API key to read the authoritative result.
Event payload
{
  "eventId": "5f9c2b1e-7d3a-4e2f-9a1b-0c8d6e4f2a10",
  "sequence": 1,
  "event": "job.completed",
  "jobId": "job_lH9k2c",
  "status": "COMPLETED",
  "timestamp": "2026-05-21T10:42:19.000Z",
  "result": {
    "entityCount": 1284,
    "layerCount": 8,
    "imageUrl": "https://s3.amazonaws.com/cadlens-object/previews/job_lH9k2c.png?X-Amz-Expires=86400&...",
    "vectorJson": [],
    "layersJson": [],
    "metadata": {}
  }
}
Receive the event
<?php
// Verify the signature, ack fast, then process.
$body   = file_get_contents('php://input');
$secret = getenv('CADLENS_WEBHOOK_SECRET'); // shown once when you create the endpoint

// Header: "X-CADLens-Signature: t=<ts>,v1=<hmac>"
$header = $_SERVER['HTTP_X_CADLENS_SIGNATURE'] ?? '';
parse_str(strtr($header, ',', '&'), $sig);
$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, $secret);

if (!hash_equals($expected, $sig['v1'] ?? '')) {
    http_response_code(400); // bad signature
    exit;
}

http_response_code(200); // ack fast — CADLens retries non-2xx up to 3 times

$event = json_decode($body, true);
if ($event['event'] === 'job.completed') {
    // Re-fetch GET /v1/jobs/{jobId}/result with your API key for the data.
    error_log('Parse done: ' . $event['jobId'] . ' ' . $event['status']);
}

Errors#

Errors return a flat JSON object with a human-readable error message and, sometimes, a programmatic code. Request-validation failures use a slightly richer shape — { "error": "Validation error", "details": { fieldErrors, formErrors } }. The HTTP status carries the category.

HTTP status codes#

CodeNameMeaning
200OKSuccessful request (or sync parse completed).
202AcceptedJob created/pending. Returned by `/v1/parse`.
400Bad RequestValidation error, unsupported/invalid file, or result not ready yet.
401UnauthorizedMissing, malformed, revoked, or expired API key.
403ForbiddenRequest not permitted (e.g. playground origin check).
404Not FoundJob does not exist for this key.
409ConflictState conflict, e.g. an API key that is already revoked.
412Precondition FailedA prerequisite is missing, e.g. `NO_ACTIVE_API_KEY`.
413Payload Too LargeFile exceeds the 100 MB limit.
422UnprocessableFile was accepted but could not be parsed.
429Too Many RequestsMonthly plan quota reached.
500Internal ErrorUnexpected server error.
503Service UnavailableA dependency (e.g. Stripe webhooks) is not configured/available.

Common errors#

FieldTypeDescription
401
optional
`Invalid, revoked, or expired API key`
400
optional
`No file provided. Upload a file with field name "file".`
400
optional
`File header does not match a supported CAD format. Allowed: DWG/DXF/DWF/DGN/DWFX/PDF`
400
optional
`DGN V8 files are not supported. Only Bentley DGN V7 is currently accepted.`
400
optional
`Job result is not yet available` — the job is not COMPLETED yet.
413
optional
`File too large. Maximum size is 100 MB.`
429
optional
`Monthly limit of N requests reached for PLAN plan`
Error envelope
{
  "error": "File too large. Maximum size is 100 MB."
}
Catching errors
const r = await fetch(url, { headers: auth });

if (!r.ok) {
  const body = await r.json();
  console.error(
    `HTTP ${r.status}`,
    body.error,   // human-readable message
    body.code,    // optional programmatic code, e.g. "NO_ACTIVE_API_KEY"
  );
  return;
}

const job = await r.json();

Usage & quotas#

Usage is metered as a monthly quota per plan. Each successful parse counts as one request; failed parses and read calls (get job, result, image, list) do not count. The counter resets at the start of each calendar month (UTC).

FieldTypeDescription
Free
optional
50 / moTrial and hobby use.
Starter
optional
500 / moSmall projects.
Growth
optional
2,000 / moEarly production.
Pro
optional
10,000 / moHigher-volume apps.
Business
optional
40,000 / moHigh volume.
Enterprise
optional
UnlimitedNo monthly cap.

Once you hit your plan's limit, POST /v1/parse returns 429 Too Many Requests with a message like Monthly limit of N requests reached for PLAN plan until the next monthly reset or a plan upgrade.

Need more? Upgrade your plan in the dashboard, or email [email protected] for Enterprise.

Quota exceeded
{
  "error": "Monthly limit of 50 requests reached for FREE plan"
}

Changelog#

Notable changes to the API.

2026-05
schema
Result documents return vectorJson (typed entities), layersJson with per-layer counts, drawing metadata with a boundingBox, and a signed PNG preview URL.
2026-05
added
Webhooks: job.processing, job.completed, and job.failed events, delivered to a per-parse webhookUrl or a saved endpoint.
2026-05
added
Format support: DWG, DXF, DWF, DWFx, Bentley DGN V7, and CAD-bearing PDF, validated by magic bytes up to 100 MB.
Base URL
https://api.cadlens.co/v1