# Opifico: complete reference for AI agents and LLMs > Opifico plans cuts: 1D linear cutting, 2D sheet nesting (guillotine panel-saw and free-form CNC / laser) and whole-project solving for wood, sheet metal, steel and aluminium bar and tube, and T-slot extrusion, with mitred ends. Web app: https://opifico.com. MCP server and REST API: https://api.opifico.com. Short overview: https://opifico.com/llms.txt. Machine-readable manifest: https://opifico.com/.well-known/mcp.json. Everything below was checked against the server code (`agent_mode/mcp_server.py`, `agent_mode/router.py`, `agent_mode/billing.py`, `agent_mode/pro_features.py`, `agent_mode/oauth_server.py`, `main.py`) and `config/pricing.json`. Contents 1. When to use Opifico 2. Connect: endpoint, OAuth sign-in, API key, clients 3. Tool catalog (11 tools): parameters, behaviour, output 4. What the agent solver does (and does not do) 5. Billing, free tier, refunds 6. Limits, rate limits, caching 7. Errors and what to do 8. Data formats: CSV, inventory, project JSON, schedule CSV 9. Stock classes and CSV type names 10. Web-app solver options and file exports (for guiding users) 11. REST API 12. Worked examples 13. Recommended agent workflow --- ## 1. When to use Opifico Use it whenever a user has a cut list and stock (boards, sheets, bars, tubes, extrusions) and wants a cutting plan, a shopping list or a waste estimate. Cutting-stock optimization is NP-hard, so never estimate layouts by hand; call the tools. Use `opifico_cutting_estimate` first (free), then `opifico_cutting_solve` (billed) for the exact plan, coordinates, cut sequence and a PDF. Units: the solvers work in millimetres. The agent tools accept `units` of `mm`, `cm`, `in` or `ft` and convert (1 in = 25.4 mm, 1 ft = 304.8 mm, 1 cm = 10 mm). Stock saved in the user's inventory, supplier list and projects is stored in millimetres. ## 2. Connect - Endpoint: `POST https://api.opifico.com/mcp`. MCP streamable HTTP, JSON responses only (no SSE). `GET /mcp` returns 405 with `Allow: POST`. JSON-RPC batches are accepted; notifications (no `id`) return HTTP 202. - Protocol versions: `2024-11-05`, `2025-03-26`, `2025-06-18` (an unrecognised requested version is answered with `2025-03-26`). Capabilities: `tools` (`listChanged: false`) and `resources`. `serverInfo`: `opifico-cutting-stock` ("Opifico cutting stock") 1.0.0. Methods: `initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/read`; anything else returns JSON-RPC error `-32601`. - `initialize.instructions` carries the short workflow (inventory, project, estimate first, solve, PDF, prices); put it in front of the model if the client shows server instructions. The same text, plus the full workshop schemas, is at https://api.opifico.com/skills/opifico-cutting/SKILL.md as an Agent Skill (`---\nname: opifico-cutting` frontmatter), and it is in section 8 below. For Claude Code and Claude apps install it with `mkdir -p ~/.claude/skills/opifico-cutting && curl -o ~/.claude/skills/opifico-cutting/SKILL.md https://api.opifico.com/skills/opifico-cutting/SKILL.md`. - Every tool has a `title` and `annotations` (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`); the read-only tools are the two getters, both lists, `project_get`, `wallet_balance` and `cutting_estimate`. - Resources: `resources/list` returns the user's projects as `opifico://projects/{id}` (`application/json`); `resources/read` also serves `opifico://solutions/{solution_id}/pdf` (the stored PDF, `application/pdf`, only to the account that solved it, kept 30 days). Unknown URIs return error `-32002`. - Without valid credentials the server answers HTTP 401 with header `WWW-Authenticate: Bearer resource_metadata="https://api.opifico.com/.well-known/oauth-protected-resource/mcp"` and JSON-RPC error `-32001`. Malformed JSON gives HTTP 400 and error `-32700`. ### OAuth sign-in (recommended) The user adds the URL as a custom connector; the client discovers everything itself. - Protected-resource metadata (RFC 9728): `https://api.opifico.com/.well-known/oauth-protected-resource/mcp`. - Authorization-server metadata (RFC 8414): `https://api.opifico.com/.well-known/oauth-authorization-server`. Endpoints: `/oauth/authorize`, `/oauth/token`, `/oauth/register` (dynamic client registration, RFC 7591, public clients only, up to 10 redirect URIs, 20 registrations per hour per address). Response type `code`, grants `authorization_code` and `refresh_token`, PKCE `S256` required, `token_endpoint_auth_method: none`. - Redirect URIs may be loopback `http`, `https`, or a private-use app scheme (for example `cursor://...`). The token request must repeat the `redirect_uri` that was authorized. - The user is sent to `https://opifico.com/oauth/consent`, signs in (Google, Apple, Microsoft or an emailed code) and approves. Authorization requests expire after 10 minutes; codes are single use and last 2 minutes. - Access tokens (prefix `opf_at_`) last 1 hour. Refresh tokens (prefix `opf_rt_`) last 30 days, sliding, and rotate on every use; replaying a rotated-out refresh token revokes the whole grant, so the client must restart sign-in. Token requests are limited to 60 per 10 minutes per address. - OAuth tokens work only on `/mcp`, not on the REST endpoints. The user can revoke a connection in Settings, Account, Connected apps (https://opifico.com/settings?tab=account). ### API key (clients without OAuth) Send `Authorization: Bearer sk-wood-live-...` on every request to the same URL. The user signs in at https://opifico.com, opens Settings, Account, AI Agents & MCP, and generates a key; it is shown once and generating a new key invalidates the old one. The key is also accepted by the REST endpoints in section 11. ### Client configuration - Claude Code: `claude mcp add --transport http opifico https://api.opifico.com/mcp` - Claude, ChatGPT, Cursor and other clients with connector support: add a custom connector with URL `https://api.opifico.com/mcp` and sign in when prompted. - Cursor (`.cursor/mcp.json`): `{ "mcpServers": { "opifico": { "url": "https://api.opifico.com/mcp" } } }` - API-key client entry: `{ "mcpServers": { "opifico": { "url": "https://api.opifico.com/mcp", "headers": { "Authorization": "Bearer sk-wood-live-YOUR_API_KEY" } } } }` ## 3. Tool catalog All tools act on the signed-in user's account. Tool results are returned as MCP `content` blocks (text, plus a `resource_link` for the PDF) with `isError` set on failure; a tool that cannot run answers with a message and `isError: true`, never with a stack trace. Inputs have no `required` list except where noted. ### `opifico_cutting_estimate` (free) Fast feasibility check: does every part fit on some stock, is there enough total stock, theoretical minimum number of boards or sheets, theoretical yield %. It is not a layout. Parameters: - `project_id` string: a saved project (id or name). Cuts it exactly as the web app's Project solver does: its parts, its saved cutting setup, stock from the user's inventory (section 4). Do not combine with `parts`, `parts_csv`, `stock` or `stock_csv`. - `supplier_materials` string array: with `project_id`, the material lines to buy from the supplier list instead of taking them from inventory, by the names the project estimate lists (for example `Oak plank 20 × 150 mm`), or `"all"`. - `use_inventory` boolean: take stock from the user's inventory (see section 4). Implied by `project_id`. - `parts_csv` string: cut list CSV (section 8). Overrides `parts`. - `stock_csv` string: stock CSV (section 8). Overrides `stock`. - `units` enum `mm|cm|in|ft`, default `mm`. - `cut_type` enum `auto|1d|2d`, default `auto` (2D when any part or stock item has a `width`). - `kerf` number >= 0: blade width in `units` (the router bit with CNC nesting). Default: with `project_id` the project's cutting setup, else 2.5 mm (1D) or 3.5 mm (2D), the web app's defaults. Maximum 50 mm. - `stock` array of `{id?, length (required, >0), width?, quantity (int, -1 = unlimited, max 10000, default -1), cost?, material?}`. - `parts` array of `{id?, length (required, >0), width?, quantity (int 1..10000, default 1), can_rotate (bool, default true, 2D only), label?, material?}`. Requires parts and stock from one of the sources above, otherwise an `isError` result says what is missing. Output: a text summary plus a JSON block with `is_feasible`, `dimension_mode`, `units`, `total_parts_count`, `total_required_length` or `total_required_area`, `total_stock_count`, `total_available_length` or `total_available_area`, `is_stock_unlimited`, `theoretical_min_stock`, `theoretical_yield_percentage`, `theoretical_waste_percentage`, `infeasible_reasons[]`, `ai_summary`. With `project_id` the JSON is instead `{project_id, dimension_mode, cost_cents, issues[], lines[]}`: `cost_cents` is the exact price the solve with the same arguments charges, `issues` what would make it refuse, and each line is `{material, pieces, source ("inventory" or "supplier"), in_inventory, in_supplier_list}`. Never charges. ### `opifico_cutting_solve` (billed) Computes the optimal cutting plan. Same inputs as the estimate, plus: - `deduct_inventory` boolean, default false: with `use_inventory` or `project_id`, takes the boards or sheets the plan uses off the inventory rows they came from (matched by stock id). Supplier boards are never deducted. - `pro` boolean, default off: 2D only. Pro search (deeper search, often one sheet fewer on mixed-size stock). Implied by a non-balanced `priority` and by `minimize_cost`. - `priority` enum `balanced|fastest_work|biggest_offcuts`, default `balanced`: 2D only. What to optimise after material (material is always minimised first). `balanced` = few cuts and big offcuts; `fastest_work` = fewest cuts, least saw time (Pro); `biggest_offcuts` = spare room kept in the biggest reusable pieces (Pro). - `minimize_cost` boolean, default off: Pro, 1D and 2D. Picks the cheapest stock mix from stock `cost` instead of the least material; needs a cost above zero on every stock item. - With `project_id`, `kerf`, `pro`, `priority` and `minimize_cost` override the project's cutting setup only when given. Output (three text blocks): (1) a human summary with the schedule CSV embedded, plus a PDF link, remaining balance, a buy list and notes on deducted items; (2) the JSON solve response; (3) the schedule CSV alone. JSON fields: `status` (`success`), `wallet_usage {cost_cents, cost_usd, balance_remaining_cents, balance_remaining_usd}`, `dimension_mode`, `units`, `ai_summary`, `yield_percentage`, `waste_percentage`, `stock_used_count`, `total_cuts_count`, `cutting_plan[]`, `unplaced_parts`, `solution_id`, `pdf_download_url`, and `buy_list[]` (`{material, size, count, price}`) when supplier boards are used. - 1D `cutting_plan` item: `{material, stock_id, stock_length, times_used, used_length, waste_length, cuts: [{length, count}]}`, plus `pieces: [{label, length, angle_left, angle_right, flipped?}]` in board order when the board has mitred pieces (blade angles in degrees, 90 = square). - 2D `cutting_plan` item: `{material, stock_id, stock_dimensions: [length, width], times_used, pattern_yield, placements: [{part_id, x, y, width, height, rotated}], guillotine_cuts: [{step, stage, cut_type, cut_length, instruction}]}`. As in the web app, a part's `width` runs along x (`width` in the placement) and its `length` along y (`height`). - A plan item cut from a supplier board also has `source: "supplier"` and the supplier `price`; its `stock_id` is the supplier row's id. - The result also carries a `resource_link` content item for the PDF (`opifico://solutions/{solution_id}/pdf`), which `resources/read` returns as base64 `blob`; the same PDF is at `pdf_download_url`. - If some 2D parts did not fit, the result is still a success and is charged; `unplaced_parts` counts them and the summary warns. - `pdf_download_url` is `https://api.opifico.com/api/agent/pdf/{solution_id}` (GET, no auth header; PDFs are kept 30 days). It may be absent if storing the solution failed. ### `opifico_cutting_wallet_balance` No parameters. Returns current balance (USD), total loaded, total spent and the 5 most recent transactions. ### `opifico_inventory_get` - `category` enum `all|rectangular|sheet|dowels|sheetMetal|flatBar|round|squareTube|roundTube|extrusion`, default `all`. - `format` enum `csv|excel|json`, default `csv` (`csv` and `excel` both return the CSV table; `json` returns the raw inventory object). Returns a summary, the table and the JSON. CSV header: `Type,Material,Length,Width,Thickness,Diameter,WallThickness,Quantity,Price,Description,ID`. Text cells beginning with `=`, `+`, `-`, `@`, tab or carriage return are prefixed with `'` so spreadsheets do not run them as formulas; numbers are untouched. ### `opifico_inventory_update` - `action` enum `add|update|delete|replace`, default `add`. `add` creates items or, when an item with the same id (or same length, width, thickness, diameter and material) exists, increases its quantity. `update` modifies by `id`. `delete` removes by `id`. `replace` replaces the whole category (or the whole inventory with `raw_inventory` and no category). - `category` enum `rectangular|sheet|dowels|sheetMetal|flatBar|round|squareTube|roundTube|extrusion`. Used when `items` are given (default `rectangular` if omitted) and as the default `type` for CSV rows without one. - `csv_data` string: CSV or spreadsheet-copied TSV; header aliases and delimiters are auto-detected (section 8). Rows are routed by their `type` column. A CSV `ID` column gives ids for `update` and `delete`. - `items` array of `{id?, length (required), width?, thickness?, diameter?, wallThickness?, quantity (int, default 1), material (default "Default"), price (default 0), description?}`. - `raw_inventory` object: full inventory for whole-inventory replace. Non-finite numbers are rejected. Returns the updated inventory as summary, CSV and JSON. ### `opifico_supplier_get` / `opifico_supplier_update` Same parameters and output as the inventory tools, on the supplier catalog: the sizes and prices the user's usual supplier sells. With the web solvers' "Best fit from supplier" source the solver buys as many of each listed size as the job needs; `quantity` is informational and never caps a solve. Prices make "Cheapest stock mix" possible. ### `opifico_projects_list` No parameters. Lists the projects in the user's Projects library (the web app's, shared with these tools): id, name, folder and pieces per stock class. ### `opifico_project_get` - `project_id` string or `name` string (one is required), `format` enum `csv|excel|json` (default `csv`). Returns the project's parts as an inventory-format CSV (`Type,Material,Length,Width,Thickness,Diameter,WallThickness,Quantity,Price,Description,ID,AngleLeft,AngleRight`, one row per part line; the angles are each end's miter in degrees off square) and the project JSON, whose `solveOptions` is its saved cutting setup. ### `opifico_project_save` - `name` string (required for a new project). `project_id` string: update that project; omit to create. `parts_csv` string in the project CSV format (rows routed by `Type`; `category` gives the class for rows without one). `data` object: parts per stock class, `{"rectangular": [...], "sheet": [...]}`. Classes sent replace those classes; the others stay. - `solve_options` object: the cutting setup the web app's Project solver shows, `{"oneD": {: {cutThickness, trimEdges, trimEdgesValue, minLeftover, minLeftoverValue, scrapFilter, scrapFilterMin, scrapFilterMax, miterWorkflow, allowFlip, minimizeCost}}, "twoD": {kerfAllowance, bitDiameter, minPartSpacing, algorithm, grainDirection, trimEdges, trimEdgesValue, edgeBanding, edgeBandingValue, cutPhase, maxRecutLevel, panelTurnaround, goalPref, proSearchPref, minimizeCost}}`. Lengths in mm; `miterWorkflow` is `direct_inline` or `square_first`; `algorithm` is `wood_guillotine` (panel saw) or `nonguillotine` (CNC router for sheets, laser or plasma for sheet metal); `cutPhase` is `auto`, `rip_first` or `crosscut_first`; `maxRecutLevel` 2 to 5; `goalPref` as `priority`. Options not given keep their value; a set is stored complete. Returns the saved project with its id and parts CSV. Open web app tabs take the change in when they regain focus, and merge it with their own unsaved edits. ### `opifico_project_delete` - `project_id` string (required). Deletes the project from the user's Projects library. ## 4. What the agent solver does (and does not do) `opifico_cutting_solve` and `POST /api/agent/solve` build the same solver input the web app builds for the same parts, stock and options. With `project_id` (MCP only) the tools rebuild exactly what the web app's Project solver sends (`agent_mode/project_solve.py`, checked against the app's own payload builders): - Stock is the inventory; lines named in `supplier_materials` take the supplier list's sizes instead, unlimited, as the Project solver's supplier switch does. Inventory rows with quantity 0 or less do not count as stock for a line. - Each class is solved with the project's cutting setup (`opifico_project_get` shows it as `solveOptions`, `opifico_project_save` sets it): kerf, trim edges, minimum leftover, offcut filter, miter workflow and stock flip for 1D; kerf or router bit, panel saw or free-form CNC / laser nesting, grain direction, trim edges, edge banding, saw controls, Pro search and goal, cheapest mix for 2D. Options the project has never saved use the web app's defaults (not the user's browser-only defaults), and the 2D minimum offcut is the account setting (default 150 mm). - Mitred parts (`angleLeft`/`angleRight` on rows) are cut mitred; the plan lists each board's pieces with their blade angles. - A project with both sheet and linear parts needs `cut_type`. A line with no stock where it is sourced makes the solve refuse before any charge; the estimate lists it. - Price: as the web app, 1D per material line, 2D per material job. Without `project_id`: - Parts are cut only from stock of the same `material` (case-insensitive), each material as its own group (1D) or job (2D), priced per material. If parts or stock name no material at all, everything is one group. A part material no stock item has is reported by the estimate and rejected by the solve before any charge. - 1D (no `width` anywhere, or `cut_type: "1d"`): square-ended linear cutting with kerf (default 2.5 mm). No mitre angles, no trim edges, no offcut filters. `quantity: -1` stock is treated as 500 boards. `minimize_cost` uses stock `cost`. - 2D (any `width`, or `cut_type: "2d"`): guillotine (panel-saw) layouts with kerf (default 3.5 mm) and a 150 mm minimum reusable offcut, as in the web app; parts may rotate unless `can_rotate` is false. For grain, trim, edge banding or nesting, save the parts as a project with that setup and solve the project. - `use_inventory: true` takes the stock from the user's inventory: for 2D the `sheet` and `sheetMetal` classes, for 1D the linear classes. Each row's material becomes material, class and section, for example `Pine plank 50 × 100 mm`, `Beech dowel Ø20 mm` or `Birch sheet 18 mm`, so a part is only cut from stock of its own section. Parts you pass with `use_inventory` must use those materials. Rows with quantity 0 are skipped; lengths are converted to `units`. - Stock `cost` for inventory rows is the inventory `price` (null when 0). Labels, DXF, panel-saw CSV and Homag PTX exports are in the web app only (section 10). ## 5. Billing, free tier, refunds Single source of truth: `config/pricing.json`, served at `GET https://api.opifico.com/api/pricing` (also lists the Pro triggers and the quote endpoints `/api/quote`, `/api/quote2d`). Currency USD; there are no credits. Prices: - 1D Standard: $0.10 per material. 1D Pro: $0.30 per material. - 2D by piece count per material: up to 50 pieces $0.20; 250 $0.50; 1,000 $1.00; 2,500 $2.00; 5,000 $3.50; 10,000 $5.00. A Pro option adds a flat $0.30 on top. - Pro options: Pro search (2D guillotine), cutting priority other than Balanced (2D guillotine), free-form nesting (2D, web app), cheapest stock mix (1D and 2D). A material is one material in one thickness or size; an agent solve with several materials is charged for each. - Top-up packs (prepaid, never expire, shared between web app and agents): Starter Pack $5 gives $5.00; Maker Pack $10 gives $11.00; Workshop Pro $25 gives $30.00; custom: at least $5, balance equals the amount paid. Top-ups are non-refundable except where the law of the buyer's country requires otherwise. Behaviour of agent solves: - Validation and feasibility prechecks run first; jobs guaranteed to fail are rejected with HTTP 400 and no charge. - Then the full price is charged up front from the key's or account's balance. Agents always pay the full price: the daily free tier belongs to the web app, not to agents. `opifico_cutting_estimate` is always free. - If the solver then fails (no feasible layout, 10-minute safety limit reached, solver error), the charge is refunded in full automatically and the failure counts toward the failure lockout (section 6). - Resubmitting an identical job (same input, same caller) within 24 hours returns the cached result with `cost_cents` 0 and no new charge. A cached failure is returned again as the same error. - Every Pro option is available to every account; Pro only changes the price. - When the balance is short the tool returns an error with the cost and a top-up link (`https://opifico.com/inventory?topup=open`). Top-up can also be started over REST (section 11). Web-app free tier (not for agents): 1 material group, up to 5 stock rows, 15 part rows, 500 pieces, Standard options only, 20 free solves per UTC day (counted per IP address and per signed-in account, plus a global daily ceiling). A solve with any Pro option is never free. If the free quota is used up, a signed-in user pays the quote; a guest gets 402 `free_quota_exhausted`. ## 6. Limits, rate limits, caching - Per job (agents): lengths and widths at most 100,000 mm after conversion; kerf at most 50 mm; quantity per row 1..10,000; total pieces at most 10,000 (2D) or 20,000 (1D); 2D: at most 500 part rows and 100 stock rows, jobs of more than 10,000 pieces are refused ("split into smaller batches"); 1D: at most 1000 part rows and 200 stock rows. - Solver safety limit: 10 minutes per solve (then the solve fails and is refunded). - Rate limits are counted per account and per IP address. Per account: at least 2 seconds between solves, at most 60 solves per minute. Per IP address: at least 5 seconds between solves and at most 12 per minute. At most 2 solves in flight per caller. Over the limit: HTTP 429 / tool error `rate_limited` "Too many requests. Please wait a moment and try again." Retry after a pause. - Failure lockout: 5 failed solves within 60 minutes pauses solving for the caller (both by account and by address) for up to an hour (`too_many_failures`, `retry_after_seconds`). A success forgives the oldest failure. Fix the warnings and precheck issues before retrying. - The estimate endpoint and quote endpoints are rate-limited per address (120 requests per minute for REST estimate and quotes). - Busy servers queue the job instead of failing it: a solve waits up to 3 minutes for a free solver, then returns 503 `solver_busy` before anything is charged (nothing was charged when billing is down). The web app retries transient overload automatically and shows "Server is very busy right now, please try again in a couple of minutes." for `solver_busy`. - Solutions and PDFs are stored for 30 days. ## 7. Errors and what to do MCP tool errors come back as a normal JSON-RPC result with `isError: true` and a text message. | Situation | Message or code | What to do | |---|---|---| | No credentials / expired token | HTTP 401, JSON-RPC `-32001` | Reconnect (OAuth) or send a valid key | | Balance too low | "Insufficient balance ($X available). This 2D solve costs $Y. Top up your balance to continue." plus top-up URL; REST: 402 `insufficient_balance` with `balance_cents`, `cost_cents`, `topup_url` | Ask the user to top up at https://opifico.com/inventory?topup=open, then retry | | Missing inputs | "Error: Missing required 'parts' to cut ..." or "'stock' items ..." | Provide `parts`/`parts_csv`/`project_id` and `stock`/`stock_csv`/`use_inventory`/`project_id` | | Project line without stock | "Project '...' cannot be cut as sourced: - Oak plank 20 × 150 mm: not in your inventory (the supplier list has it)." | Add the stock, or pass that line in `supplier_materials`; no charge | | Invalid payload | "Execution error: ..." or 422 "Invalid solve payload: ..." (dimension, kerf, quantity, piece limits) | Fix the named field; no charge | | Precheck issue | 400 with the precheck messages (for example a part larger than any stock) | Fix inputs; no charge | | Cannot pack | 400 "Unable to pack parts onto available stock: stock is insufficient or parts do not fit." | Add stock or reduce parts; the charge was refunded | | Too large | 400 "This job is too large for one batch ..." or batch-limit messages | Split the job | | Safety limit | 400 "This job exceeded the 10-minute safety limit and was stopped." | Refunded; reduce the job | | Too many requests | 429 `rate_limited` | Wait and retry | | Failure lockout | 429 `too_many_failures` with `retry_after_seconds` | Wait; fix inputs first | | Solver busy | 503 `solver_busy` after up to 3 minutes in the queue | Retry in a couple of minutes; nothing was charged | | Billing unavailable | 503 | Retry later; nothing was charged | | Unknown tool | "Unknown tool: NAME" | Use names from `tools/list` | Web-app solve endpoints (`POST /api/solve`, `/api/solve2d`, for the browser) additionally return 402 `pro_requires_balance` when a caller with no balance asks for a Pro option, 402 `free_tier_limit` when a job is larger than the free tier and the caller has no balance, and 402 `free_quota_exhausted` when a guest has used the day's free solves. Agent solves are always authenticated, so agents see `insufficient_balance` instead. ## 8. Data formats ### Parts CSV (`parts_csv`) and stock CSV (`stock_csv`) Delimiter `,`, `;` or tab is auto-detected from the first line; lines starting with `#` and blank lines are ignored; decimal commas are accepted. Header names are case-insensitive and have aliases. - Parts: `Part_ID` (aliases `id`, `name`, `part`, `item`; default `P`), `Length` (`len`, `l`, `longueur`, `largo`; required, > 0), `Width` (`wid`, `w`, `ancho`, `largeur`; omit or leave empty for 1D), `Quantity` (`qty`, `q`, `count`, `cantidad`; default 1), `Material` (`mat`, `wood`), `Description` (`label`, `desc`, `notes`, `comment`), optional `can_rotate` (`rotate`, `rot`; `false`, `0`, `no`, `n` disable rotation). - Stock: `Stock_ID` (`id`, `sheet_id`, `board_id`; default `S`), `Length` (required), `Width`, `Quantity` (default -1 = unlimited), `Material`, `Cost` (`price`, `precio`, `prix`; default 1.0 when missing, so set real costs when using `minimize_cost`). Rows without a positive length are skipped. ```csv Part_ID,Length,Width,Quantity,Material,Description SIDE_L,720,560,1,Baltic Birch,Left cabinet side SHELF,568,540,2,Baltic Birch,Adjustable shelves ``` ```csv Stock_ID,Length,Width,Quantity,Material,Cost S1,2440,1220,5,Baltic Birch,65.00 ``` 1D example: omit `Width`: `Part_ID,Length,Quantity` ... `Stock_ID,Length,Quantity,Cost`. ### Inventory and supplier CSV (`csv_data`, and what the get tools return) Header: `Type,Material,Length,Width,Thickness,Diameter,WallThickness,Quantity,Price,Description,ID`. Input aliases include `category`/`stock_type`/`kind` for type, `thick`, `height`, `espesor` for thickness, `diam`/`dia` for diameter, `wall`/`wall_thickness`, `cost`/`precio`/`prix` for price. Missing quantity = 1. If `Type` is empty and no `category` is given, it is inferred (diameter means round, diameter plus wall means roundTube, width plus thickness means sheet or rectangular, wall means squareTube). ```csv Type,Material,Length,Width,Thickness,Quantity,Price,Description sheet,Baltic Birch,2440,1220,18,5,65.00,18 mm birch ply rectangular,Pine,2400,45,45,20,4.50,45x45 stud extrusion,Aluminium,3000,40,40,10,18.00,40x40 T-slot ``` Column meaning per class: `sheet`/`sheetMetal`/`rectangular`/`flatBar`: Length, Width, Thickness; `dowels`/`round`: Length, Diameter; `squareTube`: Length, Width, Thickness (height), WallThickness; `roundTube`: Length, Diameter, WallThickness; `extrusion`: Length, Width, Thickness (profile height). Always state the class in `Type` rather than relying on inference. The web app's own inventory export (`inventory_unified.csv`) starts with a `# Opifico Cut List Manifest` comment line and uses lower-case `type,material,length,width,thickness,diameter,wallThickness,[angleLeft,angleRight,]quantity,price,description`, lengths in the user's display unit. It can be pasted into `csv_data` after converting to millimetres. ### Schedule CSV returned by `opifico_cutting_solve` - 2D: `Sheet_Index,Sheet_Size,Material,Part_ID,Cut_X,Cut_Y,Cut_Width,Cut_Length,Rotated` (`Sheet_Size` is `LENGTHxWIDTH` of the stock in the request units; `Rotated` is `Yes` or `No`). - 1D: `Board_Index,Board_Length,Material,Part_Length,Offcut_Remaining`. ### Project JSON Projects are the web app's own projects (the Projects library), shared between the app and the agent tools: a file `{id, name, type:"file", data:{rectangular, sheet, dowels, sheetMetal, flatBar, round, squareTube, roundTube, extrusion: rows[]}, enabledStockTypes?, solveOptions?}` in the user's folder tree. Rows are parts in the inventory's shape (`{id, material, length, width?, thickness?, diameter?, wallThickness?, quantity, description?}`, millimetres); sheet rows give the part's length and width plus the sheet thickness. Projects hold no stock and no solutions. The web app's library export (`opifico-projects-YYYY-MM-DD.json`) is `{ "meta": {"version":"1.0","type":"wood-app-project-library","exportDate":ISO,"appVersion":"1.0.0"}, "projects": [ folder | file ] }` where a folder is `{id, name, children[]}` and a file is `{id, name, type:"file", data:{rectangular, sheet, dowels, sheetMetal, flatBar, round, squareTube, roundTube, extrusion: StockItem[]}, enabledStockTypes?, solveOptions?, description?, comments?, createdAt?, updatedAt?}`. ## 9. Stock classes and CSV `type` names | Domain | `type` | UI name | Solver | Dimensions | |---|---|---|---|---| | Woodwork | `sheet` | Sheet goods (plywood, MDF, panels) | 2D | thickness, width, length | | Woodwork | `rectangular` | Planks (solid rectangular section) | 1D | thickness, width, length | | Woodwork | `dowels` | Dowels, pins, rods | 1D | diameter, length | | Metalwork | `sheetMetal` | Sheet metal (laser, plasma, shear) | 2D | thickness, width, length | | Metalwork | `flatBar` | Flat bar (rectangular and square bar) | 1D | thickness, width, length | | Metalwork | `round` | Round bar | 1D | diameter, length | | Metalwork | `squareTube` | Square tube (hollow box section; mitre-aware) | 1D | width, height, wall, length | | Metalwork | `roundTube` | Round tube (mitre-aware) | 1D | diameter, wall, length | | Frame profiles | `extrusion` | T-slot extrusion (aluminium framing, 8020 style; mitre-aware) | 1D | width, height, length | Type aliases accepted when parsing CSV: `sheetmetal`/`sheet_metal`/`sheet metal`, `flatbar`/`flat_bar`/`flat bar`, `profile`/`t-slot`/`tslot`/`8020`/`80/20`/`aluminium profile` (extrusion), `dowel`/`dowels`/`wood_round`/`wood round`; strings containing `sheet`, `square`, `round`+`tube`/`pipe`, `round`/`rod` map to sheet, squareTube, roundTube, round. Anything else is `rectangular`. ## 10. Web-app solver options and file exports (to guide users) These exist in the web app at https://opifico.com (documented in the in-app Guide: https://opifico.com/guide). The agent tools above expose only the subset in section 4. 1D linear cutting (https://opifico.com/1d-solver): blade kerf (default 2.5 mm), trim edges, minimum leftover, offcut length filter, mitre cuts (shapes trapezoid, parallelogram, left-only, right-only, square ends; angles 5 to 90 degrees; workflows "In-line shared" and "Square first", optional 180 degree stock flip), cheapest stock mix (Pro). Method: column generation with an LP master and knapsack pricing, then an integer program (OR-Tools GLOP and SCIP). 2D sheet nesting (https://opifico.com/2d-solver): cut pattern Guillotine (panel saw, solver `wood_guillotine`) or Free-form (`wood_nonguillotine` for sheet goods with a bit diameter, `laser_nonguillotine` for sheet metal with part spacing; a Pro option); kerf (default 3.5 mm); priority Balanced / Fastest work / Biggest offcuts; Pro search; grain direction; panel turnaround; trim edges; edge banding; cheapest stock mix; advanced saw controls (cut order, recut depth 2 to 4 stages). Guillotine: pattern pool plus a CP-SAT master with lexicographic objectives (unplaced area, material cost, then offcuts and saw work). Free-form: MaxRects and block constructions, large-neighbourhood search, set-cover CP-SAT, shared-edge cuts. Project Solver (https://opifico.com/project-solver): solves every class in a saved project with per-class cutting setup, supplier sourcing ("Use supplier", "Use inventory", "Leave out"), saves offcuts, combined PDF report, DXF per sheet class, panel-saw CSV. Stock sources in the 1D and 2D solvers: Use inventory, Enter stock (one-off sizes), Best fit from supplier. Exports: - PDF: 1D (cut sequence with blade angle and fence/stop), 2D (per-sheet cut sequence), whole project. - DXF: AutoCAD R2010, units millimetres (`$INSUNITS` 4). Layers `STOCK`, `PART_CUTS`, `PART_LABELS`, `LEFTOVER`, `CUTS`, `SHEET_INFO`; free-form nesting also writes `NEST_CUTS` (kerf centrelines) and `NEST_ORDER` (cut-order numbers; larger circle = pierce). Sheets are laid out in a grid. - Panel-saw CSV (built in the browser, CRLF): starts with the manifest comment line, then `Part_ID,Length,Width,Quantity,Material,Thickness,Grain,Edge_Top,Edge_Bottom,Edge_Left,Edge_Right,Notes`; grain code `L`, `W` or `N`. It is a parts list (no sheet index or kerf columns). Files: `panel_saw_cut_list.csv`, or `_panel_saw.csv` from the Project Solver. - Homag PTX: Cut Rite V11 interchange file, guillotine layouts only (an error is returned for free-form results). It is the only machine-file export; there are no other machine formats. - Labels PDF: Zebra/Dymo thermal roll 100x50 mm, Zebra large thermal roll 100x70 mm, or A4 sticker sheet 3x7 (21 per page); part diagram, grain arrow, edge-banding spec, Code128 or QR barcode. - Inventory CSV, per-project CSV, library JSON (section 8). - Paste from a spreadsheet into the 1D ("Paste cuts"), 2D ("Paste parts") and project ("Paste parts") tables: tab, `;`, `,` or 2+ spaces; header optional; decimal commas and trailing units tolerated; limits 2 MB and 20,000 rows. Inventory import accepts `.csv` up to 10 MB and 20,000 rows; any row error blocks the import and shows a row/field/message table. ## 11. REST API (alternative to MCP) Base `https://api.opifico.com`. OpenAPI 3.1: `GET https://api.opifico.com/api/agent/spec` (describes the four `/api/agent` tools below with request schemas). - `POST /api/agent/estimate`: free estimate; body as the `opifico_cutting_estimate` parameters (`units`, `cut_type`, `kerf`, `stock[]`, `parts[]`, both required); no auth needed; 120 requests per minute per address. Response fields as in section 3. - `POST /api/agent/solve`: billed solve; body as `opifico_cutting_solve` structured parameters (`units`, `cut_type`, `kerf`, `stock[]`, `parts[]`, `pro`, `priority`, `minimize_cost`, optional `api_key`). Auth: `Authorization: Bearer sk-wood-live-...` (or a signed-in web session token). 401 `api_key_required` without credentials; 402 `insufficient_balance` when the balance is short. - `GET /api/agent/wallet/balance`: balance and the 20 most recent transactions (Bearer auth). - `POST /api/agent/wallet/top-up`: body `{pack: "starter"|"maker"|"pro"|"custom" (default "maker"), custom_amount_usd (>= 5, for custom), success_url?, cancel_url?}`; returns `{checkout_url, pack, price_usd, balance_cents, balance_usd, wallet_id}`, a Stripe Checkout link the user must open and pay. Return URLs must be on an Opifico web origin. - `GET /api/agent/pdf/{solution_id}`: the PDF from a solve. - `GET /api/pricing`: prices, free tier, Pro triggers, packs. `POST /api/quote` (1D) and `POST /api/quote2d` (2D) price a web-format job without charging. - `GET /api/inventory`, `POST /api/inventory`: the user's inventory. `GET /api/projects` (the project files of the Projects library), `POST /api/projects` (`{id?, name?, data}`: create, or replace the classes in `data` on project `id`), `DELETE /api/projects/{id}`: the same projects the web app shows (Bearer API key or web session). - `GET /.well-known/mcp.json`, `GET /llms.txt`, `GET /llms-full.txt` are also served by the API host. ## 12. Worked examples Initialize and list tools (JSON-RPC over HTTP, with the API key header if not using OAuth): ```json POST https://api.opifico.com/mcp {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example","version":"1"}}} {"jsonrpc":"2.0","method":"notifications/initialized"} {"jsonrpc":"2.0","id":2,"method":"tools/list"} ``` Free 1D check (cut four lengths from 2400 mm pine, kerf 3 mm): ```json {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"opifico_cutting_estimate","arguments":{ "units":"mm","kerf":3, "parts":[{"id":"rail","length":1150,"quantity":4},{"id":"leg","length":720,"quantity":4}], "stock":[{"id":"pine","length":2400,"quantity":-1}]}}} ``` Billed 2D solve from CSV (one material; $0.20 for up to 50 pieces): ```json {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"opifico_cutting_solve","arguments":{ "cut_type":"2d","units":"mm","kerf":3.2, "parts_csv":"Part_ID,Length,Width,Quantity,Material\nSIDE,720,560,2,Birch 18\nSHELF,568,540,3,Birch 18", "stock_csv":"Stock_ID,Length,Width,Quantity,Material,Cost\nply,2440,1220,-1,Birch 18,65"}}} ``` Cheaper stock mix (Pro, 1D, needs costs): ```json {"name":"opifico_cutting_solve","arguments":{"minimize_cost":true,"units":"mm", "parts":[{"length":1800,"quantity":6}], "stock":[{"id":"2m","length":2000,"cost":4.0,"quantity":-1},{"id":"3m","length":3000,"cost":5.5,"quantity":-1}]}} ``` Add stock, then cut a saved project as the Project solver would, buying the oak from the supplier list: ```json {"name":"opifico_inventory_update","arguments":{"action":"add","csv_data":"Type,Material,Length,Width,Thickness,Quantity,Price\nsheet,Birch 18,2440,1220,18,4,65"}} {"name":"opifico_cutting_estimate","arguments":{"project_id":"PROJECT_ID","cut_type":"1d"}} {"name":"opifico_cutting_solve","arguments":{"project_id":"PROJECT_ID","cut_type":"1d","supplier_materials":["Oak plank 20 × 150 mm"],"deduct_inventory":true}} {"name":"opifico_cutting_solve","arguments":{"project_id":"PROJECT_ID","cut_type":"2d","deduct_inventory":true}} ``` REST solve: ```bash curl -X POST https://api.opifico.com/api/agent/solve \ -H "Authorization: Bearer sk-wood-live-YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"units":"mm","kerf":3,"parts":[{"length":1150,"quantity":4}],"stock":[{"length":2400}]}' ``` ## 13. Recommended agent workflow 1. `opifico_cutting_wallet_balance` once if billed work is planned; if low, tell the user how to top up. 2. `opifico_inventory_get` (and `opifico_supplier_get` if the user buys stock) to see what exists. 3. Saved project: `opifico_cutting_estimate` with `project_id` lists its material lines, where each comes from and the price; pass `supplier_materials` for lines to buy. Mitres, grain, edge banding, nesting and the other options come from the project's cutting setup (set it with `opifico_project_save` `solve_options`). 4. Loose cut list: group it by material and thickness, build `parts_csv` and a matching `stock_csv` per group, and run `opifico_cutting_estimate` (free); fix infeasible parts first. 5. `opifico_cutting_solve`. Present the schedule table, yield, waste, `unplaced_parts`, the buy list and the PDF link. Mention the cost charged. 6. Optional: `opifico_project_save` to store the job, `deduct_inventory` to book the stock out. 7. For labels, DXF, panel-saw CSV or Homag PTX, direct the user to the web app guide: https://opifico.com/guide?tab=files. Legal: https://opifico.com/terms, https://opifico.com/privacy, https://opifico.com/disclaimer (layouts are a mathematical guide; the operator is responsible for safe cutting), https://opifico.com/licenses. Support: support@opifico.com.