Developers
The BUSE API
Everything the website does, you can do from code or from an agent: upload a file, get the price the checkout will charge, place and pay the order.
- 01Session cookie
- 02JSON envelopes
- 03Pages as Markdown
01Overview
What it is
BUSE prints parts in FDM in Agadir and delivers anywhere in Morocco, delivery included in every price.
The API is the one the website itself uses. Prices are computed on the server from the uploaded file's geometry: a client only chooses the material SKU, the finish, the quantity and whether the order is rushed, never a price, a volume or a weight.
The flow is: sign in (or sign up) → `POST /api/upload` the STL/OBJ/3MF → `POST /api/quotes/batch` to price a basket → `POST /api/addresses` → `POST /api/orders` → `POST /api/payments/checkout` for a Stripe Checkout URL. Every response is a JSON envelope `{ success, data }` or `{ success: false, error: { code, message, details } }`.
Every public HTML page also answers in Markdown with `Accept: text/markdown`; the site index for agents is /llms.txt.
02Authentication
Sign in, then send the cookie
Accounts are email and password (better-auth). The sign-in response sets a session cookie; send it back on every call. There are no API keys yet.
# 1. Sign in (keeps the session cookie in cookies.txt)
curl -c cookies.txt -H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"..."}' \
https://buse.ma/api/auth/sign-in/email
# 2. Upload the file; the key is what every pricing call takes
curl -b cookies.txt -F file=@part.stl https://buse.ma/api/upload
# 3. Price the basket as the order will charge it
curl -b cookies.txt -H 'Content-Type: application/json' \
-d '{"items":[{"fileKey":"uploads/<you>/<key>","filename":"part.stl","materialId":"mat_pla_white","quantity":2}]}' \
https://buse.ma/api/quotes/batch03Endpoints
Every endpoint
Generated from the same validators the server enforces. Request bodies and the error codes are in the OpenAPI document.
Auth
Email + password sessions (better-auth). The session is a cookie; send it back on every call.
| Method | Path | What it does | Auth |
|---|---|---|---|
| POST | /api/auth/sign-up/email | Create an accountEmail, password and name. A verification email is sent; the session cookie is set once verified. | Public |
| POST | /api/auth/sign-in/email | Sign inSets the `better-auth.session_token` cookie on success. Keep the cookie jar for the calls below. | Public |
| GET | /api/auth/get-session | Current sessionNull when not signed in. | Session |
| POST | /api/auth/sign-out | Sign outEnds the session. | Session |
Catalog
Materials and delivery destinations.
| Method | Path | What it does | Auth |
|---|---|---|---|
| GET | /api/materials | Material catalogEvery in-stock colour SKU with its price per gram. Public. | Public |
| GET | /api/shipping/destinations | Delivery citiesType-ahead on the cities we deliver to; the id is what an address's `shippingDestinationId` takes. Rate limit: 60 per minute per client. | Session |
Quotes
Upload a file, price it, save a devis.
| Method | Path | What it does | Auth |
|---|---|---|---|
| POST | /api/upload | Upload a 3D fileMultipart form with one `file` field: STL, OBJ or 3MF, up to 50 MB. The returned `key` is what every pricing call takes as `fileKey`; it lives in the caller's own namespace. Rate limit: 20 per minute per client. | Session |
| GET | /api/upload | Upload limitsAccepted extensions and the size cap. | Session |
| POST | /api/analyze | Analyse a file and price one copyGeometry (volume, bounding box, printability) plus a quote in the given SKU. A part that does not fit the 256 mm bed, is too small or is not a closed mesh is a 400 with `details.reason`. Rate limit: 20 per minute per client. | Session |
| POST | /api/quotes/calculate | Price one fileThe price of one line: SKU, finish, quantity, rush. `isRush` is refused with `reason: rushUnavailable` when the print does not fit a working day. Rate limit: 60 per minute per client. | Session |
| POST | /api/quotes/batch | Price a basketThe total an order will charge: lines, the per-order service fee, the order minimum, the plate credit and delivery (included). Always price a basket here rather than summing single lines. Rate limit: 60 per minute per client. | Session |
| POST | /api/quotes | Save a devisSaves the basket as a numbered quote (DV-YYYY-NNNN), valid for a week, re-priced from the files. Rate limit: 20 per minute per client. | Session |
| GET | /api/quotes | List my devisThe caller's saved quotes. | Session |
| GET | /api/quotes/{id} | A devisWith its lines. | Session |
| PATCH | /api/quotes/{id} | Update a devisIts status. Rate limit: 5 per minute per client. | Session |
| DELETE | /api/quotes/{id} | Delete a devisAnd its lines. | Session |
| GET | /api/quotes/{id}/pdf | The devis as PDFRe-priced from the files; `application/pdf`. | Session |
Orders
Addresses, orders, payment, invoices.
| Method | Path | What it does | Auth |
|---|---|---|---|
| GET | /api/addresses | My addressesThe caller's delivery addresses. Rate limit: 60 per minute per client. | Session |
| POST | /api/addresses | Add an address`shippingDestinationId` must be an id from /api/shipping/destinations; an unknown city is refused with `reason: cityNotServed`. Rate limit: 20 per minute per client. | Session |
| PATCH | /api/addresses/{id} | Edit an addressThe caller's own. Rate limit: 20 per minute per client. | Session |
| DELETE | /api/addresses/{id} | Remove an addressThe caller's own. Rate limit: 20 per minute per client. | Session |
| POST | /api/orders | Place an orderPriced again from the files on the server; the response total is what Stripe will charge. `acceptTerms` must be `true`. The order is created unpaid; pay it through /api/payments/checkout. Rate limit: 20 per minute per client. | Session |
| GET | /api/orders | My ordersThe caller's orders, newest first. | Session |
| GET | /api/orders/{id}/invoice | The invoice PDFIssued when the order was paid; 404 before. | Session |
| POST | /api/payments/checkout | Pay an orderReturns a Stripe Checkout URL for the order's total, in MAD. The order turns `paid` when Stripe confirms, never from the client. Rate limit: 20 per minute per client. | Session |
Projects
Work the instant quote cannot price: a human quote.
| Method | Path | What it does | Auth |
|---|---|---|---|
| POST | /api/requests | Submit a custom projectFor work the instant quote cannot price: a part to reproduce from photos, a batch nobody has modelled. A human answers with a quote. Rate limit: 5 per minute per client. | Session |
| GET | /api/requests | My project requestsThe caller's requests. | Session |
Cards
The 3D-printed business card generator.
| Method | Path | What it does | Auth |
|---|---|---|---|
| POST | /api/cards | Price a business cardText and style choices only; the mesh is generated and priced on the server. Rate limit: 20 per minute per client. | Session |
Account
Profile and contact.
| Method | Path | What it does | Auth |
|---|---|---|---|
| PATCH | /api/user/profile | Update my profileName and phone. Rate limit: 20 per minute per client. | Session |
| POST | /api/contact | Write to the workshopSends an email to the team. Public. Rate limit: 5 per minute per client. | Public |
04Errors
Every error is JSON
Validation failures list the fields; business rules name a reason (rushUnavailable, cityNotServed, exceedsBuildVolume, meshNotClosed); unknown routes answer the same envelope with a hint.
{ "success": false,
"error": { "code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": { "errors": [{ "field": "items.0.materialId", "message": "..." }] } } }Build on it
The document is the contract. Something missing? Tell us.