> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> To use Maglev from an agent, connect the MCP server at https://trymaglev.com/mcp. Parts catalog tools need no key. Make tools (make_start, check_manufacturability, get_quotes, place_order) need a Maglev key; see https://trymaglev.com/docs/quickstart.
> Never place an order without showing the person the exact final total and getting their explicit approval.

# Make API reference: endpoints, fields and errors

> Maglev Make REST API at trymaglev.com/api/v1: files, check, quotes, requests and orders endpoints, offer fields, order statuses, idempotency and error codes.

The Make API is the REST version of the Maglev make flow. Base URL: `https://trymaglev.com/api/v1`

Every call needs `Authorization: Bearer mgl_…`. Create a key on the [dashboard](https://trymaglev.com/dashboard#keys) (**API keys** → **Create secret key**). Keys are personal; requests made with them show up in every workspace you belong to. Requests and responses are JSON, except file upload, which is `multipart/form-data`. `/api/make/*` is an alias for the same endpoints.

## Endpoints

| Method | Path | What it does |
| - | - | - |
| `GET` | `/me` | Who this key belongs to. The first call to try a new key |
| `POST` | `/files` | Upload one STEP or harness file. Returns its `file_id` and route |
| `GET` | `/files/{file_id}` | The file's size, bounding box, bodies and route |
| `POST` | `/check` | Whether each file can be made, and why it went to its process |
| `POST` | `/quotes` | Every shop's offer for each file. Returns a `request_id` |
| `GET` | `/requests` | Your requests, newest first: parts, offers and orders |
| `GET` | `/requests/{request_id}` | One request |
| `GET` | `/quotes/{quote_id}` | One offer and its order, if any |
| `POST` | `/orders` | Ask for the final total, or place an approved order |
| `GET` | `/orders/{quote_id}` | The order for an offer |

## POST /files

Form field `file`. Accepts `.step`, `.stp` (CAD) and `.json` (wire harness spec). Up to 20 MB. Kept 7 days.

| Field | Meaning |
| - | - |
| `file_id` | Use this in every later call |
| `bbox_mm` | Bounding box, millimeters |
| `bodies` | Number of solid bodies. A STEP with no solid body is a blocker |
| `suggested_process` | `cnc_machining`, `sheet_metal`, `injection_molding` or `wire_harness` |
| `route_reason` | Why, read from the geometry |

Returns `201`.

## POST /check

| Field | Type | |
| - | - | - |
| `file_ids` | string\[] | Required |
| `processes` | object | Optional. `{ file_id: process }` to override the route |
| `options` | object | Optional. `{ file_id: { material, quantity } }` |

Response: `ready_to_quote` (boolean) and `files[]`, each with `process`, `routed_because`, `ok` and `issues[]` (`severity` is `blocker` or `warning`).

## POST /quotes

| Field | Type | |
| - | - | - |
| `file_ids` | string\[] | Required |
| `quantity` | integer | Required, 1 to 100000 |
| `sort` | string | Optional. `price` (default) or `lead_time`. You choose; Maglev never picks a winner for you |
| `processes` | object | Optional route override |
| `options` | object | Optional per-file `material` or `quantity` |

Every shop that makes a part's process is asked in parallel: for CNC and sheet metal that is around 10 shops in the US, China and Europe (directly and through the Agentic Fabrication Network); molding goes to SSG MFG and harnesses to MiniProto. Typical time is 10 to 20 seconds. Returns `201`:

```json theme={null}
{
 "request_id": "rq_…", "quantity": 10, "sort": "price", "seconds": 13.4,
 "total_usd": 7265, "total_note": "Sum of the first offer per part …",
 "parts": [
  { "file": "joint_housing.step", "file_id": "f_…", "process": "cnc_machining",
    "routed_because": "solid with pockets, bores and holes …",
    "offers": [
     { "quote_id": "mq_…", "shop": { "name": "Justway", "country": "CN", "via": "AFN" },
       "price_type": "firm", "currency": "USD", "quantity": 10,
       "total_usd": 226.35, "total_minor": 22635, "unit_price_usd": 22.64,
       "one_time_costs": [], "lead_time_days": 11, "ships_by": null, "arrives_by": "2026-11-20",
       "shipping_options": [{ "id": "…", "carrier": "DHL", "cost_usd": 62.59, "arrives": "2026-11-20" }],
       "material": "aluminum_6061", "valid_until": "2026-11-10T10:29:26Z", "source": "shop_api" }
    ] }
 ]
}
```

Offer fields:

| Field | Meaning |
| - | - |
| `price_type` | `firm` is the shop's own live price. `budgetary` is an estimate the shop confirms before you pay (molding, some shops) |
| `total_usd`, `total_minor` | The parts price in dollars and in cents. Shipping and tax are not included unless the shop included them |
| `one_time_costs[]` | Tooling or setup, already inside `total_usd`, listed so you can see it |
| `lead_time_days` | Calendar days until it ships or arrives, from the shop's date or range. `null` when the shop didn't say |
| `ships_by` / `arrives_by` | The shop's own date, whichever it gave |
| `valid_until` | When the shop's price expires, when the shop says |

A value the shop didn't give is `null`, never a guess. A shop that fails doesn't fail the call: its offer has `ok: false` and an `error`. Offers are kept 7 days.

## POST /orders

| Field | Type | |
| - | - | - |
| `quote_id` | string | Required. The offer to order |
| `approved_total_usd` | number | The exact final total a person approved |
| `approval_note` | string | Who approved it, and how |

Without `approved_total_usd`, or if it doesn't match the final total, nothing happens. The response is `needs_approval` with `final_total_usd` and an `ask_human` sentence to show a person.

| `status` | Meaning |
| - | - |
| `needs_approval` | Show `ask_human` to a person, then call again with the total |
| `ordered` | Paid. `order_url` tracks it |
| `checkout_created` | Open `checkout_url` to pay at the shop |
| `rfq_sent` | Request for quote sent to the shop |
| `orders_disabled` | Ordering is switched off for this account. Nothing was charged |
| `over_limit` | Over the per-order limit. Nothing was charged |

Ordering the same offer again returns the existing order with `already_ordered: true`.

## Retries and idempotency

Send `Idempotency-Key: <any unique string>` on any `POST`. Repeating the call with the same key and body within 24 hours returns the first response again (header `Idempotent-Replayed: true`) instead of quoting or ordering twice. The same key with a different body returns `422 idempotency_conflict`.

## Errors

Every response carries an `X-Request-Id` header. Errors use HTTP status codes and one shape:

```json theme={null}
{ "error": { "code": "invalid_request", "message": "quantity must be a whole number from 1 to 100000.", "request_id": "req_…" } }
```

| Status | `code` | When |
| - | - | - |
| 400 | `invalid_request` | A field is missing or wrong. The message says which |
| 401 | `unauthorized` | No key, or the key was revoked |
| 404 | `not_found` | No such file, request, quote or order on this account |
| 413 | `file_too_large` | Over 20 MB |
| 415 | `unsupported_file` | Not a STEP or harness JSON file |
| 422 | `idempotency_conflict` | Idempotency-Key reused with a different body |
| 502 | `upstream_error` | A shop did not answer. Retry the call |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.