Upload and print
Upload an STL, GLB, 3MF or sliced G-code at /print, get a quote weighed from a real slice, then order the part printed in Austin and shipped.
The /print page takes your own file, validates it, weighs it with a real slice, and turns it into an order we print in Austin, Texas and ship to you. You choose material, colour, quality, quantity, scale and infill; the price comes from the grams the slicer says will leave the nozzle. You need an account, because the file is stored against it and the order is billed to it.
What you can upload#
| Kind | Extensions | Size limit | What we read from it |
|---|---|---|---|
| Mesh | .stl | 100MB | Bounding box, volume, triangle count |
| Mesh | .glb | 100MB | Bounding box, volume, triangle count |
| Sliced | .gcode | 40MB | Print footprint, filament grams, print time, plate preview |
| Sliced | .3mf, .gcode.3mf | 40MB | Print footprint, filament grams, print time, plate preview |
A mesh must fit the plate: 340 × 320 × 340 mm on a Bambu Lab H2S. The three dimensions are sorted before they are compared, so a part that only fits when rotated still passes. An already-sliced file skips this check entirely — your slicer already arranged it on a plate, and the footprint it reports can include purge lines and prime towers that are not part of the model.
How the upload works#
The bytes go from your browser straight to storage, not through the app. There is a 32 MiB cap on request bodies in front of the service, and anything over it is rejected before the app ever sees it — so a 100MB model could never arrive as an ordinary POST. It used to fail as a confusing validation error rather than a size error. Now the app hands the browser a signed URL, the browser uploads to storage, and the app validates the stored object afterwards.
- 1The page asks for an upload URL
It sends the filename and the size. Format and size are checked here, before a single byte moves — an unsupported extension or an oversize file is refused now rather than after the transfer.
json{ "fileName": "bracket.stl", "size": 48213004 } - 2Your browser uploads to storage
A signed PUT URL comes back, valid for 15 minutes, pointing at a staging path. The transfer is browser to storage, so its speed is your connection, not ours.
- 3We validate what actually landed
The app measures the stored object itself rather than trusting the size the browser claimed, parses the bytes that actually landed, and only then copies the exact bytes it validated out of staging into their final path. That copy is the point of the staging step: the signed URL stays writable for its full 15 minutes, so validating and printing from the same writable object would let a small part be priced and a large one printed.
- 4The model is weighed
As soon as the file is stored, a slice is queued in the background. Until it lands the page shows Calculating, because showing an estimate and then quietly replacing it reads as the site changing its mind about the price.
/api/print-upload/initSigned inReserve a staging path and return a 15-minute signed upload URL.
/api/print-upload/completeSigned inValidate the uploaded object, store it, and return its measurements.
What the quote is computed from#
Weight. One all-in rate per gram covers filament and normal machine time together, so a 21 g PLA part costs exactly 21 × $0.12 = $2.52 and nothing else is added except postage.
| Material | Rate | Density |
|---|---|---|
| PLA | $0.12/g | 1.26 g/cm³ |
| PETG | $0.12/g | 1.27 g/cm³ |
| ABS | $0.12/g | 1.04 g/cm³ |
| ASA | $0.14/g | 1.07 g/cm³ |
| Flexible TPU | $0.13/g | 1.21 g/cm³ |
- Billable weight rounds up to the whole gram, like postage — so the figure agrees with the whole grams Bambu Studio shows you.
- There is a $1.00 minimum per part.
- A colour outside the twelve stock spools is priced at $0.20/g, whatever the material, because the spool has to be bought in for your order and most of it will go unused.
- Ordering several copies of one part discounts it: 5% off at 5 or more, 10% off at 10 or more.
- Shipping is $7 flat in the US. Checking out through the cart makes it free over $50, and prices international postage at $30 flat, free over $500; Print & Ship always adds the $7.
The grams come from a real OrcaSlicer slice wherever one is possible, not from a volume estimate. The estimate reads a part as 30% solid walls plus infill, which undercounts anything thin badly — a stencil that weighed 21 g on the plate estimated at 12.9 g. That is why checkout refuses to sell an unmeasured print: it would sell below the advertised rate. An already-sliced upload is priced from the filament figure in its own header rather than from that estimate — but checkout still wants a slice of ours on file before it will sell any print, and a bare .gcode is the one thing we cannot re-slice, so send a plain .gcode plate through the print order API (/docs/api/submit-order) rather than this page.
curl -s https://x3dstudios.com/api/price \
-H "Content-Type: application/json" \
-d '{"bboxMm":{"x":120,"y":80,"z":60},"material":"pla","quality":"standard","quantity":1,"color":"Black"}'{
"filamentChanges": 0,
"multiColorGrams": 0,
"multiColorHours": 0,
"grams": 320,
"printHours": 15.84,
"materialCost": 38.4,
"machineCost": 0,
"shipping": 7,
"baseFee": 0,
"perUnit": 38.4,
"subtotal": 38.4,
"total": 45.4,
"currency": "USD",
"material": "pla",
"quality": "standard",
"quantity": 1,
"color": "Black",
"customColor": false,
"pricePerGram": 0.12
}The options you choose#
materialstringoptionaldefaultpla- The plastic. Changes the per-gram rate and the density used to turn volume into weight.
plapetgabsasatpu colorstringoptionaldefaultBlack- One of the twelve stock swatches, or a hex or name you type beside the custom swatch. Off-palette prices at $0.20/g, and the page says so where you pick it rather than at checkout.
qualitystringoptionaldefaultstandard- Standard is 0.2mm layers at 1×. Premium is 0.1mm layers at 1.75× — finer surface, roughly twice the machine time.
standardpremium quantityintegeroptionaldefault1- 1 to 20 per order line. Bulk factors apply at 5 and at 10.
scalePercentintegeroptionaldefault100- 50% to 300%, in steps of 5. Weight scales with the cube of it, so 200% is roughly eight times the material. The scaled part still has to fit 340mm.
infillintegeroptionaldefault20- 10% to 100%, in steps of 5. Higher infill means a stronger, heavier, more expensive part. Above 35% the slice — run at the profile's sparse default — stops being representative, and the quote falls back to the volume estimate, which approaches solid volume exactly where the slice diverges.
notestringoptional- Free text kept with the order and shown to the operator on the work order: orientation preferences, colour matching, anything a human should see. Trimmed to 900 characters.
Placing the order#
Print & Ship goes straight to Stripe checkout for that one file. Add to Cart holds it so you can order several files together, and the cart re-prices every print server-side before checkout — the number stored in your browser is never trusted. Either way you get a confirmation email, the order appears on /orders, and it moves through placed, printed, packed, shipped, delivered.
Troubleshooting#
| What you see | What it means | What to do |
|---|---|---|
| Unsupported file type. Use STL, GLB, 3MF, or G-code. | The extension is not one we print. OBJ is the common case. | Convert it to STL and upload that. |
| File too large (max 100MB for STL files). | Over the ceiling for that format — 100MB for meshes, 40MB for sliced files. | Decimate the mesh, or for a sliced file re-export with fewer objects on the plate. |
| This model is 412×180×95mm — larger than the 340×320×340mm build volume. | It does not fit in any orientation. | Scale it down, or split it and order the pieces. |
| We couldn't read a printable mesh from that file. Make sure it's a valid, watertight STL/GLB. | The parser could not get triangles and a bounding box out of it. | Re-export from your CAD tool, and run the free check at /docs/studio/printability first. |
| We couldn't read the print footprint/filament from that sliced file. | The G-code header carries no usable filament or footprint numbers. A 3MF saved as a project rather than exported as a sliced plate lands here. | In Bambu Studio or OrcaSlicer, slice the plate and use Export plate sliced file. |
| Couldn't upload that file to storage. Check your connection and try again. | The direct browser-to-storage transfer failed part way, or the signed URL had already expired. | Select the file again to get a fresh 15-minute link. |
| That upload reference isn't valid. | The staging path or its token did not match your account. | Choose the file again rather than retrying the same reference. |
| Too many uploads — try again shortly. | You are over 40 uploads in the last hour. | Wait for the window to roll over. The response carries Retry-After. |
| We're still measuring this model. | Checkout was reached before the slice finished. We do not sell a print we have not weighed. | Give it up to a minute and try again. A bare .gcode never clears this, because there is nothing left for us to slice — order that plate through the print order API instead. |
| Sign in to order a print. | Uploads are tied to an account. | Sign in, then select the file again. |