Brick Work API v2: contract
Status: the contract of the whole v2 surface, written for M.11a (issue #113, 2026-10-27). The transport, the meta endpoints and item addressing are implemented in M.11a, the reads in M.11b, the writes in M.11c and the orders, jobs, sources, sync, imports, identifications, tokens and admin operations in M.11d, and the MCP and the parity test in M.11e: every operation of the contract is implemented (a piece that has not landed answers 501 not_implemented, none is left). The machine-readable twin is openapi-v2.yaml; the data model behind it is data-model-design.md (sections 3.1, 3.2, 3.5, 3.6, 3.8 and 7).
How to read this file. v2 is v1 with the 4.0 identity, not a redesign (api-v1.md is the v1 contract and stays as the reference for what v2 inherits). Section 1 says what changed and what did not. Section 2 holds the conventions, section 3 the resource catalogue with every v1 operation mapped to its v2 operation (or dropped, with the reason), section 4 the pieces and the 501 list. Each operation carries the piece that implements it: M.11a the contract, transport, meta and item addressing; M.11b the reads; M.11c the writes; M.11d orders, jobs, sources, keys, sync, imports, identifications, tokens and admin; M.11e the MCP regenerated from this file and the parity test (no operations of its own).
1. What changed from v1, and what did not
v1 answers 410 Gone on the 4.0 line (its /health excepted) because it cannot represent local or unidentified items; the 410 text names /api/v2 and this file.
Unchanged (a client changes only how it names items): envelopes (data, page, write receipts), cursor pagination and the filter vocabulary, ETag / If-Match, Idempotency-Key, problem+json errors with stable codes, bearer tokens with the read < write < admin scopes, BW_API_TOKEN, the throttle on failed token checks, read-only instances, archive instead of delete, one event per write with via=api and actor = token name, and Undo through POST /events/{id}/undo. The code behind them is shared with v1 (brickwork/api/transport.py).
Changed:
| Topic | v1 | v2 |
|---|---|---|
| Item address | /items/{type}/{no}, BrickLink type letter and number |
/items/{id} (Brick Work id, per install) and number paths: /sets/by-number/{no}, /parts/by-number/{no}, /minifigures/by-number/{no}, GET /lookup?provider=&kind=&key= |
| Item in a body | item_type, item_no |
item: {id, kind, name, unidentified, bricklink: [{type, no, color_id, preferred}], rebrickable: {key} or null} |
| Colour in a body | color_id (BrickLink) |
color: {id, name, rgb, bricklink_id, rebrickable_id} |
| Item in a request | item_type, item_no, color_id |
item: {id} or item: {provider, kind, key} and color in the same provider space; 422 unidentified when the number resolves to nothing |
The ? type |
placeholder type for unidentified items | gone: unidentified: true |
| Unresolved matches | /unresolved, /id-matches |
links: /links?status=to_check, /items/{id}/links, /items/{id}/provider-status, merge and unmerge |
| Merged ids | none | 307 to the survivor on GET, 409 merged on writes |
| Codes mapped to words | some stored as letters | words stored and served (Q8); the v1 mappings are dropped |
| Wish ids | integers | uuids (Q6) |
| Copy body | no part-list state | adds inventory: {source, version, ordinal, of, pending, changed_since_build} |
| Settings | v1 whitelist | adds prefer_bricklink_inventory, candidate_sets_common_max and the picture order inside source_priority |
| Catalogue status | loaded, counts | builds (dump, mapping version, local mapping manifest, BrickLink list), coverage per link status, last reconcile |
| MCP | tools take BrickLink type and number | regenerated from this file; tools take numbers and a provider, never a Brick Work id; tool names stay where the operation exists |
Ids are per install (data model 3.1). A Brick Work id from one install means nothing in another, and a restore from a different archive can renumber. Clients store provider numbers and use ids only within a session.
2. Conventions
2.1 Identity and addressing
| Thing | Address | Notes |
|---|---|---|
| Item | /items/{id}, a canonical integer (no sign, no leading zero; anything else is 404, so a redirect's Location never equals the URL it answers) |
Catalogue ids are running numbers from the catalogue build (up to six digits today); local items (BrickLink-born, API-born, unidentified) start at 1,000,000,000. An item the collection has not projected yet is still addressable: the read falls back to the attached catalogue. |
| Item by number | /sets/by-number/{no}, /parts/by-number/{no}, /minifigures/by-number/{no}, GET /lookup?provider=&kind=&key= |
Same item body as /items/{id}, answered with 200, not a redirect, so a client needs one round trip. |
| Colour | color query parameter or body member |
In the provider's space (below). GET /colors/{id} takes a Brick Work colour id. |
| Copy, stock line, lot, storage, place, flag | uuid text | As v1. |
| Wish | uuid text | Was an integer. |
| Wishlist | text id (default for the first) |
As v1. |
| Event, job, order, order line, instruction file | integer | As v1. |
Number paths and /lookup (decisions 2 to 4 of #113):
providerisbricklink(the default) orrebrickable. BrickLink is the default because every current client, order e-mail and the House API speak BrickLink numbers. A set number is the same at both providers, so a set is looked up at both and the answers merge.kindisset,part,minifigure,instructions,box,gear,bookorcatalog; the three number paths fix it. Rebrickable has numbers only for sets, parts and minifigures; another kind withprovider=rebrickableis422.- A BrickLink number resolves through the item's links (every confirmed link:
manual,matched,inferred) and through a BrickLink-born local item's own number. A Rebrickable number resolves through the born key. - A BrickLink figure number the catalogue holds as a part (#150). BrickLink files the old Technic figures (
tech028) as minifigures, where the catalogue links the same number to a Rebrickable part. Withprovider=bricklinkandkind=minifigure, a number that no minifigure holds but exactly one part holds, as that very same BrickLink number, answers with the part (kind: part). A catalogue minifigure always wins; two parts keep the answer404; no other number is tried. An order line or a BrickLink subsets list that names such a figure becomes a part line in no colour. - Colour-scoped links. With
color, the colour is read in the provider's space (bricklink: a BrickLink colour id,rebrickable: a Rebrickable colour id) and the lookup is the inverse of the resolution function (data model 3.2): a link scoped to that colour applies, an unscoped link applies only when the item has none scoped to the colour (the leg rule), and amanuallink decides alone. The answer then carriesdata.color, the resolved Brick Work colour, so a client can map provider colours. Withoutcolor, a link of any scope matches. - A BrickLink-born local item's own number is an unscoped claim that follows the same precedence: a
manuallink for the number decides alone, and the item does not answer by its born number in a colour it has a scoped link for. Across different items a colour-scoped link and an unscoped one both apply, so they are409 ambiguoustogether. - One match is
200with the item body. Several is409 ambiguouswithcandidates(item bodies) and the problem fields; none is404. A merged item found by number is followed to its survivor silently. - Unknown query names are
400 unknown_filter. A colour that names no colour, or a badkindorprovider, is422 validation_failed.
Colours in a request are in the provider's space (decision 4): a BrickLink colour id with provider=bricklink, a Rebrickable colour id with provider=rebrickable, and a Brick Work colour id with provider=brickwork. The brickwork space is for paths that already name the item by id (/items/{id}/...); on a number path it is 422, since a Brick Work id is not a number.
Item and colour bodies (data model 7):
{"id": 15011, "kind": "set", "name": "...", "unidentified": false,
"bricklink": [{"type": "S", "no": "6081-1", "color_id": null, "color_scoped": false, "brickwork_color_id": null, "preferred": true}],
"rebrickable": {"key": "6081-1"}}
bricklink lists the confirmed BrickLink links, preferred first; a colour-scoped link names its BrickLink colour in color_id and has color_scoped: true, an unscoped one has color_id: null and color_scoped: false. A link scoped to a colour that has no BrickLink id also reads color_id: null, but with color_scoped: true: it never reads as "every colour", and brickwork_color_id (set exactly when color_scoped) names the scope in Brick Work's own space. (color_scoped and brickwork_color_id are additions to the design 7 shape, made for this case.) A BrickLink-born local item with no link of its own lists its born number. rebrickable is the born key or null. unidentified is true for a local item that is not born at Rebrickable (data model 3.8): no born key, or a BrickLink-only number. Every field of a row that names an item (item, child, parent_minifig, part, figure) carries this body, and every colour (color) the colour body {id, name, rgb, bricklink_id, rebrickable_id}.
Merged ids (decision 5, amended 2026-10-27):
GETof a merged id (any/items/{id}...path) answers307 Temporary RedirectwithLocationto the same path under the survivor (the query string is kept),Cache-Control: no-storeand a problem body (code: moved,survivor).- This deviates from data model 3.6 and 7, which say
301. A merge can be undone exactly (unmerge restores every moved row), and a cached permanent301would keep sending the unmerged id to the old survivor.307also keeps the method, though onlyGETredirects. - Resolving an id and serialising its item are one read snapshot, so a merge that commits in between never mixes two states in one answer.
- A write that names a merged id is
409 mergedwithsurvivor; nothing is written, since a client that silently writes to a moved id would surprise.POST /items/{id}/unmergeis the one write that names a merged id on purpose.
2.2 Value formats
As v1: money is a decimal string with a currency code ^[A-Z]{3,8}$; time is ISO-8601 UTC with offset; dates the user types are stored as given. Stored words are served as stored (Q8): conditions new, used (stock adds damaged; wishes new, used, any), completeness sealed, complete, incomplete with completeness_source manual or derived, kinds as above. The v1 code-to-word mappings are gone because nothing is stored as a letter any more.
2.3 Envelopes
- Single read:
{"data": {...}}withETagon copies, stock and wishes. - List:
{"data": [...], "page": {"next_cursor": "...|null", "limit": 50, "total": 123|null}}. - Write:
{"data": {...}, "event_id": 412, "undo_url": "/api/v2/events/412/undo", "idempotent_replay": false}. - Queued work:
202 Accepted, the write envelope with theJobasdata, headerLocation: /api/v2/jobs/{id}. - Every response of the API carries
Cache-Control: no-store.
2.4 Resource states
As v1: archived rows are hidden from lists (archived=true|include|only), a GET of an archived record returns it with archived_at, writes to one fail with 409 archived.
2.5 Pagination, filters, sort
As v1: opaque cursor, limit 1 to 200 (default 50), sort and direction with a stable tie-break, unknown filter names are 400 unknown_filter. The filters that named an item by BrickLink type and number change: item_type becomes kind, item_no becomes number with provider, and color_id / colour become color (in the provider space, default bricklink; provider=brickwork takes a Brick Work colour id). type on GET /items is kind.
2.6 ETag and If-Match
As v1: copies, stock and wishes carry an ETag; PATCH, PUT and DELETE accept If-Match; a mismatch is 412 precondition_failed and nothing is written. POST /copies/{id}/inventory accepts If-Match as well, since a switch rewrites the copy's lines.
2.7 Idempotency
As v1: Idempotency-Key: <1 to 80 characters of [A-Za-z0-9-]>, stored in the one event row of the request; same key and body replays the stored response (idempotent_replay: true), another body is 409 idempotency_conflict, a key is honoured for 30 days. Fingerprint version: the request fingerprint (method, path, sorted query, body, file hash) carries a version v (now 1), and the receipt stores the version it was made under; a replay is compared under the receipt's own version, so a later change to what is hashed bumps v and old receipts keep replaying. Required on: creates (POST /copies, /stock, /wishes, /found, /stock/booklets, /lots, /wishlists) and the wishlist edits (PATCH and DELETE /wishlists/{id}, POST /wishlists/{id}/restore and /transfer), quantity moves (/copies/{id}/take, /stock/{id}/give, /wishes/{id}/to-owned, /storages/{id}/contents, /copies/{id}/parts/bulk), orders (/orders/documents, /orders/{id}/allocate, /orders/{id}/allocations/retry), prices (/prices/observations, /items/{id}/prices), /events/{id}/undo, and the v2 identity writes (PUT /items/{id}/links, POST /items/{id}/merge, POST /items/{id}/unmerge, POST /items/{id}/change-variant, POST /copies/{id}/inventory). A missing key is 428 idempotency_key_required. Optional elsewhere.
2.8 Errors
Media type application/problem+json, as v1:
{"type":"urn:brickwork:error:ambiguous","title":"Ambiguous","status":409,"code":"ambiguous",
"detail":"bricklink part 3001 names 2 items; address one by id",
"candidates":[{"id":44280,"kind":"part","name":"...","unidentified":false,"bricklink":[],"rebrickable":{"key":"3001"}}],
"instance":"/api/v2/parts/by-number/3001"}
Optional members: retry_after, job_id, source, and in v2 candidates (with ambiguous), survivor (with merged and moved), piece and error (with not_implemented). Codes and statuses are in Appendix A; x-error-status in the OpenAPI file lists the same table. A path under /api/v2 that does not exist is 404 not_found and a wrong method 405 method_not_allowed with Allow, both problem+json.
2.9 Authentication, scopes, tokens
As v1: Authorization: Bearer <token>; read < write < admin; BW_API_TOKEN is a bootstrap admin token named bootstrap; stored tokens are hashed, scoped and may expire; with neither, 503 api_disabled. /health and /openapi.yaml need no token. BW_READ_ONLY makes every non-GET 403 read_only_instance whatever the scope. Failed token checks back off per client (429 too_many_attempts, Retry-After); forwarded addresses count only with BW_TRUSTED_PROXIES. The scope of every v2 operation is the one its security entry in openapi-v2.yaml names; the blueprint reads it from there, so the contract is the single place that says who may call what. Request bodies are refused unread over their cap (JSON 4 MB, uploads per route) with 413 payload_too_large.
2.10 Reads (M.11b) and writes (M.11c)
What the 60 read operations (M.11b) and the 67 write operations (M.11c, further down) do beyond v1, and where the first contract text changed when they were built (each is also in openapi-v2.yaml).
- The query is the contract's. A query name an operation does not declare is
400 unknown_filter, a name given twice is400 bad_requestunless the contract declares it an array (copy_idon the exports).limit,direction,cursorandsortare accepted where the operation lists them. - Filters on an item or colour take the provider space of decision 4:
number(withprovider,bricklinkby default,rebrickable) finds the live items that number stands for in any kind, or inkind;coloris read in the sameproviderspace, andprovider=brickworkis forcoloronly (a Brick Work id is not a number:numberwith it is422). A filter that names nothing is an empty list; a colour that does not exist is422. - Lists are filtered and sorted over the rows the screens use, and paged with the cursor of v1: it holds the sort position, never an offset, so rows that arrive meanwhile never move a page.
page.totalis the exact count of the filtered rows (v1 left it null underqandarchived); the SQL-paged histories (/events,/items/{id}/prices/history) givetotal: null(a long log is paged by its key, never counted), and/catalogue/itemsis paged in SQL with the exact count of the entries that match.archived=true|include|onlyas before;status=unknownis a copy with no recorded completeness;storage,place,lottakenullfor none. - Rows that name an item or a colour carry the bodies of section 2.1, built for a whole page in a fixed number of queries. The tie-break of every sort is the row's key, so a page is stable.
etagis in the body of a copy, stock line or wish on lists and single reads; theETagheader is on the single read. It is a hash of the stored row and its flag ids, so a change of the item's name does not move it.- A merged id is
307on every/items/{id}/...path and onGET /minifigures/{id}; theLocationkeeps the rest of the path and the query. - Corrections to the first contract text, found by validating every answer against it: the id of an
Imageis the picture number or the 32-character hex id of an own photo (/images/{id}/contenttakes either);Lot.created_dateiscreated_at;StaleItem.source_tableiscopy,stockorwish(the 4.0 tables);ItemLinksayscolor_scopedandbrickwork_color_idas the item body does;ItemDetailalso carriesused_in,gapsandmatches; the unpriced lines of a copy's part-out value are{item, color};GET /export/missing.jsonnames its lines by item and colour body with the BrickLink type, number and colour number beside them, and lists the lines the XML must leave out underskipped;GET /wishlistspages like every list; the virtual location "Not stored" has the idunstored(nullstays the filter spelling);has_fileonGET /instruction-filesis whether the PDF is on disk. - Gaps of a missing booklet or box name the copy's set as their item (
targetinstructionsorbox,target_idthe copy id); every other gap names the item that is missing. - The catalogue is the attached
catalogue.db, not v1's BrickLink file.GET /catalogue/itemsfinds a text by number (the Rebrickable number, then any linked BrickLink number) and by name; without the file the answer is424 catalogue_not_loaded.
Writes (M.11c). Every write calls the core writer that owns the change, in one transaction with its answer; a write that has no core writer got one beside the code it belongs to (checker.mark, checker.set_checked, checker.mark_stock_parts, admin.attach_instruction, the colour scope of identity_admin.add_link, the BrickLink preference in admin.patch_settings).
- The request is checked against
brickwork/api2/write_contract.json, generated fromopenapi-v2.yamlbypython -m scripts.generate_api_writes(the gate fails when it differs). An object that lists properties is closed: an unknown member, a wrong type, a value out of its enum, pattern or range is422 validation_failednaming the member; wrong media type is415, a body that is not a JSON object400, a query name the operation does not declare400 unknown_filter. The Idempotency-Key is read after the body, so a malformed body is422before a missing key is428. - An item in a body is an
ItemKey: byid(a merged id is409 mergedwith thesurvivor) or byprovider,kindandkey(bricklinkunless named). A number that names no item, or an id that does not exist, is422 unidentified; one that names several is409 ambiguouswith thecandidatesas item bodies; a kind Rebrickable has no numbers for is422. A colour next to it is read in the provider's space of the key and in Brick Work's own space when the item is named by id. On a path that names the item (POST /items/{id}/prices) and onPATCH /wishes/{id}the colour is a Brick Work colour id. - Creating goes through the Add flow (
add.resolve,validate_edits,confirm), so an API copy has the part list the web would give it and its catalogue import is queued for the token that asked (the embedded worker is woken after the commit).decisionisanother(add although the set is owned or wished),skipor absent. Absent on something already owned or wished is409 conflict;skipon it is200with{data: {skipped: true, existing: <the item's body>}, event_id: null}and writes nothing (the client asked for the skip, so it is an answer, not an error; changed in the follow-up to M.11c),skipon something new adds it (201). Loose stock merges into an identical line (the answer lists that line's id);POST /stockof a set andPOST /copiesof anything but a set are422. A new wish for a list and an item and colour already wished adds to the wished quantity ondecision=another. - The envelope.
event_idis the last event of the write that a registered undoer takes (else its last event),undo_urlis that event's Undo (left out when nothing takes it). Undoing that event undoes the write whole: the undoable events the same request made before it, newest first, in one transaction (so Undo ofPOST /wishes/{id}/to-ownedtakes the copies away and puts the wish back). A preview (dry_run) and a write that changed nothing haveevent_id: nullwhere the contract allows it (WrittenBulkResult, and, since M.11c,WrittenSettingsandWrittenItemLink). - Idempotency is stored as a
bw_apieventop:<key>in the transaction of the write, per token, for 30 days; a failed write leaves no record, so the same key can be sent again. The record is hidden from/events(no values). A preview stores nothing. The wishlist writes require the key as well (the contract says so; section 2.7 lists them since M.11c). If-Matchis checked in the write's transaction, after the404and before409 archived. A restore of a record that is not archived is409; every write to an archived record is409 archived.- Marks of a copy's lines (
PATCH /copies/{id}/parts/{part_id},POST .../parts/bulk,PATCH .../figures/{item_id},PUT .../checked) are onepart_checkevent of the shape a check answer writes; Undo is refused (409 undo_blocked) when the lines changed after it, so marks are undone in the order they were made. Each count is bounded 0 to the line's quantity on its own. A figure's own parts areparts/{part_id}too and never change the set's derived completeness; a whole missing figure does.parts/bulknames the set's non-spare lines by item and colour:mode=setreplacesmissing,mode=addadds to it (over the quantity is422, nothing written); a line that is not there, or a number that names no item or several, is listed underskippedwith itsfield(items[n]) andcode(not_found,unidentified,ambiguous);dry_runreports the same without writing.PATCH /stock/{id}/partstakesspareas 0 or 1 (null is 0); its event is acollectionreceipt over the loose figure's parts. POST /copies/{id}/checktakes the checker'soperationid from the Idempotency-Key (a random one without it) and answers the fullCheckState, asGETdoes; a repeated operation answers from the checker's own receipt, which is then the event.- Take, Give, Found, booklets answer a
Receipt(event_id,message); Take of a whole minifigure with a colour is422; a stock or gap that changed is409 stale_match. - Storages:
DELETEof a storage that holds things, or that other storages sit in, is409 conflictunlessmove_to_unstored=true(it never moves storages); the idunstoredis the target ofPOST /storages/unstored/contents. Flags: the app's own status flags (system) are409to rename or archive. An archived place or flag keeps its references; restoring brings them back. - PDFs.
POST /instruction-filesis multipart withitemas JSON text ({"id": 15011}),labelandfile; the PDF belongs to a set, a booklet names the set with its number. A photo (POST /copies/{id}/photos,/stock/{id}/photos) is an image up to 15 MB; the copy's or line'simage_idstays the cached picture, the own photo is in/items/{id}/images. - Settings.
GET /settingsreadsadmin.settings;source_priorityis{step: {kind or "*": [source ids]}}overpictures,metadataandmarket_price(a step that is sent replaces that step),prefer_bricklink_inventoryis the global switch of design 4.4,candidate_sets_common_max(a whole number of 25 or more, default 300) is the most sets a part may be held by and still count in Lot Detective (#200). A key outside the whitelist is422 unsupported_setting. Changing settings needsadmin. - Links, merge, unmerge.
PUT /items/{id}/linksmakes sure the item has the BrickLink link as amanualone (a link to check is confirmed; one scoped to a colour is its own row, in the BrickLink colour space) and, withpreferred: true, makes it the preferred one of its scope; the BrickLinktypemust be the item's. It is idempotent per (type, number, colour): putting what is there writes nothing.preferred: falsemeans no change to the preference: a preference is replaced by marking another link preferred, never cleared to none (there is always a resolver order).POST /items/{id}/mergenames the loser in the path and the survivor as anItemKey;Merge.receiptis{event_id, counts}.POST /items/{id}/unmergenames the merged id in the path;merge_idis optional and must belong to it; an unmerge has no Undo (merge again). No M.11c operation writes provider status: a person resolves a to-check row by a link or a merge. - Change variant (#151).
POST /items/{id}/change-varianttakes{"to": <ItemKey>}and names the old variant in the path; both are catalogue figures (or parts, or other items) of one kind, neither merged. Every owned row moves from the item in the path toto, keeping its id: loose stock, wishes, the figure lines of copies (a copy keeps its part list; a line that named a catalogue listtodoes not have becomes amigratedone) and the part lines of loose parts. BrickLink's number (the old item's preferred one, else the one link to check onto) becomesto's preferredmanuallink, and the old item's link of it becomesto_check; the old item is left as it is.VariantChangeis{from, to, number, receipt: {event_id, counts}}. One event: its Undo (POST /events/{id}/undo) puts every row and link back, and is409once any of them changed. Refused with409: a merged item on either side (mergedwithsurvivor), another kind, a set, the same item, a job in flight for either, a copy, loose figure or part list with a line for both items, a row holding a fetched part list of the old item, and a live stock row or wish that would meet a live one oftoon its key (nothing is added up). - Sets versus a pile of parts (#199).
GET /planning/set-scoresranks the catalogue's sets by how much of a pile of loose parts each explains: give exactly one oflot,storage(with the storages inside it),loose=trueorstock(repeated line ids), else422; an unknown lot or storage is404, a stock id that is not a live loose part or figure422.limit(1 to 100, default 20),offsetanddetail(matched lines per set, 0 to 50, default 10) page and size the answer; the ranking is cached per catalogue build and input, so a later page is cheap. Each set carriesevidence(the sum, over the lines it holds, of the rarity weight ln(N/df) times the covered share of the set's quantity),coverage(evidence over the set's own total weight), covered, complete and distinctive lines, thematchedlines with the stock flags passed through (flagssums them per flag) andswaps(the same part in another colour or as an alternate mould, listed apart and in no number). It reads and writes nothing, so a read token may call it, and it is a GET: an arbitrary parts list goes through the Python servicebrickwork.core.set_scoring.score. The formula and the decisions are ADR 0026; the how-to for developers is the reference page Set scoring. - Which sets came in this lot (#200).
GET /lots/{id}/candidate-setsranks the sets a lot may have come from with the engine ofGET /planning/set-scores, read-only; an unknown lot is404.scoreis 0 to 100: the set's evidence over the best candidate's, so the best set that passes the set filters scores 100; it is not a probability.reasonsare sentences ("7 of 9 distinctive parts found", "2 printed parts only in this set"),distinctive_found_partsanddistinctive_missing_partslist up to 20 distinctive parts each (held by at most 25 sets) with their picture (id,content_urlof the newest local one,image_urlof the catalogue). Filters:min_score,theme(a catalogue theme id or name, with its sub-themes; an unknown one is422),year_from,year_to,parts_min,parts_max(the catalogue's part count),ignore_common(default true: lines held by more thancandidate_sets_common_maxsets are dropped before scoring) andlimit(1 to 100, default 20) andoffset, which page after the filters. A bad boolean is400 unknown_filter, any other bad value422. The rules and the code are in Set scoring. - Value and gain (#57).
GET /valuation(MCP toolget_valuation) answerscopies(every live copy, in set-number order) andstatistics(the Paid vs value figures of Statistics), all in the display currency of Settings. A copy haspaid(its price paid,nullwithpaid_reasonno_price_paidfor none or 0.00, orno_rate),worth(nullwithworth_reasonno_quoteorno_rate) andgain(worth minus paid, negative for a loss, with its whole percent of the price paid;nullunless both are known). A money figure is{amount, currency, converted, rate, rate_as_of, original}: a converted one carries the rate used, its date and the amount as recorded, and a missing rate is anullfigure with the reason, never a guess; two currencies never share a figure.worth_sourceisoverride(the copy's own value) orusedornew(the sold average of its set, chosen as the Prices card chooses it, for the copy's condition and completeness; a new or sealed copy is priced new, one with a missing or damaged line has the incomplete-set discount taken off, as the set page does,worth_discount_pct), withworth_date(the day of the quote),worth_salesandworth_stale(the Prices card's rule).statisticscounts only the copies that have both a price paid and a worth (comparable_copies;enoughis three or more), so the gain compares like with like;without_price_paid,without_quoteandwithout_ratecount the rest apart, andby_themeandby_yearlist{label, paid, worth, gain, percent, copies}(a purchase year that is not recorded isNot recorded).GET /export/insurance.csvis the insurance report: every live copy, one row each, columns Set number, Name, Year, Condition, Completeness, Purchase date, Price paid, Price paid currency, Current value, Current value currency, Value source, Value date, Photos (the copy's own photos, a count) and Remarks; text cells are made safe for a spreadsheet. Both are read-only. The column set is a proposal until the Discussions say what insurers ask for. - Stickers (#201).
GET /lots/{id}/stock/{stock_id}/stickerslists the sets whose latest inventory holds the part line's part in its colour and a sticker sheet (a part of the categoryStickers), with the sheet and its picture, and the line'sstickerstate (flagged,confirmed).POST /lots/{id}/stock/{stock_id}/stickerwithset_item_idandsheet_item_idfrom that list confirms the choice (409when the line has no Sticker flag,422for a pair that is not a candidate);DELETEon the same path is "Not a sticker": it removes the flag and stops counting a confirmation. Both are one undoable event, an unchanged state writes nothing (changed: false,event_id: nullwhen there is no event) and the Idempotency-Key is optional. The candidate-sets answer lists each set'sswaps. Filing withsticker: trueis a Part Spotter (web) matter; the API sets the flag withPUT /stock/{id}/flagsas before. See Set scoring for the evidence rule. - The copy's part list.
POST /copies/{id}/inventorytakessource,version(Rebrickable; default the latest) and, new in M.11c,remember(also the set's preference for new copies, in the same event). BrickLink's list without a key is424 source_not_configured; with no fetched list the answer is202withLocation: /api/v2/jobs/{id}and the copy waits (inventory.pending); a copy already waiting, or already on that list, is409.DELETE .../inventory/pendingcancels the wait and puts back the preference it remembered. - Refusals of the 4.0 store are problems: a state that does not allow the change (nothing to switch, a marked copy, a pending change, a refused merge) is
409, a missing catalogue424, the rest422; the words are the refusal's own with folders cut. - Corrections to the first contract text of the writes:
WishlistTransfer.idsare texts (wish ids are uuids);Settings.source_priorityis nested as above;CopyInventorySwitchhasremember;WrittenSettings.event_idandWrittenItemLink.event_idmay be null;InstructionUpload.itemis JSON text in the form;PriceCreate.colorandWishPatch.colorare Brick Work colour ids;LooseFigurePartKey.sparenull reads as 0.
Orders, jobs, sources, sync, imports, identifications, tokens and admin (M.11d). What the 57 remaining operations do beyond v1, and where the first contract text changed when they were built.
- Detached writes. A write that reaches the network (a key test, a connect, the ECB feed, a pulled order, a sync plan or send, a photo identification, the catalogue import, a drain) or runs long (a BrickTracker plan) runs outside any transaction; the core writers open their own short ones, and the answer is stored in one more. A claim row (
api:claim:<token>:<key>, 15 minutes) makes a second request with the same key while the first is out409 idempotency_conflict; a call that fails stores no receipt and releases the claim, so the key can be sent again. A reply is replayed before any network call. An exhausted budget is429 budget_exhausted(a remote throttle429 source_throttled) withRetry-After; a provider's reply that cannot be used is502 source_error. Where a call changes nothing in the collection (a key test, a plan)event_idisnull. An order imported by BrickLink number fetches first and imports in the transaction that stores the answer, so it is whole or not at all. - A body whose members are all optional may be left out (
POST /jobs/{id}/retry,POST /items/{id}/import,POST /items/{id}/prices/refresh): an empty body is{}. A body that holds key material or a password (PUT /sources/{source}/key,PUT /sources/{source}/user,POST .../user/connect) is fingerprinted with the keyed digest, never a plain hash, and needsBW_SECRET_KEY. POST /colors/syncis dropped (section 3.3): the BrickLink colour table comes with the catalogue builds since M.8f.POST /catalogue/importloads the newest folder of BrickLink item lists beside the collection and reconciles (the page's Load lists) and answersCatalogueStatusas the 4.0 catalogue is: whether it is loaded, its items, the builds the collection activated, the link coverage per status and the last reconcile.- Orders.
Ordercarries the counts andphasethe Orders page shows (lines,pieces,received,allocated,held).OrderLinenames its item and colour as the bodies of section 2.1 (nullwhile the importer could not place it, and thenunplaced_reasonsays why in words,nullwhen placed;rawholds the provider's words andsuggestionsthe items the line could be, best first: the ones a name that matches several items names, or, for a name no item has exactly, the nearest names of the loaded BrickLink item list, a longest-prefix match first and then names that hold all its words (#155); a suggestion is never placed by itself). A match names the item as anItemKeyand the colour in its provider space (color); a part needs its colour. A BrickLink number that no item holds but the loaded BrickLink item list names (an instruction booklet, a BrickLink-only part) becomes a local BrickLink-born item, at import and when matched by number (#154); an import's Undo leaves that item, unused. A line or document that is not in the order is404; the core's refusals (a stale count, a gap that changed, a document in the wrong phase) are422.GET /orders/{id}/allocationslistsAllocationas the 4.0 table has it (target_type,copy_id,stock_id,item,color,intent_before,intent_after,target_label,undo_event_id);.../allocation-reviewthe saved lines with what is available and the gaps they could fill (destinations);.../historythe order's saves from the event log withviaandactor.short.xmlandexport.csvare downloads. Every order write is oneorders:<id>event and its Undo restores the order rows. - Jobs.
Jobis a row ofjobwith itsjob_steprows (interruptedis a running job whose worker is gone).GET /jobspages by id and takesstateandfailed. Retry and resume are for the pipeline jobs (kind: item); the two fetch kinds answer404there. Queueing answers202withLocation: /api/v2/jobs/{id}when it made one job, else aJobList(POST /jobs/pictures,/jobs/instruction-identities;POST /prices/refreshanswers200).POST /jobs/picturestakesscope(missingorall),kindsandexpected_count(the call is refused when the count has changed) and queues onepicturesjob per held item of those kinds that has none running, replacing v1'ssourceandtypes.POST /items/{id}/importtakesstepsonly; the sources of a step are the stored order of its kind. A price refresh of an item with an unfinished job answers that job. - Sources and keys.
Source.keyis{configured, origin, saved_at}and nothing else. The user token or hash of the collection sync is the key store under<source>_user.Undoof a key save, a key removal and a source switch is admin's, as making them was. - Sync and import.
SyncPlanalso carriesremoteand, for a send,state;ImportPlanlistsunplacedlines (notunresolved);SyncAppliedandImportAppliedalso carryevent_id. A plan applied twice is applied once (already_applied), whatever the key. A pull apply is a write-scope operation and so is its Undo; a send apply and an import apply are admin's, and an import's Undo is admin's. The uploaded database is streamed to a private file under the import folder (up toBW_IMPORT_MAX_MB, 200 by default) and deleted whatever happens. - Identifications. A candidate is
{item, score, name}withowned(#45) on the answer of the upload, which also lists the predictions that named no item or several underunresolved. A read of an archived identification returns it witharchived_at(section 2.4, v1 hid it);DELETEof an archived one is409.GET .../thumbis the 256 px JPEG. - Tokens, exchange rates, database. A token's secret is in the answer of its creation only (the replay record keeps it out: a replay of the creation answers
secret: null, so a lost answer means revoking the token and creating another);bootstrapis reserved and cannot be revoked. A rate is a decimal string per EUR;PUTwithnullremoves a hand-entered one and the ECB feed keeps hand-entered rates.GET /database/checkaddscatalogue_integrity_checkwhen a catalogue is attached.GET /database/exportis a snapshot of the collection without the stored source keys and refuses (409) if a known secret would remain in it; it writes nothing. - v1 answers
410 Goneand names v2 on every path butGET /health; its modules and route table are gone.
The MCP (M.11e). The MCP server reads openapi-v2.yaml and calls /api/v2; its 65 tools are the operations that carry x-mcp-tool: the 61 tools of v1 keep their names (decision 7) and get_set_by_number, get_part_by_number, get_minifigure_by_number and lookup_item are new. Its 8 resources are x-mcp.resources. How a client uses it is in mcp.md.
- A tool takes numbers, never a Brick Work item id. The contract marks the item-id path parameters
x-mcp-item: true(/items/{id}forget_item,import_item,get_prices,record_priceandrefresh_price;item_idin/copies/{id}/figures/{item_id}forset_figure_missing) and theItemKeyschemax-mcp-item: key. A tool with a marked path parameter takesprovider(bricklinkby default, orrebrickable),kindandnoinstead, and the adapter callsGET /lookup?provider=&kind=&key=before the call and puts the id into the path. A tool whose body names an item (add_copies,add_stock,add_wish,take_for_gap) takesitemas{provider, kind, key}and has noidmember. The rule is read from the contract, not listed in the adapter. - A lookup that fails is the tool's error, unchanged:
404 not_found, or409 ambiguouswith thecandidates. Nothing is written then. - On
get_pricesthe provider is also the colour space ofcolor(decision 4), soprovider=rebrickablereadscoloras a Rebrickable colour id. Elsewherecolorstays as the operation has it (record_pricetakes a Brick Work colour id, asPOST /items/{id}/pricesdoes). - Collection records keep their ids (copy, stock line, wish, wishlist, storage, lot, event, job, order, plan, purchase location): a client takes them from the list tools.
bricklink_idandbricklink_order_idare numbers of BrickLink and stay. - Resources:
brickwork://items/{provider}/{kind}/{no}isget_lookup(the contract maps the URI's variables to its query); the others are as in v1. - Fixed in the contract:
PATCH /copies/{id}/figures/{item_id}named the figure's parameter with theidof an item path, so two parameters were calledid; it now usesPath_figure_item_id.
3. Resource catalogue
3.1 Summary
201 operations in all (M.11a 9, M.11b 64, M.11c 71, M.11d 57 by piece); v1 had 179 operations.
| Group | Operations | Pieces |
|---|---|---|
| Meta | 4 | M.11a 4 |
| Catalogue | 5 | M.11b 5 |
| Items | 8 | M.11a 1, M.11b 6, M.11d 1 |
| Identity | 11 | M.11a 4, M.11b 3, M.11c 4 |
| Images | 1 | M.11b 1 |
| Copies | 19 | M.11b 6, M.11c 13 |
| Copy parts | 5 | M.11b 2, M.11c 3 |
| Loose stock | 13 | M.11b 3, M.11c 10 |
| Minifigures | 2 | M.11b 2 |
| Instructions | 7 | M.11b 3, M.11c 4 |
| Prices | 9 | M.11b 5, M.11c 2, M.11d 2 |
| Wishlist | 16 | M.11b 5, M.11c 11 |
| Storages | 7 | M.11b 2, M.11c 5 |
| Lots | 7 | M.11b 3, M.11c 4 |
| Places | 5 | M.11b 1, M.11c 4 |
| Flags | 5 | M.11b 1, M.11c 4 |
| Planning | 9 | M.11b 9 |
| Orders | 15 | M.11d 15 |
| Events | 3 | M.11b 2, M.11c 1 |
| Jobs | 7 | M.11d 7 |
| Sources | 6 | M.11d 6 |
| Settings | 5 | M.11c 2, M.11d 3 |
| Admin | 10 | M.11b 1, M.11d 9 |
| Matching | 2 | M.11b 2 |
| Tokens | 3 | M.11d 3 |
3.2 Operations
Scope none means no token. Every path is relative to /api/v2. The v1 column names the v1 operation the row replaces when its path changed, says same when the path is unchanged and NEW when v1 had no such operation. Write rows are one transaction plus one event; the receipt kinds and Undo dispatch per write group are v1's (api-v1.md section 4) and move to this file with M.11c.
Meta
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /health |
Liveness, app, schema and API version. No token. | none | M.11a | same |
GET /me |
The calling token: name, scopes, expiry. | read | M.11a | same |
GET /openapi.yaml |
This contract as OpenAPI 3.1 YAML. No token. | none | M.11a | same |
GET /version |
Build, API and schema versions, enabled features. | read | M.11a | same |
Catalogue
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /catalogue/items |
Search the local BrickLink catalogue file by type and text. | read | M.11b | same |
GET /catalogue/items/{id} |
One catalogue entry. | read | M.11b | GET /catalogue/items/{type}/{no} |
GET /catalogue/status |
Catalogue file state (loaded, item counts, imported_at). | read | M.11b | same |
GET /colors |
All colours. | read | M.11b | same |
GET /colors/{color_id} |
One colour. | read | M.11b | same |
Items
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /items |
Items known to the store, with text search over name, number and properties. | read | M.11b | same |
GET /items/{id} |
Item detail: properties, counts, owned and wished summary. | read | M.11a | GET /items/{type}/{no} |
GET /items/{id}/colors |
Colours this part is known in. | read | M.11b | GET /items/{type}/{no}/colors |
GET /items/{id}/detail |
Item detail: properties, counts, owned and wished summary, prices, matches (what v1's GET /items/{type}/{no} returned). | read | M.11b | NEW |
GET /items/{id}/images |
Local pictures of the item. | read | M.11b | GET /items/{type}/{no}/images |
POST /items/{id}/import |
Create the item identity and queue the six import steps. | write | M.11d | POST /items/{type}/{no}/import |
GET /items/{id}/inventory |
Parts and figures the item is made of (inventory lines). | read | M.11b | GET /items/{type}/{no}/inventory |
GET /items/{id}/used-in |
Owned copies that contain this part or figure. | read | M.11b | GET /items/{type}/{no}/used-in |
Identity
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /items/{id}/links |
The item's provider links with status, method, origin, preferred and colour scope (design 3.2), to-check candidates included. | read | M.11b | NEW |
PUT /items/{id}/links |
Set one manual link of the item, or make one preferred for its colour scope (design 3.2). Idempotent per (provider, type, number, colour). | write | M.11c | NEW |
POST /items/{id}/merge |
Merge this local item into a survivor (design 3.6): every reference moves, the id answers 307 to the survivor afterwards. Refused for a live catalogue item. | write | M.11c | NEW |
GET /items/{id}/provider-status |
What is known about the item at a provider when there is no key: looked and nothing found, to check without candidate, kind differs (design 3.2). | read | M.11b | NEW |
POST /items/{id}/unmerge |
Undo a merge by its receipt (design 3.6): the merged id answers for itself again. Refused if a moved row changed since. | write | M.11c | NEW |
GET /links |
Links and provider statuses across items, by status: status=to_check is the list that replaces v1's /unresolved. | read | M.11b | NEW |
GET /lookup |
Find the item for any provider number: provider, kind and key (and a colour in the provider space). Same body and errors as the by-number paths. | read | M.11a | NEW |
GET /minifigures/by-number/{no} |
Find the item for a minifigure number; the same item body as /items/{id} (decision 2). Default provider bricklink; provider=rebrickable for Rebrickable numbers. Several items answer 409 ambiguous with the candidates, none 404. | read | M.11a | NEW |
GET /parts/by-number/{no} |
Find the item for a part number; the same item body as /items/{id} (decision 2). Default provider bricklink; provider=rebrickable for Rebrickable numbers. Several items answer 409 ambiguous with the candidates, none 404. | read | M.11a | NEW |
GET /sets/by-number/{no} |
Find the item for a set number; the same item body as /items/{id} (decision 2). A set number is the same at both providers. Several items answer 409 ambiguous with the candidates, none 404. | read | M.11a | NEW |
Images
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /images/{id}/content |
Image bytes from the local picture store. | read | M.11b | same |
Copies
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /copies |
Owned set copies. | read | M.11b | same |
POST /copies |
Add owned copies of a set (quantity N makes N copies) and queue its import. | write | M.11c | same |
DELETE /copies/{id} |
Archive the copy (the UI calls this deleting a set). | write | M.11c | same |
GET /copies/{id} |
One copy. | read | M.11b | same |
PATCH /copies/{id} |
Edit condition, completeness, storage, purchase and estimate fields, note. | write | M.11c | same |
GET /copies/{id}/check |
Part-check session state for this copy. | read | M.11b | same |
POST /copies/{id}/check |
Apply one part-check action (start, answer, found_one, skip, select, back, next, spares, recheck, finish, undo; start takes scope all or missing). |
write | M.11c | same |
PUT /copies/{id}/checked |
Set or clear the "Set is checked" flag and recompute derived completeness. | write | M.11c | same |
GET /copies/{id}/completeness |
Stored and derived completeness, checked flag, missing and damaged counts. | read | M.11b | same |
PUT /copies/{id}/flags |
Replace the copy flags. | write | M.11c | same |
GET /copies/{id}/gaps |
Missing, damaged and absent items of the copy. | read | M.11b | same |
POST /copies/{id}/inventory |
Switch the copy's part list in place (design 4.4): to Rebrickable (a version) or BrickLink. A BrickLink switch is pending until its fetch job lands. | write | M.11c | NEW |
DELETE /copies/{id}/inventory/pending |
Cancel a pending BrickLink switch (design 4.4): the copy keeps its current list. | write | M.11c | NEW |
GET /copies/{id}/matches |
Loose stock that would fill the copy gaps. | read | M.11b | same |
POST /copies/{id}/photos |
Upload an own photo (multipart, 15 MB). | write | M.11c | same |
PUT /copies/{id}/pin |
Pin or unpin a copy. | write | M.11c | same |
POST /copies/{id}/restore |
Clear the archive mark. | write | M.11c | same |
POST /copies/{id}/take |
Take N of one missing part (with colour) or whole minifigure (no colour) from loose stock into the copy. | write | M.11c | same |
Copy parts
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /copies/{id}/figures |
Whole figures of the copy. | read | M.11b | same |
PATCH /copies/{id}/figures/{item_id} |
Set the missing count of one whole figure. | write | M.11c | PATCH /copies/{id}/figures/{type}/{no} |
GET /copies/{id}/parts |
Part lines of the copy (required, spare, figure components). | read | M.11b | same |
POST /copies/{id}/parts/bulk |
Apply missing counts to many lines by item and colour (mode set or add, optional dry run). | write | M.11c | same |
PATCH /copies/{id}/parts/{part_id} |
Set missing, damaged, checked for one line (mark missing or found). | write | M.11c | same |
Loose stock
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
POST /found |
Record found parts into copies, a loose line, or a list. | write | M.11c | same |
GET /stock |
Loose stock lines (parts, minifigures, booklets, anything). | read | M.11b | same |
POST /stock |
Add loose stock; identical lines merge. | write | M.11c | same |
POST /stock/booklets |
Add loose booklets from lines "set number, quantity". | write | M.11c | same |
DELETE /stock/{id} |
Archive the line. | write | M.11c | same |
GET /stock/{id} |
One stock line. | read | M.11b | same |
PATCH /stock/{id} |
Edit a stock line (may merge into an existing line; response lists merged ids). | write | M.11c | same |
PUT /stock/{id}/flags |
Replace the line flags. | write | M.11c | same |
POST /stock/{id}/give |
Give N from this line to a copy gap or a loose-figure part gap. | write | M.11c | same |
GET /stock/{id}/parts |
Parts of a loose minifigure. | read | M.11b | same |
PATCH /stock/{id}/parts |
Set missing and damaged on parts of a loose minifigure. | write | M.11c | same |
POST /stock/{id}/photos |
Upload an own photo (multipart, 15 MB). | write | M.11c | same |
POST /stock/{id}/restore |
Clear the archive mark. | write | M.11c | same |
Minifigures
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /minifigures |
Figures by identity: owned in sets, loose, missing. | read | M.11b | same |
GET /minifigures/{id} |
One figure: sets, loose lines, missing, matches, prices. | read | M.11b | GET /minifigures/{no} |
Instructions
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /instruction-files |
Booklet files and booklet identities with coverage. | read | M.11b | same |
POST /instruction-files |
Attach a PDF (multipart, up to 20 MB) to an item. | write | M.11c | same |
DELETE /instruction-files/{id} |
Archive a PDF association. | write | M.11c | same |
GET /instruction-files/{id} |
One file record. | read | M.11b | same |
PATCH /instruction-files/{id} |
Rename the label. | write | M.11c | same |
GET /instruction-files/{id}/content |
Download the PDF. | read | M.11b | same |
POST /instruction-files/{id}/restore |
Restore an archived record. | write | M.11c | same |
Prices
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /items/{id}/prices |
Current prices and recorded observations of one item. | read | M.11b | GET /items/{type}/{no}/prices |
POST /items/{id}/prices |
Record one manual or valuation observation. | write | M.11c | POST /items/{type}/{no}/prices |
GET /items/{id}/prices/history |
Observation history of one item. | read | M.11b | GET /items/{type}/{no}/prices/history |
POST /items/{id}/prices/refresh |
Queue a market price refresh for one item. | write | M.11d | POST /items/{type}/{no}/prices/refresh |
GET /prices |
Current price per item, colour, condition and guide. | read | M.11b | same |
POST /prices/observations |
Record up to 500 observations in one transaction. | write | M.11c | same |
POST /prices/refresh |
Queue refresh jobs for every stale item. | write | M.11d | same |
GET /prices/stale |
Items whose price is older than the freshness rule. | read | M.11b | same |
GET /valuation |
Value and gain per live copy, and the Paid vs value figures of Statistics (#57). | read | M.11b | NEW |
Wishlist
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /wishes |
Wishlist rows; under_max filters to rows priced under the wish maximum. | read | M.11b | same |
POST /wishes |
Add a wish (same colour and item adds to the live row on decision=another). | write | M.11c | same |
DELETE /wishes/{id} |
Archive the wish. | write | M.11c | same |
GET /wishes/{id} |
One wish. | read | M.11b | same |
PATCH /wishes/{id} |
Edit quantity, colour, condition, max price, max price with box and their currencies, note. | write | M.11c | same |
PUT /wishes/{id}/flags |
Replace the wish flags. | write | M.11c | same |
POST /wishes/{id}/restore |
Restore an archived wish. | write | M.11c | same |
POST /wishes/{id}/to-owned |
Turn a wish into owned copies and archive the wish, one transaction. | write | M.11c | same |
GET /wishlists |
get wishlists | read | M.11b | same |
POST /wishlists |
post wishlists | write | M.11c | same |
DELETE /wishlists/{id} |
delete wishlists id | write | M.11c | same |
PATCH /wishlists/{id} |
patch wishlists id | write | M.11c | same |
GET /wishlists/{id}/export.csv |
Export wishlist as Rebrickable CSV | read | M.11b | same |
GET /wishlists/{id}/export.xml |
Export wishlist as BrickLink wanted list XML | read | M.11b | same |
POST /wishlists/{id}/restore |
post wishlists id restore | write | M.11c | same |
POST /wishlists/{id}/transfer |
post wishlists id transfer | write | M.11c | same |
Storages
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /storages |
Active storages with counts, plus the virtual "Not stored". | read | M.11b | same |
POST /storages |
Create a storage. | write | M.11c | same |
DELETE /storages/{id} |
Archive; refuses non-empty unless move_to_unstored=true. | write | M.11c | same |
GET /storages/{id} |
Storage with its contents. | read | M.11b | same |
PATCH /storages/{id} |
Rename. | write | M.11c | same |
POST /storages/{id}/contents |
Move copies, stock and lots into this storage (id "unstored" for none). A lot carries its parts that sit in the lot's own storage, in the same receipt. | write | M.11c | same |
POST /storages/{id}/restore |
Restore an archived record. | write | M.11c | same |
Lots
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /lots |
Part lots. | read | M.11b | same |
POST /lots |
Create a lot from existing stock lines. | write | M.11c | same |
DELETE /lots/{id} |
Archive the lot. | write | M.11c | same |
GET /lots/{id} |
Lot with its stock. | read | M.11b | same |
PATCH /lots/{id} |
Edit name, note, storage, purchase fields. | write | M.11c | same |
POST /lots/{id}/restore |
Restore an archived record. | write | M.11c | same |
Places
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /places |
Purchase locations with counts and totals per currency. | read | M.11b | same |
POST /places |
Create a purchase location. | write | M.11c | same |
DELETE /places/{id} |
Archive, retaining references. Undo restores the place. | write | M.11c | same |
PATCH /places/{id} |
Rename. | write | M.11c | same |
POST /places/{id}/restore |
Restore an archived record. | write | M.11c | same |
Flags
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /flags |
Flags of kind owner, status and tag. | read | M.11b | same |
POST /flags |
Create a flag. | write | M.11c | same |
DELETE /flags/{id} |
Archive, retaining flag links. Undo restores the flag. | write | M.11c | same |
PATCH /flags/{id} |
Rename. | write | M.11c | same |
POST /flags/{id}/restore |
Restore an archived record. | write | M.11c | same |
Planning
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /export/insurance.csv |
The insurance report as CSV: one row per live copy with price paid, worth, source and date of the quote (#57). | read | M.11b | NEW |
GET /export/missing.csv |
Missing parts of the chosen copies as CSV (the printable list, #43). | read | M.11b | same |
GET /export/missing.json |
The same missing-item aggregation and eligibility as XML, returned as JSON. | read | M.11b | same |
GET /export/missing.xml |
BrickLink wanted-list XML of missing items for chosen copies. | read | M.11b | same |
GET /export/picker.csv |
Part Runner results for the chosen copies as CSV, in pick order (#43). | read | M.11b | same |
GET /export/wishlist.csv |
The wishlist (one list or all) as CSV (#43). | read | M.11b | same |
GET /planning/ranking |
Owned copies ranked by cost to complete, with the repair settings used. | read | M.11b | same |
Orders
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /orders |
Orders with line and receipt state. | read | M.11d | same |
POST /orders/documents |
Import an order document: pasted email, BrickLink XML, or fetch by order number with the user key. | write | M.11d | same |
GET /orders/export.csv |
Saved order lines (kind=lines) or one row per order with new and used totals (kind=summary) as CSV, filtered by order date and store (#44). Totals never mix currencies. | read | M.11d | same |
GET /orders/{id} |
Order with documents and lines. | read | M.11d | same |
POST /orders/{id}/allocate |
Allocate received lines to copies or stock. | write | M.11d | same |
GET /orders/{id}/allocation-review |
Saved lines, available received quantities and current copy-gap destinations, paginated. | read | M.11d | same |
GET /orders/{id}/allocations |
Allocation history, paginated. | read | M.11d | same |
POST /orders/{id}/allocations/retry |
Retry reviewed chosen allocations against current counts and gaps. Requires an Idempotency-Key. |
write | M.11d | same |
POST /orders/{id}/documents/{doc_id}/phase |
Move a document: review, reopen or save. | write | M.11d | same |
GET /orders/{id}/history |
Ledger receipts from web and API operations, with Undo state, paginated. | read | M.11d | same |
PATCH /orders/{id}/lines/{line_id} |
Match a line to a catalogue identity. | write | M.11d | same |
PUT /orders/{id}/lines/{line_id}/hold |
Hold or release a line. | write | M.11d | same |
POST /orders/{id}/received |
Record received counts per line. | write | M.11d | same |
POST /orders/{id}/reconcile |
Reconcile the order. | write | M.11d | same |
GET /orders/{id}/short.xml |
BrickLink XML of the still-missing lines. | read | M.11d | same |
Events
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /events |
Audit events, newest first. | read | M.11b | same |
GET /events/{id} |
One event with old and new values. | read | M.11b | same |
POST /events/{id}/undo |
Undo a receipt event. One endpoint for all receipt kinds. | write | M.11c | same |
Jobs
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /jobs |
Import and refresh jobs with steps. | read | M.11d | same |
POST /jobs/drain |
Run one drain of due jobs in the request worker. | write | M.11d | same |
POST /jobs/instruction-identities |
Queue identity refresh for booklets without a file. | write | M.11d | same |
POST /jobs/pictures |
Queue a picture refresh for a scope. | write | M.11d | same |
GET /jobs/{id} |
One job. | read | M.11d | same |
POST /jobs/{id}/resume |
Resume an interrupted or budget-waiting job; a failed job needs retry. | write | M.11d | same |
POST /jobs/{id}/retry |
Retry failed steps (all, or one step). | write | M.11d | same |
Sources
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
POST /catalogue/import |
Rebuild the catalogue file from the configured BrickLink XML directory. | admin | M.11d | same |
GET /sources |
Sources with key state (configured yes or no), budget, usage today, last success and error. | read | M.11d | same |
PATCH /sources/{source} |
Enable or disable a source. | admin | M.11d | same |
DELETE /sources/{source}/key |
Archive stored key; Undo restores it. | admin | M.11d | same |
PUT /sources/{source}/key |
Store an encrypted source key (replaces a stored one). Write-only. | admin | M.11d | same |
POST /sources/{source}/key/test |
One budgeted call with the key in use; the result carries no key material. | admin | M.11d | same |
Settings
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /exchange-rates |
Exchange rates per 1 EUR (hand-entered or ECB) and the display currency (#39). Stored amounts never change currency. | read | M.11d | same |
POST /exchange-rates/ecb |
Load the ECB daily reference rates (one keyless request); hand-entered rates are kept. | admin | M.11d | same |
PUT /exchange-rates/{code} |
Enter (or with null remove) one rate per 1 EUR by hand. | admin | M.11d | same |
GET /settings |
Allowed settings and freshness rules. | read | M.11c | same |
PATCH /settings |
Update allowed settings (whitelist). | admin | M.11c | same |
part_list_grouping is a settings key: grouped (default), bricklink, or none. PATCH uses the same save and undo event as the Settings page. It changes the set detail list only.
Admin
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /database/check |
Integrity, foreign-key check and row counts. | admin | M.11d | same |
GET /database/export |
SQLite snapshot download. Stored source keys, even encrypted, are removed from the copy; refuses (409) if known plaintext credentials would remain. | admin | M.11d | same |
GET /identifications |
Photo identification history; archived rows hidden. | read | M.11d | same |
POST /identifications |
Upload an image for Brickognize identification (image, up to 8 MB). | write | M.11d | same |
DELETE /identifications/{id} |
Archive identification; Undo restores it. | write | M.11d | same |
GET /identifications/{id} |
Identification result. | read | M.11d | same |
GET /identifications/{id}/thumb |
Thumbnail bytes. | read | M.11d | same |
POST /imports/bricktracker |
Upload a BrickTracker app.db (any upstream version, up to 200 MB) and get an import plan. A copy is upgraded with upstream's migrations and compared with this collection; nothing is written to the collection. | admin | M.11d | same |
POST /imports/{id}/apply |
Apply an import plan: rows marked add are written in one transaction; locked rows (already imported, duplicate wishes) are always skipped. Undo with POST /events/{id}/undo (admin). | admin | M.11d | same |
GET /statistics |
Counts by completeness, theme, storage, place. | read | M.11b | same |
Matching
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /gaps |
Missing items over all copies and loose figures. | read | M.11b | same |
GET /matches |
Suggestions: loose stock that fills a gap. | read | M.11b | same |
Tokens
| Operation | What it does | Scope | Piece | v1 |
|---|---|---|---|---|
GET /tokens |
Tokens including revoked rows; cursor, limit and direction. Never the secret or hash. | admin | M.11d | same |
POST /tokens |
Create a token. The secret is shown once in this response. | admin | M.11d | same |
DELETE /tokens/{id} |
Revoke a token; a second revocation is 409 conflict. |
admin | M.11d | same |
3.3 Dropped v1 operations
| v1 operation | Why it is gone |
|---|---|
GET /unresolved |
Replaced by GET /links?status=to_check. A link always has an item (design 3.2), so nothing is "unresolved without an item"; a to-check candidate is a link, and "looked, found nothing" is the item's provider status. |
POST /unresolved/resolve |
Resolving is adding or preferring a link (PUT /items/{id}/links) or merging the local item into a catalogue item (POST /items/{id}/merge). |
GET /id-matches |
The id match table is the link table: GET /items/{id}/links and GET /links. |
POST /colors/sync |
The BrickLink colour table comes with the catalogue builds (M.8f), not from a source call; POST /catalogue/import loads the BrickLink item lists and reconciles. |
The ? item type and the item_type, item_no and color_id members are gone from every body (section 1). Nothing else in v1's 179 operations is dropped: 175 are carried (13 of them with a changed path, the rest on the same path with changed bodies where they named an item).
4. Pieces and what is implemented
| Piece | What | Operations |
|---|---|---|
| M.11a | This contract and the OpenAPI twin; the /api/v2 blueprint (auth, scopes, throttle, read-only, audit origin, envelopes, errors, body caps); meta; item addressing and the item and colour serializers; the merged-id 307; v1's 410 names v2; the contract check covers v2 |
9 |
| M.11b | Reads: catalogue, colours, items (detail, inventory, used-in, colours, images, links, provider status), copies, stock, minifigures, gaps and matches, instructions, prices, wishes and wishlists, storages, lots, places, flags, planning, exports, events, statistics, set scores, candidate sets of a lot | 59 |
| M.11c | Writes: the v2 write contract, create by numbers with 422 unidentified, the copy inventory switch and cancel, links, merge and unmerge, settings, undo |
65 |
| M.11d | Orders, jobs, sources and keys, sync, imports, identifications, tokens, admin; the dead v1 callers removed | 57 |
| M.11e | The MCP regenerated from this file, the v2 parity test, API and MCP docs. Done (2026-10-28) | none |
Operations by piece: M.11a 9, M.11b 64, M.11c 71, M.11d 57 (201 in all). The two tables below are generated from the contract and the routes by scripts/check_api_contract.py --write, and that check (run by scripts/check.sh) fails when the contract, the routes or these tables disagree. A piece lands by adding itself to brickwork.api2.DONE.
Implemented
| Implemented operation | Piece | Backing |
|---|---|---|
POST /api/v2/catalogue/import |
M.11d | brickwork.api2.sources.post_catalogue_import |
GET /api/v2/catalogue/items |
M.11b | brickwork.api2.catalogue.get_catalogue_items |
GET /api/v2/catalogue/items/{id} |
M.11b | brickwork.api2.catalogue.get_catalogue_items_by_id |
GET /api/v2/catalogue/status |
M.11b | brickwork.api2.catalogue.get_catalogue_status |
GET /api/v2/colors |
M.11b | brickwork.api2.catalogue.get_colors |
GET /api/v2/colors/{color_id} |
M.11b | brickwork.api2.catalogue.get_colors_by_color_id |
GET /api/v2/copies |
M.11b | brickwork.api2.copies.get_copies |
POST /api/v2/copies |
M.11c | brickwork.api2.write_collection.post_copies |
DELETE /api/v2/copies/{id} |
M.11c | brickwork.api2.write_collection.delete_copies_by_id |
GET /api/v2/copies/{id} |
M.11b | brickwork.api2.copies.get_copies_by_id |
PATCH /api/v2/copies/{id} |
M.11c | brickwork.api2.write_collection.patch_copies_by_id |
GET /api/v2/copies/{id}/check |
M.11b | brickwork.api2.copies.get_copies_by_id_check |
POST /api/v2/copies/{id}/check |
M.11c | brickwork.api2.write_collection.post_copies_by_id_check |
PUT /api/v2/copies/{id}/checked |
M.11c | brickwork.api2.write_collection.put_copies_by_id_checked |
GET /api/v2/copies/{id}/completeness |
M.11b | brickwork.api2.copies.get_copies_by_id_completeness |
GET /api/v2/copies/{id}/figures |
M.11b | brickwork.api2.copies.get_copies_by_id_figures |
PATCH /api/v2/copies/{id}/figures/{item_id} |
M.11c | brickwork.api2.write_collection.patch_copies_by_id_figures_by_item_id |
PUT /api/v2/copies/{id}/flags |
M.11c | brickwork.api2.write_collection.put_copies_by_id_flags |
GET /api/v2/copies/{id}/gaps |
M.11b | brickwork.api2.copies.get_copies_by_id_gaps |
POST /api/v2/copies/{id}/inventory |
M.11c | brickwork.api2.write_identity.post_copies_by_id_inventory |
DELETE /api/v2/copies/{id}/inventory/pending |
M.11c | brickwork.api2.write_identity.delete_copies_by_id_inventory_pending |
GET /api/v2/copies/{id}/matches |
M.11b | brickwork.api2.copies.get_copies_by_id_matches |
GET /api/v2/copies/{id}/parts |
M.11b | brickwork.api2.copies.get_copies_by_id_parts |
POST /api/v2/copies/{id}/parts/bulk |
M.11c | brickwork.api2.write_collection.post_copies_by_id_parts_bulk |
PATCH /api/v2/copies/{id}/parts/{part_id} |
M.11c | brickwork.api2.write_collection.patch_copies_by_id_parts_by_part_id |
POST /api/v2/copies/{id}/photos |
M.11c | brickwork.api2.write_collection.post_copies_by_id_photos |
PUT /api/v2/copies/{id}/pin |
M.11c | brickwork.api2.write_collection.put_copies_by_id_pin |
POST /api/v2/copies/{id}/restore |
M.11c | brickwork.api2.write_collection.post_copies_by_id_restore |
POST /api/v2/copies/{id}/take |
M.11c | brickwork.api2.write_collection.post_copies_by_id_take |
GET /api/v2/database/check |
M.11d | brickwork.api2.system.get_database_check |
GET /api/v2/database/export |
M.11d | brickwork.api2.system.get_database_export |
GET /api/v2/events |
M.11b | brickwork.api2.history.get_events |
GET /api/v2/events/{id} |
M.11b | brickwork.api2.history.get_events_by_id |
POST /api/v2/events/{id}/undo |
M.11c | brickwork.api2.write_identity.post_events_by_id_undo |
GET /api/v2/exchange-rates |
M.11d | brickwork.api2.system.get_exchange_rates |
POST /api/v2/exchange-rates/ecb |
M.11d | brickwork.api2.system.post_exchange_rates_ecb |
PUT /api/v2/exchange-rates/{code} |
M.11d | brickwork.api2.system.put_exchange_rates_by_code |
GET /api/v2/export/insurance.csv |
M.11b | brickwork.api2.valuation.get_export_insurance_csv |
GET /api/v2/export/missing.csv |
M.11b | brickwork.api2.ranking.get_export_missing_csv |
GET /api/v2/export/missing.json |
M.11b | brickwork.api2.ranking.get_export_missing_json |
GET /api/v2/export/missing.xml |
M.11b | brickwork.api2.ranking.get_export_missing_xml |
GET /api/v2/export/picker.csv |
M.11b | brickwork.api2.ranking.get_export_picker_csv |
GET /api/v2/export/wishlist.csv |
M.11b | brickwork.api2.ranking.get_export_wishlist_csv |
GET /api/v2/flags |
M.11b | brickwork.api2.places.get_flags |
POST /api/v2/flags |
M.11c | brickwork.api2.write_where.post_flags |
DELETE /api/v2/flags/{id} |
M.11c | brickwork.api2.write_where.delete_flags_by_id |
PATCH /api/v2/flags/{id} |
M.11c | brickwork.api2.write_where.patch_flags_by_id |
POST /api/v2/flags/{id}/restore |
M.11c | brickwork.api2.write_where.post_flags_by_id_restore |
POST /api/v2/found |
M.11c | brickwork.api2.write_collection.post_found |
GET /api/v2/gaps |
M.11b | brickwork.api2.loose.get_gaps |
GET /api/v2/health |
M.11a | brickwork.api2.get_health |
GET /api/v2/identifications |
M.11d | brickwork.api2.identifications.get_identifications |
POST /api/v2/identifications |
M.11d | brickwork.api2.identifications.post_identifications |
DELETE /api/v2/identifications/{id} |
M.11d | brickwork.api2.identifications.delete_identifications_by_id |
GET /api/v2/identifications/{id} |
M.11d | brickwork.api2.identifications.get_identifications_by_id |
GET /api/v2/identifications/{id}/thumb |
M.11d | brickwork.api2.identifications.get_identifications_by_id_thumb |
GET /api/v2/images/{id}/content |
M.11b | brickwork.api2.item_views.get_images_by_id_content |
POST /api/v2/imports/bricktracker |
M.11d | brickwork.api2.sync.post_imports_bricktracker |
POST /api/v2/imports/{id}/apply |
M.11d | brickwork.api2.sync.post_imports_by_id_apply |
GET /api/v2/instruction-files |
M.11b | brickwork.api2.instructions.get_instruction_files |
POST /api/v2/instruction-files |
M.11c | brickwork.api2.write_where.post_instruction_files |
DELETE /api/v2/instruction-files/{id} |
M.11c | brickwork.api2.write_where.delete_instruction_files_by_id |
GET /api/v2/instruction-files/{id} |
M.11b | brickwork.api2.instructions.get_instruction_files_by_id |
PATCH /api/v2/instruction-files/{id} |
M.11c | brickwork.api2.write_where.patch_instruction_files_by_id |
GET /api/v2/instruction-files/{id}/content |
M.11b | brickwork.api2.instructions.get_instruction_files_by_id_content |
POST /api/v2/instruction-files/{id}/restore |
M.11c | brickwork.api2.write_where.post_instruction_files_by_id_restore |
GET /api/v2/items |
M.11b | brickwork.api2.catalogue.get_items |
GET /api/v2/items/{id} |
M.11a | brickwork.api2.items.get_items_by_id |
POST /api/v2/items/{id}/build |
M.11c | brickwork.api2.write_collection.post_items_by_id_build |
POST /api/v2/items/{id}/change-variant |
M.11c | brickwork.api2.write_identity.post_items_by_id_change_variant |
GET /api/v2/items/{id}/colors |
M.11b | brickwork.api2.item_views.get_items_by_id_colors |
GET /api/v2/items/{id}/detail |
M.11b | brickwork.api2.item_views.get_items_by_id_detail |
GET /api/v2/items/{id}/images |
M.11b | brickwork.api2.item_views.get_items_by_id_images |
POST /api/v2/items/{id}/import |
M.11d | brickwork.api2.jobs.post_items_by_id_import |
GET /api/v2/items/{id}/inventory |
M.11b | brickwork.api2.item_views.get_items_by_id_inventory |
GET /api/v2/items/{id}/links |
M.11b | brickwork.api2.item_views.get_items_by_id_links |
PUT /api/v2/items/{id}/links |
M.11c | brickwork.api2.write_identity.put_items_by_id_links |
POST /api/v2/items/{id}/merge |
M.11c | brickwork.api2.write_identity.post_items_by_id_merge |
GET /api/v2/items/{id}/prices |
M.11b | brickwork.api2.prices.get_items_by_id_prices |
POST /api/v2/items/{id}/prices |
M.11c | brickwork.api2.write_money.post_items_by_id_prices |
GET /api/v2/items/{id}/prices/history |
M.11b | brickwork.api2.prices.get_items_by_id_prices_history |
POST /api/v2/items/{id}/prices/refresh |
M.11d | brickwork.api2.jobs.post_items_by_id_prices_refresh |
GET /api/v2/items/{id}/provider-status |
M.11b | brickwork.api2.item_views.get_items_by_id_provider_status |
POST /api/v2/items/{id}/unmerge |
M.11c | brickwork.api2.write_identity.post_items_by_id_unmerge |
GET /api/v2/items/{id}/used-in |
M.11b | brickwork.api2.item_views.get_items_by_id_used_in |
GET /api/v2/jobs |
M.11d | brickwork.api2.jobs.get_jobs |
POST /api/v2/jobs/drain |
M.11d | brickwork.api2.jobs.post_jobs_drain |
POST /api/v2/jobs/instruction-identities |
M.11d | brickwork.api2.jobs.post_jobs_instruction_identities |
POST /api/v2/jobs/pictures |
M.11d | brickwork.api2.jobs.post_jobs_pictures |
GET /api/v2/jobs/{id} |
M.11d | brickwork.api2.jobs.get_jobs_by_id |
POST /api/v2/jobs/{id}/resume |
M.11d | brickwork.api2.jobs.post_jobs_by_id_resume |
POST /api/v2/jobs/{id}/retry |
M.11d | brickwork.api2.jobs.post_jobs_by_id_retry |
GET /api/v2/links |
M.11b | brickwork.api2.item_views.get_links |
GET /api/v2/lookup |
M.11a | brickwork.api2.items.get_lookup |
GET /api/v2/lots |
M.11b | brickwork.api2.places.get_lots |
POST /api/v2/lots |
M.11c | brickwork.api2.write_where.post_lots |
DELETE /api/v2/lots/{id} |
M.11c | brickwork.api2.write_where.delete_lots_by_id |
GET /api/v2/lots/{id} |
M.11b | brickwork.api2.places.get_lots_by_id |
PATCH /api/v2/lots/{id} |
M.11c | brickwork.api2.write_where.patch_lots_by_id |
GET /api/v2/lots/{id}/candidate-sets |
M.11b | brickwork.api2.ranking.get_lots_by_id_candidate_sets |
GET /api/v2/lots/{id}/pull-list/{set_id} |
M.11b | brickwork.api2.pull_list.get_lots_by_id_pull_list_by_set_id |
POST /api/v2/lots/{id}/pull-list/{set_id}/found |
M.11c | brickwork.api2.pull_list.post_lots_by_id_pull_list_by_set_id_found |
POST /api/v2/lots/{id}/pull-list/{set_id}/make-copy |
M.11c | brickwork.api2.pull_list.post_lots_by_id_pull_list_by_set_id_make_copy |
POST /api/v2/lots/{id}/restore |
M.11c | brickwork.api2.write_where.post_lots_by_id_restore |
DELETE /api/v2/lots/{id}/stock/{stock_id}/sticker |
M.11c | brickwork.api2.stickers.delete_lots_by_id_stock_by_stock_id_sticker |
POST /api/v2/lots/{id}/stock/{stock_id}/sticker |
M.11c | brickwork.api2.stickers.post_lots_by_id_stock_by_stock_id_sticker |
GET /api/v2/lots/{id}/stock/{stock_id}/stickers |
M.11b | brickwork.api2.stickers.get_lots_by_id_stock_by_stock_id_stickers |
GET /api/v2/matches |
M.11b | brickwork.api2.loose.get_matches |
GET /api/v2/me |
M.11a | brickwork.api2.get_me |
GET /api/v2/minifigures |
M.11b | brickwork.api2.loose.get_minifigures |
GET /api/v2/minifigures/by-number/{no} |
M.11a | brickwork.api2.items.get_minifigures_by_number_by_no |
GET /api/v2/minifigures/{id} |
M.11b | brickwork.api2.item_views.get_minifigures_by_id |
GET /api/v2/openapi.yaml |
M.11a | brickwork.api2.get_openapi_yaml |
GET /api/v2/orders |
M.11d | brickwork.api2.orders.get_orders |
POST /api/v2/orders/documents |
M.11d | brickwork.api2.orders.post_orders_documents |
GET /api/v2/orders/export.csv |
M.11d | brickwork.api2.orders.get_orders_export_csv |
GET /api/v2/orders/{id} |
M.11d | brickwork.api2.orders.get_orders_by_id |
POST /api/v2/orders/{id}/allocate |
M.11d | brickwork.api2.orders.post_orders_by_id_allocate |
GET /api/v2/orders/{id}/allocation-review |
M.11d | brickwork.api2.orders.get_orders_by_id_allocation_review |
GET /api/v2/orders/{id}/allocations |
M.11d | brickwork.api2.orders.get_orders_by_id_allocations |
POST /api/v2/orders/{id}/allocations/retry |
M.11d | brickwork.api2.orders.post_orders_by_id_allocations_retry |
POST /api/v2/orders/{id}/documents/{doc_id}/phase |
M.11d | brickwork.api2.orders.post_orders_by_id_documents_by_doc_id_phase |
GET /api/v2/orders/{id}/history |
M.11d | brickwork.api2.orders.get_orders_by_id_history |
PATCH /api/v2/orders/{id}/lines/{line_id} |
M.11d | brickwork.api2.orders.patch_orders_by_id_lines_by_line_id |
PUT /api/v2/orders/{id}/lines/{line_id}/hold |
M.11d | brickwork.api2.orders.put_orders_by_id_lines_by_line_id_hold |
POST /api/v2/orders/{id}/received |
M.11d | brickwork.api2.orders.post_orders_by_id_received |
POST /api/v2/orders/{id}/reconcile |
M.11d | brickwork.api2.orders.post_orders_by_id_reconcile |
GET /api/v2/orders/{id}/short.xml |
M.11d | brickwork.api2.orders.get_orders_by_id_short_xml |
GET /api/v2/parts/by-number/{no} |
M.11a | brickwork.api2.items.get_parts_by_number_by_no |
GET /api/v2/places |
M.11b | brickwork.api2.places.get_places |
POST /api/v2/places |
M.11c | brickwork.api2.write_where.post_places |
DELETE /api/v2/places/{id} |
M.11c | brickwork.api2.write_where.delete_places_by_id |
PATCH /api/v2/places/{id} |
M.11c | brickwork.api2.write_where.patch_places_by_id |
POST /api/v2/places/{id}/restore |
M.11c | brickwork.api2.write_where.post_places_by_id_restore |
GET /api/v2/planning/build-scout |
M.11b | brickwork.api2.ranking.get_planning_build_scout |
GET /api/v2/planning/ranking |
M.11b | brickwork.api2.ranking.get_planning_ranking |
GET /api/v2/planning/set-scores |
M.11b | brickwork.api2.ranking.get_planning_set_scores |
GET /api/v2/prices |
M.11b | brickwork.api2.prices.get_prices |
POST /api/v2/prices/observations |
M.11c | brickwork.api2.write_money.post_prices_observations |
POST /api/v2/prices/refresh |
M.11d | brickwork.api2.jobs.post_prices_refresh |
GET /api/v2/prices/stale |
M.11b | brickwork.api2.prices.get_prices_stale |
GET /api/v2/sets/by-number/{no} |
M.11a | brickwork.api2.items.get_sets_by_number_by_no |
GET /api/v2/settings |
M.11c | brickwork.api2.write_money.get_settings |
PATCH /api/v2/settings |
M.11c | brickwork.api2.write_money.patch_settings |
GET /api/v2/sources |
M.11d | brickwork.api2.sources.get_sources |
PATCH /api/v2/sources/{source} |
M.11d | brickwork.api2.sources.patch_sources_by_source |
DELETE /api/v2/sources/{source}/key |
M.11d | brickwork.api2.sources.delete_sources_by_source_key |
PUT /api/v2/sources/{source}/key |
M.11d | brickwork.api2.sources.put_sources_by_source_key |
POST /api/v2/sources/{source}/key/test |
M.11d | brickwork.api2.sources.post_sources_by_source_key_test |
DELETE /api/v2/sources/{source}/user |
M.11d | brickwork.api2.sources.delete_sources_by_source_user |
PUT /api/v2/sources/{source}/user |
M.11d | brickwork.api2.sources.put_sources_by_source_user |
POST /api/v2/sources/{source}/user/connect |
M.11d | brickwork.api2.sources.post_sources_by_source_user_connect |
POST /api/v2/sources/{source}/user/test |
M.11d | brickwork.api2.sources.post_sources_by_source_user_test |
GET /api/v2/statistics |
M.11b | brickwork.api2.history.get_statistics |
GET /api/v2/stock |
M.11b | brickwork.api2.loose.get_stock |
POST /api/v2/stock |
M.11c | brickwork.api2.write_collection.post_stock |
POST /api/v2/stock/booklets |
M.11c | brickwork.api2.write_collection.post_stock_booklets |
DELETE /api/v2/stock/{id} |
M.11c | brickwork.api2.write_collection.delete_stock_by_id |
GET /api/v2/stock/{id} |
M.11b | brickwork.api2.loose.get_stock_by_id |
PATCH /api/v2/stock/{id} |
M.11c | brickwork.api2.write_collection.patch_stock_by_id |
PUT /api/v2/stock/{id}/flags |
M.11c | brickwork.api2.write_collection.put_stock_by_id_flags |
POST /api/v2/stock/{id}/give |
M.11c | brickwork.api2.write_collection.post_stock_by_id_give |
GET /api/v2/stock/{id}/parts |
M.11b | brickwork.api2.loose.get_stock_by_id_parts |
PATCH /api/v2/stock/{id}/parts |
M.11c | brickwork.api2.write_collection.patch_stock_by_id_parts |
POST /api/v2/stock/{id}/photos |
M.11c | brickwork.api2.write_collection.post_stock_by_id_photos |
POST /api/v2/stock/{id}/restore |
M.11c | brickwork.api2.write_collection.post_stock_by_id_restore |
GET /api/v2/storages |
M.11b | brickwork.api2.places.get_storages |
POST /api/v2/storages |
M.11c | brickwork.api2.write_where.post_storages |
DELETE /api/v2/storages/{id} |
M.11c | brickwork.api2.write_where.delete_storages_by_id |
GET /api/v2/storages/{id} |
M.11b | brickwork.api2.places.get_storages_by_id |
PATCH /api/v2/storages/{id} |
M.11c | brickwork.api2.write_where.patch_storages_by_id |
POST /api/v2/storages/{id}/contents |
M.11c | brickwork.api2.write_where.post_storages_by_id_contents |
POST /api/v2/storages/{id}/restore |
M.11c | brickwork.api2.write_where.post_storages_by_id_restore |
GET /api/v2/sync/{source} |
M.11d | brickwork.api2.sync.get_sync_by_source |
PUT /api/v2/sync/{source}/link |
M.11d | brickwork.api2.sync.put_sync_by_source_link |
GET /api/v2/sync/{source}/lists |
M.11d | brickwork.api2.sync.get_sync_by_source_lists |
POST /api/v2/sync/{source}/pull/apply |
M.11d | brickwork.api2.sync.post_sync_by_source_pull_apply |
POST /api/v2/sync/{source}/pull/plan |
M.11d | brickwork.api2.sync.post_sync_by_source_pull_plan |
POST /api/v2/sync/{source}/send/apply |
M.11d | brickwork.api2.sync.post_sync_by_source_send_apply |
POST /api/v2/sync/{source}/send/plan |
M.11d | brickwork.api2.sync.post_sync_by_source_send_plan |
GET /api/v2/tokens |
M.11d | brickwork.api2.system.get_tokens |
POST /api/v2/tokens |
M.11d | brickwork.api2.system.post_tokens |
DELETE /api/v2/tokens/{id} |
M.11d | brickwork.api2.system.delete_tokens_by_id |
GET /api/v2/valuation |
M.11b | brickwork.api2.valuation.get_valuation |
GET /api/v2/version |
M.11a | brickwork.api2.get_version |
GET /api/v2/wishes |
M.11b | brickwork.api2.wishes.get_wishes |
POST /api/v2/wishes |
M.11c | brickwork.api2.write_collection.post_wishes |
DELETE /api/v2/wishes/{id} |
M.11c | brickwork.api2.write_collection.delete_wishes_by_id |
GET /api/v2/wishes/{id} |
M.11b | brickwork.api2.wishes.get_wishes_by_id |
PATCH /api/v2/wishes/{id} |
M.11c | brickwork.api2.write_collection.patch_wishes_by_id |
PUT /api/v2/wishes/{id}/flags |
M.11c | brickwork.api2.write_collection.put_wishes_by_id_flags |
POST /api/v2/wishes/{id}/restore |
M.11c | brickwork.api2.write_collection.post_wishes_by_id_restore |
POST /api/v2/wishes/{id}/to-owned |
M.11c | brickwork.api2.write_collection.post_wishes_by_id_to_owned |
GET /api/v2/wishlists |
M.11b | brickwork.api2.wishes.get_wishlists |
POST /api/v2/wishlists |
M.11c | brickwork.api2.write_collection.post_wishlists |
DELETE /api/v2/wishlists/{id} |
M.11c | brickwork.api2.write_collection.delete_wishlists_by_id |
PATCH /api/v2/wishlists/{id} |
M.11c | brickwork.api2.write_collection.patch_wishlists_by_id |
GET /api/v2/wishlists/{id}/export.csv |
M.11b | brickwork.api2.wishes.get_wishlists_by_id_export_csv |
GET /api/v2/wishlists/{id}/export.xml |
M.11b | brickwork.api2.wishes.get_wishlists_by_id_export_xml |
POST /api/v2/wishlists/{id}/restore |
M.11c | brickwork.api2.write_collection.post_wishlists_by_id_restore |
POST /api/v2/wishlists/{id}/transfer |
M.11c | brickwork.api2.write_collection.post_wishlists_by_id_transfer |
Not implemented yet (answer 501)
501 not_implemented with a problem body that also carries {"error": "not_implemented", "piece": "M.11x"}. Authentication, scope and read-only come first, so a client without the right token still sees 401 or 403.
| Operation (answers 501 until its piece lands) | Piece |
|---|---|
5. Decisions
Decided by brick-works under Ulf's delegation on #113 (2026-10-27); design section 7 is the approved frame.
- v2 is v1's resource set with the 4.0 identity, not a redesign.
/items/{id}is canonical; the number paths and/lookupanswer200with the same item body.- The default provider for numbers is
bricklink(parts, minifigures);provider=rebrickablefor Rebrickable numbers; a set number is the same in both. Several matches are409 ambiguous, none404,422 unidentifiedon create. - Colours in a request are in the provider's space.
- A merged id answers
307on GET (amended from301, section 2.1) and409 mergedon writes. - Wish ids are uuids; stored words are served.
- The MCP keeps its tool names; its tools take numbers and a provider, never a Brick Work id.
Choices M.11a made where the scope comment and design section 7 were silent:
GET /items/{id}returns exactly the section 7 item body. v1's item detail (properties, counts, copies, stock, wishes, prices) moves toGET /items/{id}/detail(M.11b), withlinkswhere v1 hadmatches./minifigures/{id}(M.11b) is v1's figure aggregate (sets, loose lines, missing, matches, prices) by Brick Work id; the item body of a figure by number is/minifigures/by-number/{no}.provider=brickworkis for colours on id paths only; on number paths and/lookupit is422.- A lookup given
coloranswersdata.colornext to the item fields. GET /items/{id}/provider-statusis read-only: reconcile writesitem_provider_status; a person resolves a to-check row by adding a link or merging.PUT /items/{id}/linkssets one manual link or makes one preferred for its colour scope; there is no link delete (Undo reverses a mistake).POST /items/{id}/mergetakes{"survivor": <item key>}and names the merged item in the path;POST /items/{id}/unmergenames the merged id in the path and an optionalmerge_id.GET /settingsis implemented with the settings writes (M.11c), not with the reads.- Undo (
POST /events/{id}/undo) is a write and belongs to M.11c.
Appendix A: error codes
code |
HTTP | When |
|---|---|---|
bad_request |
400 | malformed JSON, wrong content type |
unknown_filter |
400 | filter or sort name not in the vocabulary |
unauthorized |
401 | no, bad, expired or revoked token |
forbidden |
403 | scope too low |
read_only_instance |
403 | BW_READ_ONLY set and a write was attempted |
not_found |
404 | no such path, item or record |
method_not_allowed |
405 | unsupported HTTP method; Allow lists supported methods |
api_retired |
410 | only API v1 answers it: the whole line is gone from 4.0 (its /health excepted); the text names v2 |
moved |
307 | new in v2. GET of a merged item id: 307 with Location to the survivor and Cache-Control: no-store; also carries survivor |
archived |
409 | write to an archived record |
ambiguous |
409 | new in v2. a number names several items; candidates lists them as item bodies |
merged |
409 | new in v2. a write names a merged item id; survivor names the live item |
conflict |
409 | state changed, duplicate |
stale_match |
409 | stock or gap counts changed |
undo_blocked |
409 | later change to the same rows |
not_undoable |
409 | event is not a receipt |
idempotency_conflict |
409 | key reused with another body |
precondition_failed |
412 | If-Match mismatch |
payload_too_large |
413 | document over 1 MiB, PDF over 20 MB, photo over 15 MB; any body over its cap is refused unread |
unsupported_media_type |
415 | not JSON or multipart where required |
validation_failed |
422 | field errors |
unsupported_setting |
422 | key not in the whitelist |
unidentified |
422 | new in v2. a create names a number that resolves to no item and cannot make one |
source_not_configured |
424 | key missing for the source |
source_disabled |
424 | source disabled |
catalogue_not_loaded |
424 | no catalogue file |
idempotency_key_required |
428 | required key missing |
budget_exhausted |
429 | daily budget spent, synchronous call |
source_throttled |
429 | remote asked to wait |
too_many_attempts |
429 | too many failed token checks from this client; Retry-After and retry_after give the wait |
source_error |
502 | upstream failure, redacted text |
not_set_up |
503 | database file missing |
api_disabled |
503 | no token configured |
db_unavailable |
503 | SQLite error |
internal_error |
500 | anything else, no detail leaked |
not_implemented |
501 | new in v2. the operation's piece has not landed; the body also carries piece and error |