Files
GearBox/docs/api.md
Jean-Luc Makiola e34a2cad11
Some checks failed
CI / ci (push) Failing after 11s
docs: add authentication, API reference, and MCP server guides
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:00:03 +02:00

13 KiB

API Reference

Base URL: http://localhost:3000

Auth: GET endpoints are public. POST, PUT, PATCH, and DELETE require authentication via session cookie or X-API-Key header. See authentication.md for details.

Prices are stored and returned in cents (e.g., 2500 = $25.00).
Weights are in grams.
Timestamps are unix epoch integers.


Table of Contents


Health

GET /api/health

Returns server status. Always public.

Response:

{ "status": "ok" }

Items

GET /api/items

List all items in the collection.

Response:

[
  {
    "id": 1,
    "name": "Revelate Tangle Frame Bag",
    "categoryId": 2,
    "weightGrams": 185,
    "priceCents": 12000,
    "notes": "Medium size, fits 2020 Surly Straggler",
    "productUrl": "https://revelatedesigns.com/...",
    "imageFilename": "1710000000000-uuid.jpg",
    "imageSourceUrl": "https://example.com/image.jpg",
    "createdAt": 1710000000,
    "updatedAt": 1710000000
  }
]

GET /api/items/:id

Get a single item by ID.

Response: item object (see above), or 404 { "error": "Item not found" }.


POST /api/items

Create a new item. Auth required.

Request:

{
  "name": "Revelate Tangle Frame Bag",
  "categoryId": 2,
  "weightGrams": 185,
  "priceCents": 12000,
  "notes": "Medium size",
  "productUrl": "https://revelatedesigns.com/...",
  "imageFilename": "1710000000000-uuid.jpg",
  "imageSourceUrl": "https://example.com/image.jpg"
}
Field Type Required Description
name string yes Item name
categoryId integer yes ID of an existing category
weightGrams number no Weight in grams (non-negative)
priceCents integer no Price in cents (non-negative)
notes string no Free-text notes
productUrl string no URL to product page
imageFilename string no Filename from a prior image upload
imageSourceUrl string no Original URL the image came from

Response: 201 — created item object.


PUT /api/items/:id

Update an existing item. All fields are optional. Auth required.

Request: same fields as POST, all optional.

Response: updated item object, or 404.


DELETE /api/items/:id

Delete an item and clean up its image file if one exists. Auth required.

Response:

{ "success": true }

Returns 404 if item not found.


Categories

GET /api/categories

List all categories.

Response:

[
  { "id": 1, "name": "Uncategorized", "icon": "package" },
  { "id": 2, "name": "Bags", "icon": "backpack" }
]

POST /api/categories

Create a new category. Auth required.

Request:

{ "name": "Bags", "icon": "backpack" }
Field Type Required Description
name string yes Category name
icon string no Icon name (defaults to "package")

Response: 201 — created category object.


PUT /api/categories/:id

Update a category. Auth required.

Request:

{ "name": "Carry", "icon": "bag" }

Response: updated category object, or 404.


DELETE /api/categories/:id

Delete a category. Auth required.

Response:

{ "success": true }

Threads

Research threads track multiple candidate options for a single gear slot before committing to a purchase.

GET /api/threads

List threads. By default only active (unresolved) threads are returned.

Query parameters:

Parameter Type Default Description
includeResolved boolean false Set to true to include resolved threads

Example: GET /api/threads?includeResolved=true

Response:

[
  {
    "id": 1,
    "name": "Handlebar bag",
    "categoryId": 2,
    "status": "active",
    "resolvedCandidateId": null,
    "createdAt": 1710000000,
    "updatedAt": 1710000000
  }
]

POST /api/threads

Create a new research thread. Auth required.

Request:

{ "name": "Handlebar bag", "categoryId": 2 }

Response: 201 — created thread object.


GET /api/threads/:id

Get a thread with all its candidates.

Response:

{
  "id": 1,
  "name": "Handlebar bag",
  "categoryId": 2,
  "status": "active",
  "resolvedCandidateId": null,
  "candidates": [
    {
      "id": 10,
      "threadId": 1,
      "name": "Revelate Sweetroll",
      "categoryId": 2,
      "weightGrams": 290,
      "priceCents": 13500,
      "notes": "16L, fits most drop bars",
      "productUrl": "https://revelatedesigns.com/...",
      "imageFilename": null,
      "imageSourceUrl": null,
      "status": "researching",
      "pros": "Waterproof, large capacity",
      "cons": "Pricey",
      "sortOrder": 0,
      "createdAt": 1710000000,
      "updatedAt": 1710000000
    }
  ]
}

Returns 404 if thread not found.


PUT /api/threads/:id

Update a thread's name or category. Auth required.

Request:

{ "name": "Bar bag", "categoryId": 2 }

Response: updated thread object, or 404.


DELETE /api/threads/:id

Delete a thread and all its candidates. Cleans up any candidate image files. Auth required.

Response:

{ "success": true }

POST /api/threads/:id/candidates

Add a candidate to a thread. Auth required.

Request:

{
  "name": "Revelate Sweetroll",
  "categoryId": 2,
  "weightGrams": 290,
  "priceCents": 13500,
  "notes": "16L capacity",
  "productUrl": "https://revelatedesigns.com/...",
  "imageFilename": "1710000000000-uuid.jpg",
  "imageSourceUrl": "https://example.com/image.jpg",
  "status": "researching",
  "pros": "Waterproof, large capacity",
  "cons": "Expensive"
}
Field Type Required Description
name string yes Candidate name
categoryId integer yes Category ID
weightGrams number no Weight in grams
priceCents integer no Price in cents
notes string no Notes
productUrl string no Product URL
imageFilename string no Filename from a prior image upload
imageSourceUrl string no Original image URL
status string no "researching", "ordered", or "arrived"
pros string no Pros of this option
cons string no Cons of this option

Response: 201 — created candidate object. Returns 404 if thread not found.


PUT /api/threads/:threadId/candidates/:candidateId

Update a candidate. All fields optional. Auth required.

Request: same fields as POST, all optional.

Response: updated candidate object, or 404.


DELETE /api/threads/:threadId/candidates/:candidateId

Delete a candidate and clean up its image. Auth required.

Response:

{ "success": true }

PATCH /api/threads/:id/candidates/reorder

Reorder candidates within a thread. Auth required.

Request:

{ "orderedIds": [12, 10, 11] }

orderedIds is an array of candidate IDs in the desired display order. All IDs must belong to the thread.

Response:

{ "success": true }

Returns 400 with an error message if validation fails.


POST /api/threads/:id/resolve

Resolve a thread by selecting the winning candidate. Auth required.

The winning candidate's data is copied into a new item in the collection. The thread status is set to "resolved" and resolvedCandidateId is set.

Request:

{ "candidateId": 10 }

Response:

{
  "success": true,
  "item": {
    "id": 42,
    "name": "Revelate Sweetroll",
    "categoryId": 2,
    "weightGrams": 290,
    "priceCents": 13500
  }
}

Returns 400 if the candidate does not belong to the thread or another error occurs.


Setups

Setups are named collections of items (e.g., "Bikepacking weekend", "Commute loadout").

GET /api/setups

List all setups with item counts and weight/cost totals.

Response:

[
  {
    "id": 1,
    "name": "Bikepacking weekend",
    "itemCount": 12,
    "totalWeightGrams": 4800,
    "totalPriceCents": 285000,
    "createdAt": 1710000000,
    "updatedAt": 1710000000
  }
]

POST /api/setups

Create a new setup. Auth required.

Request:

{ "name": "Bikepacking weekend" }

Response: 201 — created setup object.


GET /api/setups/:id

Get a setup with its full item list.

Response:

{
  "id": 1,
  "name": "Bikepacking weekend",
  "items": [
    {
      "id": 1,
      "name": "Revelate Tangle Frame Bag",
      "weightGrams": 185,
      "priceCents": 12000,
      "categoryId": 2,
      "classification": "base"
    }
  ],
  "totalWeightGrams": 185,
  "totalPriceCents": 12000
}

Returns 404 if setup not found.


PUT /api/setups/:id

Update a setup's name. Auth required.

Request:

{ "name": "Bikepacking overnighter" }

Response: updated setup object, or 404.


PUT /api/setups/:id/items

Replace all items in a setup atomically. Deletes all existing setup-item associations and re-inserts with the provided IDs. Auth required.

Request:

{ "itemIds": [1, 5, 12, 17] }

Pass an empty array to remove all items from the setup.

Response:

{ "success": true }

DELETE /api/setups/:id

Delete a setup. Does not delete the items themselves. Auth required.

Response:

{ "success": true }

Images

POST /api/images

Upload an image file. Auth required.

Request: multipart/form-data with an image field.

POST /api/images
Content-Type: multipart/form-data; boundary=...

--boundary
Content-Disposition: form-data; name="image"; filename="bag.jpg"
Content-Type: image/jpeg

<binary data>
--boundary--

Constraints:

  • Accepted types: image/jpeg, image/png, image/webp
  • Maximum size: 5MB
  • Files are saved to ./uploads/ with a UUID-based filename

Response: 201

{ "filename": "1710000000000-550e8400-e29b-41d4-a716-446655440000.jpg" }

Use the returned filename as imageFilename when creating or updating items or candidates.


POST /api/images/from-url

Fetch an image from a remote URL and save it locally. Auth required.

Request:

{ "url": "https://example.com/product-image.jpg" }

Response: 201

{
  "filename": "1710000000000-uuid.jpg",
  "sourceUrl": "https://example.com/product-image.jpg"
}

Returns 400 for invalid URLs, unsupported content types, files over 5MB, or non-200 HTTP responses.


Settings

Key-value store for application settings.

GET /api/settings/:key

Get a setting by key. Public.

Response:

{ "key": "collectionName", "value": "My Bikepacking Gear" }

Returns 404 if the key does not exist.


PUT /api/settings/:key

Create or update a setting. Auth required.

Request:

{ "value": "My Bikepacking Gear" }

Response: updated setting object.


Totals

GET /api/totals

Global and per-category weight and cost totals. Computed from current item data. Public.

Response:

{
  "global": {
    "totalWeightGrams": 12500,
    "totalPriceCents": 450000,
    "itemCount": 34
  },
  "categories": [
    {
      "categoryId": 2,
      "name": "Bags",
      "icon": "backpack",
      "totalWeightGrams": 1200,
      "totalPriceCents": 78000,
      "itemCount": 5
    }
  ]
}

Auth

Authentication endpoints are documented in authentication.md.

Method Path Auth Description
GET /api/auth/me public Current session state
POST /api/auth/setup public Create first admin user
POST /api/auth/login public Log in, receive cookie
POST /api/auth/logout public Clear session
PUT /api/auth/password session Change password
GET /api/auth/keys required List API keys
POST /api/auth/keys required Create API key
DELETE /api/auth/keys/:id required Revoke API key