API and ERP integration

Your price list from your ERP, the enquiries back into it.Nothing typed twice.

MetronQ has an API for the system that already runs your shop: an ERP, a CRM, a spreadsheet or an automation tool. Material prices, machines, tools and part files come from it, and quotes, part readouts and orders go back. Every change to the price list goes through a preview before anything is written.

  • REST and JSON
  • 7 permission scopes
  • Preview before write
  • Limits in every response
Your ERPERP · CRM · spreadsheet · automation
  1. Material pricesPOST /config/import
  2. Tool cribPUT /machines/{key}/tools
  3. Files to quotePOST /quotes
  4. New enquiriesGET /quotes?since=
  5. Part readoutGET /quotes/{id}/metrics
  6. Job statusPATCH /orders/{id}
price list · quotes · orders

01Benefits

What stops being done by hand

Six things somebody in a shop copies from one system into another today. With the API the system does it, and a person looks at the difference.

Material prices

TodaySomebody types prices off the wholesaler's invoice into the ERP, then again into the quoting system. When they forget, quotes go out on the old price.
With the APIThe ERP sends the new prices per kilogram. MetronQ shows the difference field by field, and nothing is written until after the preview.

Tool crib and machine park

TodayThe list of cutters and drills lives in a spreadsheet, and the quoting system assumes generic tooling.
With the APIThe tool-management system sends each machine's tool library. The smallest cutter at once sets the smallest inside corner that machine will quote.

New enquiries

TodaySomebody re-enters an enquiry from the widget into the ERP as an offer.
With the APIEvery minute the ERP asks for enquiries newer than the last one it holds, and creates them with the file, material, quantity and price.

Files to quote

TodayA customer e-mails a model or a drawing, and somebody downloads the attachment and creates the quote in the panel by hand.
With the APIThe system that already holds the file (PDM, mailbox, customer portal) sends it for quoting and receives the price, the cost breakdown and the quote PDF.

Production planning

TodayAn engineer opens the model again to count the holes and threads for the routing.
With the APIThe part readout - envelope, holes, pockets, threads, thinnest wall - reaches your system as data rather than as a picture.

Order status

TodayA job's status lives in two places, and the customer e-mails to ask about their parts.
With the APIThe ERP moves the order's status in MetronQ. Confirmation and shipping reach the customer in the same e-mail the panel sends.

02Data routes

The four routes built most often

Pick a route to see its stops and the call that matters most - with a real request and a real response.

Prices and catalogues flow from your system into MetronQ. Nothing is stored until a preview has shown the difference.

  1. Export from the ERP

    A CSV spreadsheet, or one JSON document with materials, machines and treatments.

    CSV · JSON
  2. Preview

    What would be added and changed, field by field, plus any rows and columns the system does not understand.

    POST /config/import/preview
  3. Apply

    The system sends back the preview's base_hash. If somebody saved the price list in between, it gets 409 and previews again.

    POST /config/import
  4. History

    The import lands in the settings history field by field, with the name of the key that made it.

    settings history
Request: preview a materials sheet
curl -X POST "https://app.metronq.com/api/v1/integration/config/import/preview" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "catalog": "materials", "csv_base64": "a2V5O25hbWU7cHJpY2VfcGVyX2tnCmFsdV82MDYxOy..." }'
Response: what would change200 OK
{
  "ok": true,
  "added": [],
  "updated": ["alu_6061", "steel_c45"],
  "errors": [],
  "ignored_columns": [],
  "changes": [
    {
      "section": "materials",
      "path": "price_per_kg",
      "kind": "edited",
      "default": 6.4,
      "current": 6.9,
      "entity_key": "alu_6061",
      "entity_name": "Aluminium 6061",
      "unit": "field"
    }
  ],
  "changes_total": 2,
  "base_hash": "9c1e4b0f2a7d",
  "config": { "materials": [ "..." ], "machines": [ "..." ], "...": "..." }
}

03Control

The key belongs to the shop, not to the integrator

You create a key in the settings, give it permissions, and see what was done with it and when.

A key is shown once, when it is created, and we do not store it in a readable form. A lost key is revoked and replaced. It belongs to the shop rather than to a person, so it keeps working while whoever created it is on holiday.

Permissions are narrow by design. A key that reads the catalogue cannot read enquiries, and a key that reads enquiries sees neither the customer's personal details nor the files they uploaded without scopes of their own. A key that sends files is not a key that reads quotes. No key can create or revoke another key.

Seven permission scopes

config:read
Read the catalogue and the quoting rules.
config:write
Change the catalogue and the rules, through the same preview and apply as the panel.
quotes:read
Enquiries and orders: the part, material, quantity, price and readout.
customers:read
The customer's contact details. Without this scope those fields come back empty.
quotes:write
Your number and the order status. It cannot price or approve anything.
quotes:create
Send part files for quoting and change a quote's parameters. The engine prices; the key cannot approve or give a discount.
files:read
The files customers uploaded and the 3D previews, together with quotes:read. Separate on purpose: a file is somebody's design.
  • A key expires 365 days after it is created
  • Revoking takes effect on the very next request
  • An access log kept for two years
  • The mq_live_ prefix for the secret scanner in your repository
app.metronq.com/settings/integrations
Settings
Find a parameter, e.g. margin, setup+ New quoteEN
CompanyNotificationsQuote documentTeamIntegrations
API keys

A key lets your ERP, CRM or automation tool work with this account without anybody signing in: read and update the catalogue and pricing rules, read quotes and orders, send part files for quoting and download files - each only with the permissions you give it.

+ New key

Allowance: 120 requests a minute and 20,000 a day, counted from 00:00 UTC. Used today: 212.

NamePermissionsLast usedExpiresDaily limit
ERP synca1b2c3d4...config:read, quotes:read, quotes:writetoday 09:1416/09/2027account allowanceRevoke
Price list importe5f6a7b8...config:read, config:writeyesterday 06:0002/10/2027account allowanceRevoke
Access log

When something gained or lost the ability to read this account. Kept for two years.

  • Catalogue importedPrice list import · e5f6a7b818/09/2026 06:00
  • Key createdERP sync · a1b2c3d416/09/2026 10:12
  • Key createdPrice list import · e5f6a7b802/10/2026 08:30

Settings, Integrations tab - example data

04Limits

Every response says how much is left

An integration knows where it stands before it meets a limit, not after.

API access and its limits are agreed with each shop. By default a shop gets 120 requests a minute and 20,000 a day, counted from 00:00 UTC; the figures agreed for an account can differ. Syncing the catalogue every hour and polling for new enquiries every minute fit well inside the defaults.

Headers on every response

HTTP
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 41
X-RateLimit-Quota: 20000
X-RateLimit-Quota-Remaining: 19863
X-RateLimit-Quota-Reset: 51240
An evaluation key

While an integration is being built, a shop can get a key with a budget for the whole test. The budget does not reset and shows in the panel, in whoami and in the headers. It is enough to build the connection, and deliberately not enough to run on.

What a refusal means

StatusCodeWhat to do
429rate_limitedThe minute is full. The same request works shortly; Retry-After says in how many seconds.
429daily_quota_exceededThe day is spent. Retry-After counts down to 00:00 UTC.
429total_quota_exceededThe evaluation budget is spent. No Retry-After, because waiting changes nothing.
401invalid_api_keyAn unknown, revoked or expired key, or an account whose API access is closed or whose trial, plan or licence has ended. One answer for all of them, on purpose.
403missing_scopeThe key is valid but lacks the permission this call needs. A key with the right scope will help.
404not_foundNo such enquiry or order in this account. A catalogue or machine that does not exist has a code of its own: unknown_catalog, unknown_machine.
404unknown_toolThe machine has no tool under this key. Nothing was removed, so a sync can tell a removal from a typo in its own mapping.
404file_not_foundThe stored file or preview could not be read. The quote itself exists; its bytes could not be fetched.
400invalid_base64The csv_base64 of an import preview is not valid base64. Nothing was read or written.
409config_changedSomebody saved the price list between the preview and the apply. Read, preview and apply again.
409not_analysedThe enquiry has no readout yet: its file is still being analysed, or the analysis failed (status analysis_failed).
403quoting_blocked:...The shop may not open new quotes right now. The reason follows the colon: trial_limit (the trial's quotes are used up), plan_limit (the plan's quotes for the month) or daily_limit (the shop's own daily cap).
429analysis_backlogFive files the shop sent through the API are already waiting for or in analysis (widget and panel quotes do not count). Retry-After: 30 seconds.
409file_not_uploadedThe upload did not land in storage. Send it again while the address is valid (15 minutes), then confirm; after that, open a new quote.
409upload_expiredConfirmed too late: from 45 minutes after the quote was opened (a 15-minute link plus 30 minutes of grace) the upload counts as abandoned. Open a new quote.
409quote_closedA person has approved or rejected the quote, or the customer ordered it. Its parameters no longer change.
409quote_in_reviewA technologist has started deciding and typed a price for the quote or one of its parts. A re-price would erase that work, so it is refused.
409not_a_drawingThe quote is not a drawing, so there is nothing to complete or hand to a person.
409quote_already_analyzedThe drawing is no longer waiting for input: it was rebuilt and priced, handed to the shop already, or is being analysed again.
422unknown_material, ...The request was refused and nothing was written. A known problem is one short code: unknown_material, unknown_treatment, unknown_tolerance_class, unknown_hole_fit, file_too_large, unsupported_file_type, implausible_scale and the like.
422detail: [...]The body does not match the schema: an unknown field, a value out of range, a control character in a file name. FastAPI's list comes back under detail, each entry with type, loc and msg. Nothing was written.

05Reference

Every call, each with an example

A base address, the key in a header and JSON both ways. Pick the language you are writing the integration in - every example switches with it.

Base addresshttps://app.metronq.com/api/v1/integrationHTTPS only
AuthenticationAuthorization: Bearer mq_live_...a key from the shop's settings
FormatJSONexcept CSV (.csv), PDF (/pdf), customer files (/file) and 3D previews (.glb)

Start

GET/whoamiPermission: none

Who this key is

Which shop the key belongs to, what it may do and what it has left. The first call worth writing: if it works, the rest is paths.

Request
curl "https://app.metronq.com/api/v1/integration/whoami" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "tenant": { "name": "Precision Works Ltd", "slug": "precision-works", "currency": "EUR" },
  "key": {
    "name": "ERP sync",
    "prefix": "a1b2c3d4",
    "scopes": ["config:read", "quotes:read"],
    "expires_at": "2027-09-16T10:12:00+00:00"
  },
  "rate_limit": {
    "requests_per_minute": 120,
    "requests_per_day": 20000,
    "requests_total": null,
    "requests_total_used": null,
    "requests_total_remaining": null,
    "day_resets": "00:00 UTC"
  }
}

Catalogue

GET/configPermission: config:read

The whole price list

The whole pricing document: three catalogues, every parameter, the gates, the machine and blank strategies, and the currency. The import takes the same document back.

Request
curl "https://app.metronq.com/api/v1/integration/config" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "materials": [ { "key": "alu_6061", "price_per_kg": 6.4, "...": "..." } ],
  "machines": [ { "key": "dmu50", "hourly_rate": 95.0, "...": "..." } ],
  "treatments": [ { "key": "anodising", "...": "..." } ],
  "params": { "margin_pct": 22.0, "...": "..." },
  "gates": { "mode": "review_all", "...": "..." },
  "machine_selection": "cheapest",
  "stock_selection": "cheapest",
  "currency": "EUR"
}
GET/catalog/materialsPermission: config:read

One catalogue

Materials, machines or treatments as JSON, without walking the whole price list. Machines carry their tool library and their identification block.

Request
curl "https://app.metronq.com/api/v1/integration/catalog/materials" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "catalog": "materials",
  "count": 12,
  "items": [
    {
      "key": "alu_6061",
      "name": "Aluminium 6061",
      "kind": "metal",
      "en_number": "EN AW-6061",
      "din_number": "3.3211",
      "density_g_cm3": 2.7,
      "price_per_kg": 6.4,
      "mrr_rough_cm3_min": 50.0,
      "mrr_turning_cm3_min": 60.0,
      "finishing_rate_cm2_min": 90.0,
      "stock_allowance_mm": 3.0,
      "cutting_speed_m_min": 250.0
    }
  ]
}
GET/catalog/machines.csvPermission: config:read

A catalogue as a spreadsheet

The same catalogue as the CSV the panel's Export button produces: a BOM for Excel and the shop's own separator.

Request
curl "https://app.metronq.com/api/v1/integration/catalog/machines.csv" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
key;name;technology;hourly_rate;min_quantity;envelope_mm.1;envelope_mm.2;...
dmu50;DMU 50;milling;95,00;1;650,0;520,0;...
nlx2500;NLX 2500;turning;78,00;1;366,0;705,0;...
GET/cutting-dataPermission: config:read

Cutting data

Per material the cutting speed and removal rates a price is computed from; per machine the spindle that scales them. It is not a feed-per-tooth table and does not pretend to be one.

Request
curl "https://app.metronq.com/api/v1/integration/cutting-data" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "reference": { "spindle_power_kw": 15.0, "cutting_speed_m_min": 250.0 },
  "materials": [
    {
      "key": "steel_c45",
      "name": "Steel C45",
      "cutting_speed_m_min": null,
      "cutting_speed_effective_m_min": 156.2,
      "machinability_factor": 1.6,
      "mrr_rough_cm3_min": 19.5,
      "mrr_turning_effective_cm3_min": 19.5,
      "...": "..."
    }
  ],
  "machines": [
    {
      "key": "dmu50",
      "name": "DMU 50",
      "spindle_power_kw": 13.0,
      "spindle_power_factor": 0.867,
      "min_tool_radius_mm": 1.0,
      "tool_radius_effective_mm": 6.0,
      "tool_count": 3,
      "...": "..."
    }
  ]
}

Tools

GET/tools?machine=dmu50Permission: config:read

The shop's tools

Every tool as a flat list, each with the machine it sits in and the corner radius it leaves. The machine parameter narrows it to one machine.

Request
curl "https://app.metronq.com/api/v1/integration/tools?machine=dmu50" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "count": 3,
  "items": [
    {
      "key": "e12",
      "kind": "endmill",
      "name": "12 mm carbide, 4 flutes",
      "diameter_mm": 12.0,
      "nose_radius_mm": null,
      "flute_length_mm": 45.0,
      "designation": "",
      "machine_key": "dmu50",
      "machine_name": "DMU 50",
      "cut_radius_mm": 6.0
    },
    { "key": "d6.8", "kind": "drill", "diameter_mm": 6.8, "cut_radius_mm": 3.4, "...": "..." },
    { "key": "m8", "kind": "tap", "diameter_mm": 8.0, "designation": "M8", "cut_radius_mm": 4.0, "...": "..." }
  ]
}
GET/machines/dmu50/toolsPermission: config:read

One machine's tools

One machine's tool magazine and the corner radius it implies.

Request
curl "https://app.metronq.com/api/v1/integration/machines/dmu50/tools" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "machine_key": "dmu50",
  "machine_name": "DMU 50",
  "count": 3,
  "min_tool_radius_mm": 1.0,
  "tool_radius_effective_mm": 6.0,
  "items": [ { "key": "e12", "...": "..." }, { "key": "d6.8", "...": "..." }, { "key": "m8", "...": "..." } ]
}
PUT/machines/dmu50/toolsPermission: config:write

Replace a tool library

Idempotent: the system sends the list it holds without checking what was there before. An empty list puts the machine back on its typed radius.

Request
curl -X PUT "https://app.metronq.com/api/v1/integration/machines/dmu50/tools" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tools": [ { "key": "e12", "kind": "endmill", "name": "12 mm carbide", "diameter_mm": 12.0, "flute_length_mm": 45.0 }, { "key": "d6.8", "kind": "drill", "diameter_mm": 6.8 }, { "key": "m8", "kind": "tap", "diameter_mm": 8.0, "designation": "M8" } ] }'
Response200 OK
{
  "machine_key": "dmu50",
  "machine_name": "DMU 50",
  "count": 3,
  "min_tool_radius_mm": 1.0,
  "tool_radius_effective_mm": 6.0,
  "items": [ "..." ]
}
POST/machines/dmu50/toolsPermission: config:write

Add one tool

Adds a tool, or replaces the one under the same key - for a system that reports a single change.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/machines/dmu50/tools" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "key": "r8h7", "kind": "reamer", "diameter_mm": 8.0 }'
Response200 OK
{
  "machine_key": "dmu50",
  "machine_name": "DMU 50",
  "count": 4,
  "min_tool_radius_mm": 1.0,
  "tool_radius_effective_mm": 6.0,
  "items": [ "..." ]
}
DELETE/machines/dmu50/tools/r8h7Permission: config:write

Remove a tool

Removes one tool. An unknown key answers 404, so a sync can tell a removal from a typo in its own mapping.

Request
curl -X DELETE "https://app.metronq.com/api/v1/integration/machines/dmu50/tools/r8h7" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "machine_key": "dmu50",
  "machine_name": "DMU 50",
  "count": 3,
  "min_tool_radius_mm": 1.0,
  "tool_radius_effective_mm": 6.0,
  "items": [ "..." ]
}

Import

POST/config/import/previewPermission: config:write

Preview a spreadsheet import

What this spreadsheet would do. Nothing is written: it returns the added and updated entries, the field-by-field differences, bad rows, unknown columns and a base_hash.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/config/import/preview" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "catalog": "materials", "csv_base64": "a2V5O25hbWU7cHJpY2VfcGVyX2tnCmFsdV82MDYxOy..." }'
Response200 OK
{
  "ok": true,
  "added": [],
  "updated": ["alu_6061", "steel_c45"],
  "errors": [],
  "ignored_columns": [],
  "changes": [
    {
      "section": "materials",
      "path": "price_per_kg",
      "kind": "edited",
      "default": 6.4,
      "current": 6.9,
      "entity_key": "alu_6061",
      "entity_name": "Aluminium 6061",
      "unit": "field"
    }
  ],
  "changes_total": 2,
  "base_hash": "9c1e4b0f2a7d",
  "config": { "materials": [ "..." ], "machines": [ "..." ], "...": "..." }
}
POST/config/import/preview-jsonPermission: config:write

Preview a document import

The same for a whole document. Merge changes only what the document names; replace lets it win outright, deletions included.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/config/import/preview-json" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "mode": "merge", "config": { "materials": [ "..." ], "machines": [ "..." ] } }'
Response200 OK
{
  "ok": true,
  "added": ["pom_c"],
  "updated": [],
  "changes_total": 1,
  "base_hash": "9c1e4b0f2a7d",
  "...": "..."
}
POST/config/importPermission: config:write

Apply an import

Stores what a preview returned, quoting its base_hash. If the price list changed in between, the answer is 409 config_changed.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/config/import" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "config": { "materials": [ "..." ], "...": "..." }, "base_hash": "9c1e4b0f2a7d" }'
Response200 OK
{
  "materials": [ { "key": "alu_6061", "price_per_kg": 6.9, "...": "..." } ],
  "machines": [ "..." ],
  "params": { "margin_pct": 22.0, "...": "..." },
  "currency": "EUR"
}

Enquiries

GET/quotes?since=2026-09-16T00:00:00Z&limit=50Permission: quotes:read

List enquiries

Enquiries, newest first. since is what a poll uses; status, external_ref, limit and offset do the rest. A status is one of created, uploaded, analyzing, analyzed, priced, approved, rejected, ordered and analysis_failed.

Request
curl "https://app.metronq.com/api/v1/integration/quotes?since=2026-09-16T00:00:00Z&limit=50" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "items": [
    {
      "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33",
      "number": 1042,
      "ref": "WID-1042",
      "status": "priced",
      "created_at": "2026-09-16T08:41:12+00:00",
      "filename": "bracket.step",
      "material_key": "alu_6061",
      "quantity": 25,
      "unit_price": 41.5,
      "total_price": 1037.5,
      "discount_pct": null,
      "customer_unit_price": 41.5,
      "currency": "EUR",
      "customer_email": "purchasing@example.com",
      "customer_data_visible": true,
      "external_ref": "",
      "source": "widget"
    }
  ],
  "limit": 50,
  "offset": 0
}
GET/quotes/QUOTE_IDPermission: quotes:read

One enquiry

One enquiry with its stored price breakdown - what a system reconciling it in its own books needs.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33",
  "number": 1042,
  "status": "priced",
  "unit_price": 41.5,
  "total_price": 1037.5,
  "...": "...",
  "breakdown": {
    "machine_key": "dmu50",
    "machine_name": "DMU 50",
    "machine_hourly_rate": 95.0,
    "material_cost": 172.5,
    "machining_minutes": 11.2,
    "machining_cost": 443.3,
    "setup_cost_batch": 71.3,
    "programming_cost_batch": 47.5,
    "delivery_cost": 25.0,
    "lead_days": 10,
    "...": "..."
  }
}
GET/quotes/QUOTE_ID/metricsPermission: quotes:read

Part readout

What the analysis measured: envelope, volume, holes, pockets, threads, thinnest wall and the reasons a quote was held. 409 not_analysed means the file is still being read.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/metrics" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "quote_id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33",
  "status": "priced",
  "gate_status": "instant",
  "gate_reasons": [],
  "complexity": 34.2,
  "material_key": "alu_6061",
  "quantity": 25,
  "tolerance_class": "m",
  "surface_finish": "ra_3_2",
  "confirmed_threads": ["M8"],
  "geometry_metrics": {
    "bounding_box": { "x": 120.0, "y": 80.0, "z": 18.0 },
    "volume": 74210.5,
    "surface_area": 31890.2,
    "holes": [ { "diameter": 6.8, "depth": 18.0, "through": true } ],
    "pockets": [ { "depth": 6.0, "floor_area": 1840.0, "direction": [0, 0, 1], "corner_radius": 5.0, "open": false, "...": "..." } ],
    "min_wall_thickness": 3.1,
    "...": "..."
  },
  "parts": [],
  "...": "..."
}
GET/quotes/QUOTE_ID/detailPermission: quotes:read

The whole quote

Everything the quote screen shows: where the price stands and whether a customer may see it, the price lines and VAT, the specification, the values a re-price accepts, parsed refusal reasons, the review, the readout, the parts of an assembly and where the files are.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/detail" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33",
  "ref": "API-1043",
  "status": "priced",
  "unit_price": 41.5,
  "source": "api",
  "...": "...",
  "release": { "state": "needs_review", "customer_may_see_price": false },
  "pricing": {
    "engine_unit_price": 41.5,
    "customer_total": 1037.5,
    "delivery_cost": 25.0,
    "order_total": 1062.5,
    "vat_rate_pct": 19.0,
    "order_gross": 1264.38,
    "lead_days": 10,
    "...": "..."
  },
  "material": { "key": "alu_6061", "name": "Aluminium 6061", "density_g_cm3": 2.7, "...": "..." },
  "part_mass_kg": 0.2004,
  "specification": { "tolerance_class": "m", "surface_finish": "ra_3_2", "treatments": [], "...": "..." },
  "choices": {
    "thread_candidates": [ { "designation": "M8", "count": 4, "total_depth_mm": 72.0, "hole_key": "6.8" } ],
    "hole_fit_options": [ { "key": "8.0", "diameter": 8.0, "count": 2 } ],
    "...": "..."
  },
  "outcome": {
    "gate_status": "pending_review",
    "gate_reasons": [
      {
        "raw": "part_exceeds_envelope:vf2:part_size_y:350:310",
        "code": "part_exceeds_envelope",
        "machine_key": "vf2",
        "machine_name": "Haas VF-2",
        "field": "part_size_y",
        "actual": 350.0,
        "limit": 310.0
      }
    ],
    "review": { "note": null, "reviewed_at": null, "reviewed_by": null },
    "...": "..."
  },
  "readout": { "geometry_metrics": { "...": "..." }, "...": "..." },
  "items": [],
  "files": {
    "original": { "available": true, "path": "/quotes/6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33/file" },
    "preview": { "available": true, "path": "/quotes/6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33/preview.glb" },
    "readable": true,
    "...": "..."
  },
  "created_by_key": { "name": "PDM", "prefix": "a1b2c3d4" },
  "...": "..."
}
GET/quotes/QUOTE_ID/similarPermission: quotes:read

Similar parts

The shop's three reviewed parts nearest to this one, with the approved price and how far the engine was from it.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/similar" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "count": 1,
  "items": [
    {
      "id": "0d9e7c21-5b3a-4f10-8c6d-9a2e4b7f1c05",
      "ref": "WID-1017",
      "original_filename": "bracket-rev-a.step",
      "approved_unit_price": 39.0,
      "quantity": 20,
      "currency": "EUR",
      "auto_error_pct": 4.1,
      "date": "2026-08-30T14:02:11+00:00"
    }
  ]
}
GET/quotes/QUOTE_ID/machine-optionsPermission: quotes:read

The price on each machine

The park priced against this quote: a unit price per machine, or the reasons it cannot take the part, and which one the engine picks.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/machine-options" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "count": 2,
  "items": [
    { "key": "dmu50", "name": "DMU 50", "unit_price": 41.5, "feasible": true, "reasons": [], "auto": true, "...": "..." },
    {
      "key": "vf2",
      "name": "Haas VF-2",
      "unit_price": null,
      "feasible": false,
      "reasons": [ { "code": "part_exceeds_envelope", "field": "part_size_y", "actual": 350.0, "limit": 310.0, "...": "..." } ],
      "auto": false,
      "...": "..."
    }
  ]
}
GET/quotes/QUOTE_ID/partsPermission: quotes:read

The parts of an assembly

Each part of an assembly with its own threads and fits to confirm, the machine, material and blank an engineer chose for it, and the reason when a part has no price.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/parts" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "count": 2,
  "items": [
    {
      "index": 1,
      "label": "Housing cover",
      "count": 1,
      "unpriced_reason": null,
      "price_overrides": { "machine_key": "dmu50" },
      "choices": {
        "thread_candidates": [ { "key": "1:M16", "designation": "M16", "...": "..." } ],
        "hole_fit_options": [ { "key": "1:80.0", "diameter": 80.0, "...": "..." } ],
        "...": "..."
      },
      "...": "..."
    },
    { "index": 2, "label": "Bearing 6208", "count": 2, "gate_status": "tbd", "unpriced_reason": "norm_part", "...": "..." }
  ]
}
PATCH/quotes/QUOTE_IDPermission: quotes:write

Your number on an enquiry

Stores the number from your system and nothing else. The price and the approval belong to the engine and to an engineer.

Request
curl -X PATCH "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "external_ref": "RFQ-2026-0455" }'
Response200 OK
{
  "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33",
  "number": 1042,
  "external_ref": "RFQ-2026-0455",
  "...": "..."
}

Sending a part

POST/quotesPermission: quotes:create

Open a quote for a file

The file's name and size and what to quote it as. The answer carries the upload address and headers; the file goes to storage, not through us. Every file is a quote on the plan.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/quotes" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "filename": "bracket.step", "file_size_bytes": 482113, "quantity": 25, "material_key": "alu_6061", "tolerance_class": "f", "external_ref": "RFQ-2026-0455" }'
Response201 Created
{
  "quote": {
    "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33",
    "number": 1043,
    "ref": "API-1043",
    "status": "created",
    "source": "api",
    "external_ref": "RFQ-2026-0455",
    "...": "..."
  },
  "release": { "state": "waiting", "customer_may_see_price": false },
  "upload": {
    "method": "PUT",
    "url": "https://OBJECT_STORAGE/...",
    "headers": {
      "x-amz-server-side-encryption-customer-algorithm": "AES256",
      "x-amz-server-side-encryption-customer-key": "...",
      "x-amz-server-side-encryption-customer-key-MD5": "..."
    },
    "content_length": 482113,
    "expires_at": "2026-09-25T10:15:00+00:00"
  }
}
POST/quotes/QUOTE_ID/confirm-uploadPermission: quotes:create

Confirm the upload

The file has landed: the analysis is queued and the part is priced right after it. Confirming twice is harmless.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/confirm-upload" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response202 Accepted
{
  "quote": { "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33", "ref": "API-1043", "status": "uploaded", "...": "..." },
  "release": { "state": "waiting", "customer_may_see_price": false },
  "poll_after_seconds": 10
}
POST/quotes/QUOTE_ID/repricePermission: quotes:create

Change the parameters

Quantity, material, tolerances, threads, fits, treatments or lead time - the engine prices again. Any quote, until a person decides.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/reprice" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "quantity": 50, "confirmed_threads": ["M8"], "hole_fits": { "8.0": "H7" } }'
Response200 OK
{
  "quote": { "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33", "status": "priced", "quantity": 50, "unit_price": 36.9, "...": "..." },
  "release": { "state": "instant", "customer_may_see_price": true }
}
POST/quotes/QUOTE_ID/drawing-inputsPermission: quotes:create

Complete a drawing

Material, scale or thickness for a drawing your system sent and the engine could not turn into a model. The analysis runs again.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/drawing-inputs" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "scale_ratio": 2.0, "material_key": "steel_c45" }'
Response202 Accepted
{
  "quote": { "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33", "status": "uploaded", "...": "..." },
  "release": { "state": "waiting", "customer_may_see_price": false },
  "poll_after_seconds": 10
}
POST/quotes/QUOTE_ID/submit-drawingPermission: quotes:create

Hand a drawing to a person

A drawing your system sent that the engine could not rebuild goes to a technologist, who prices it by hand.

Request
curl -X POST "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/submit-drawing" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "quantity": 10 }'
Response200 OK
{
  "quote": { "id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33", "status": "priced", "unit_price": null, "...": "..." },
  "release": { "state": "manual", "customer_may_see_price": false }
}

Files and documents

GET/quotes/QUOTE_ID/pdf?locale=enPermission: quotes:read

The quote document

The PDF the panel's Export button makes, in any language the shop offers.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/pdf?locale=en" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -o API-1043.pdf
Response200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="quote-API-1043.pdf"
GET/quotes/QUOTE_ID/filePermission: quotes:read + files:read

The uploaded file

The customer's file under its original name, decrypted on the way out.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/file" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -o bracket.step
Response200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="bracket.step"
Content-Length: 482113
GET/quotes/QUOTE_ID/preview.glbPermission: quotes:read + files:read

The 3D preview

The model the panel's viewer draws, as binary glTF. Its faces are the keys of per-face tolerances.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/preview.glb" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -o bracket.glb
Response200 OK
Content-Type: model/gltf-binary
Content-Length: 211904
GET/quotes/QUOTE_ID/items/2/preview.glbPermission: quotes:read + files:read

One part of an assembly

The preview of a single part, by the index the whole quote lists.

Request
curl "https://app.metronq.com/api/v1/integration/quotes/QUOTE_ID/items/2/preview.glb" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -o part-2.glb
Response200 OK
Content-Type: model/gltf-binary
Content-Length: 48212

Orders

GET/orders?since=2026-09-16T00:00:00ZPermission: quotes:read

List orders

Orders, newest first, with the same filters. Customer details come back only with the customers:read scope.

Request
curl "https://app.metronq.com/api/v1/integration/orders?since=2026-09-16T00:00:00Z" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "items": [
    {
      "id": "1b7a44c0-9d2e-4e51-8a10-64d1f0a2e777",
      "quote_id": "6f1c0f5a-2a4d-4d7e-9f0b-2c9b8f0a1d33",
      "status": "confirmed",
      "payment_status": "unpaid",
      "created_at": "2026-09-16T09:12:40+00:00",
      "paid_at": null,
      "quantity": 25,
      "unit_price": 41.5,
      "total_price": 1062.5,
      "min_order_top_up": 0.0,
      "delivery_cost": 25.0,
      "currency": "EUR",
      "external_ref": "",
      "customer": { "name": "Alex Morgan", "company": "Example Engineering Ltd", "city": "Riverton", "...": "..." },
      "note": "",
      "customer_data_visible": true
    }
  ],
  "limit": 100,
  "offset": 0
}
GET/orders/ORDER_IDPermission: quotes:read

One order

One order, with the customer to invoice and ship the parts to.

Request
curl "https://app.metronq.com/api/v1/integration/orders/ORDER_ID" \
  -H "Authorization: Bearer mq_live_YOUR_KEY"
Response200 OK
{
  "id": "1b7a44c0-9d2e-4e51-8a10-64d1f0a2e777",
  "status": "confirmed",
  "payment_status": "unpaid",
  "customer": {
    "name": "Alex Morgan",
    "email": "purchasing@example.com",
    "company": "Example Engineering Ltd",
    "address": "12 Industrial Road",
    "postcode": "AB1 2CD",
    "city": "Riverton",
    "...": "..."
  },
  "note": "",
  "...": "..."
}
PATCH/orders/ORDER_IDPermission: quotes:write

Status and job number

Your number, the status and the payment marker. Only the fields in the body are touched; confirmation and shipping e-mail the customer.

Request
curl -X PATCH "https://app.metronq.com/api/v1/integration/orders/ORDER_ID" \
  -H "Authorization: Bearer mq_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "external_ref": "WO-2026-0912", "status": "in_production" }'
Response200 OK
{
  "id": "1b7a44c0-9d2e-4e51-8a10-64d1f0a2e777",
  "status": "in_production",
  "payment_status": "unpaid",
  "external_ref": "WO-2026-0912",
  "quantity": 25,
  "total_price": 1062.5,
  "currency": "EUR",
  "...": "..."
}

06Questions

Common questions about the API

How do I get API access?

Access is agreed with each shop individually - just tell us which system you use. The account owner then creates keys in the settings, on the Integrations tab.

Can I test an integration before deciding?

Yes. While the connection is being built, a shop can get an evaluation key with a request budget for the whole test. The budget shows in the panel and in every API response.

Can the API change the prices customers see on its own?

Only through a preview and an apply, exactly like an import in the panel, and only with a key holding config:write. Every change lands in the settings history with the name of the key that made it.

Can an API key approve or change a quote's price?

No. A key can send a file and change the parameters it is quoted with - the engine then prices it - write your number onto an enquiry and move an order's status. A typed price, approval and discounts belong to a person.

Can my system send part files for quoting?

Yes, with a key holding quotes:create. The system opens a quote, uploads the file straight to encrypted storage and confirms; the engine analyses and prices it and the whole quote comes back. Every file is a quote on the shop's plan, exactly like one from the widget.

Does MetronQ notify my system about new enquiries by itself?

Not yet. Today your system polls for new enquiries and orders with the since parameter, which fits well inside the default 120 requests a minute. If you need notifications sent by us, tell us - requests decide what we build next.

What customer data does an integration see?

Only what the key allows. Without the customers:read scope, enquiries and orders come back without the customer's e-mail, contact details and notes, and the field customer_data_visible says plainly that they were withheld.

Can I exchange data through Excel, without a developer?

Yes. CSV export and import work in the panel, under the quoting rules: export first, edit the spreadsheet, then import with a preview of the changes.

Let's connect MetronQ to the system you already run

Tell us which ERP or CRM you use. We will agree the scopes and limits, and give you an evaluation key while the connection is being built.