LabPrints connector
The LabPrints connector lets an AI assistant order prints for a person. The assistant sends photos and a brief; LabPrints makes a proof for the person to review; the person approves a proof and pays on a checkout page. Nothing is printed or charged until they do.
Getting started
- Get an API key. Keys are issued by hand for now; there is no signup form. Email privacy@labprints.com with the address you want the key tied to. A key is either a test key or a production key.
- Add the connector to your assistant as a custom MCP connector: server
URL
https://api.labprints.com/mcp(streamable HTTP), authentication by API key sent as a bearer token in theAuthorizationheader. Never put the key in a URL. - Ask the assistant to call
get_service_infoto confirm the connection. It reports the live version and whether your key is in test mode.
The connector is not yet listed in any assistant's connector directory.
A test key runs the whole flow with sample proofs; see Test mode.
Payment. Prices are quoted before shipping and sales tax. After approving a proof, the customer pays, and chooses shipping, through Stripe Checkout on labprints.com; the connector never takes payment details. Orders are printed by professional print partners.
Test mode
A test key runs the whole flow, so you can try the connector without making an order.
- Proofs are sample images, labelled "Sample proof (test mode)", and are ready within seconds.
approve_orderreturns a link to the order's LabPrints checkout page. Pay with Stripe test card 4242 4242 4242 4242, any future expiry, any CVC. Nothing is charged. The order showspaid.- Nothing is made, sent or charged.
- Test orders and their photos are deleted after 14 days.
Products
Flip Pad
A notepad printed with 1 to 4 of your photos across its pages.
- Product id:
flip. Available. - Price: $20, before shipping and sales tax.
- 1 to 4 photos (image/jpeg, image/png). Photos below 180 dpi at their printed size are accepted but may print soft.
- Proof turnaround: 1 - 2 business days.
- Ships to: US.
No options to choose.
No text fields.
The customer chooses shipping on the checkout page, which shows each choice's price. Delivery times are estimates, not promises.
| Shipping | Estimate |
|---|---|
| Standard Shipping | 5 - 7 Business Days |
| Second Day | 2 Business Days |
| Next Business Day | No estimate given |
Fold
An accordion-fold brag book with up to 10 of your photos, front and back.
- Product id:
fold. Available. - Price: $20, before shipping and sales tax.
- 1 to 10 photos (image/jpeg, image/png). Photos below 180 dpi at their printed size are accepted but may print soft.
- Proof turnaround: 1 - 2 business days.
- Ships to: US.
Options:
- Cover color (
cover_color):dark,light
No text fields.
The customer chooses shipping on the checkout page, which shows each choice's price. Delivery times are estimates, not promises.
| Shipping | Estimate |
|---|---|
| Standard Shipping | 5 - 7 Business Days |
| Second Day | 2 Business Days |
| Next Business Day | No estimate given |
Tools
There are 8 tools. Each is classified as a read (retrieves without changing anything), a write (creates or changes data, or acts for the user) or a sensitive write (consequential, and approved by the user every time).
- get_service_info
- list_products
- get_product_spec
- create_order
- get_proof
- request_proof_changes
- approve_order
- order_status
get_service_info
Read. Changes nothing. Repeating the same call has the same effect as making it once.
Describe what the LabPrints connector offers right now: its version, each tool and how it is classified (read, write or sensitive write), the products and whether each is available, how payment works, what data it handles and how long it keeps it, the rate limits, and whether your API key is in test mode. Changes nothing.
Inputs
No inputs.
Returns
| Field | Type | Notes |
|---|---|---|
version | string | |
effective_date | string | |
tools | array | |
tools[].name | string | |
tools[].classification | one of read, write, sensitive_write | |
products | array | |
products[].product_id | string | |
products[].name | string | |
products[].status | one of available, unavailable | |
intake_methods | array | |
payment_mode | always "stripe_checkout" | |
data_handled | array | |
retention_days | object | |
retention_days.photos | integer | |
retention_days.proof_images | integer | |
retention_days.contact_details | integer | |
retention_days.order_details | integer | |
retention_days.test_orders | integer | |
retention_days.order_records | integer | |
retention_days.account_contact | integer | |
retention_days.logs | integer | |
rate_limits | object | |
rate_limits.per_key_per_minute | integer | |
rate_limits.per_ip_per_second | integer | |
rate_limits.per_ip_burst | integer | |
test_mode | boolean | |
docs_url | string | |
changelog_url | string |
list_products
Read. Changes nothing. Repeating the same call has the same effect as making it once.
List the print products you can order through LabPrints, with each one's price (before shipping and sales tax), proof turnaround and availability. Changes nothing. Call get_product_spec for everything needed to start an order.
Inputs
No inputs.
Returns
| Field | Type | Notes |
|---|---|---|
products | array | |
products[].product_id | string | |
products[].name | string | |
products[].short_description | string | |
products[].price | object | The list price, before shipping and sales tax, which the checkout page adds. |
products[].price.amount_cents | integer | |
products[].price.currency | always "USD" | |
products[].proof_turnaround_business_days | object | |
products[].proof_turnaround_business_days.min | integer | |
products[].proof_turnaround_business_days.max | integer | |
products[].status | one of available, unavailable |
get_product_spec
Read. Changes nothing. Repeating the same call has the same effect as making it once.
Get everything needed to start an order for one product: how many photos it takes and in what formats, the options to choose, any text fields and their limits, the price (before shipping and sales tax), the proof turnaround, and where it ships with the shipping choices and their delivery estimates. Also returns the length limits for the other text you send (instructions, photo captions and links, contact email), and the largest photo accepted. Changes nothing.
Inputs
| Field | Type | Required | Notes |
|---|---|---|---|
product_id | string | yes | An id from list_products. |
Returns
| Field | Type | Notes |
|---|---|---|
product | object | |
product.product_id | string | |
product.name | string | |
product.short_description | string | |
product.status | one of available, unavailable | |
product.price | object | The list price, before shipping and sales tax, which the checkout page adds. |
product.price.amount_cents | integer | |
product.price.currency | always "USD" | |
product.photos | object | |
product.photos.min_count | integer | |
product.photos.max_count | integer | |
product.photos.formats | array | |
product.photos.low_resolution_warning_dpi | integer | Photos below this resolution at their printed size are accepted, but may print soft; tell the user. |
product.options | array | |
product.options[].key | string | |
product.options[].label | string | |
product.options[].choices | array | |
product.options[].choices[].value | string | |
product.options[].choices[].label | string | |
product.options[].default | string or null | null means the order must choose a value. |
product.text_fields | array | |
product.text_fields[].key | string | |
product.text_fields[].label | string | |
product.text_fields[].required | boolean | |
product.text_fields[].max_length | integer | In Unicode code points after NFC normalisation (some emoji count as several). |
product.text_fields[].max_lines | integer or null | null means no line limit. |
product.proof_turnaround_business_days | object | |
product.proof_turnaround_business_days.min | integer | |
product.proof_turnaround_business_days.max | integer | |
product.ships_to | array | ISO 3166-1 country codes. |
product.shipping_options | array | The customer chooses one on the checkout page, which shows its price. |
product.shipping_options[].label | string | |
product.shipping_options[].estimate | string or null | Delivery estimate, as the checkout shows it; null when the checkout shows none. |
field_limits | object | |
field_limits.instructions_max_length | integer | In Unicode code points after NFC normalisation (some emoji count as several). |
field_limits.photo_caption_max_length | integer | In Unicode code points after NFC normalisation (some emoji count as several). |
field_limits.photo_url_max_length | integer | In Unicode code points after NFC normalisation (some emoji count as several). |
field_limits.email_max_length | integer | In Unicode code points after NFC normalisation (some emoji count as several). |
max_photo_bytes | integer |
create_order
Write. Changes data or acts for the user. Not idempotent: repeating the call can have a new effect. Reaches beyond LabPrints (downloads the user's photo links or opens a checkout).
Start an order: send photos (as links the LabPrints server can download) and a brief, and LabPrints makes a proof for the user to review. This does not place the order: nothing is printed or charged until the customer approves a proof and pays on the checkout page that approve_order links to. Downloads each photo and stores it with the order. Returns the order_id, the price (before shipping and sales tax) and when the proof is expected; then call get_proof. Not idempotent: when retrying, send the same idempotency_key so a retry cannot create a second order. In test mode, a sample proof is ready within seconds.
Inputs
| Field | Type | Required | Notes |
|---|---|---|---|
product_id | string | yes | An id from list_products. |
photos | array | yes | The photos to choose from. get_product_spec gives the minimum and maximum count. |
photos[].url | string | yes | An https link the LabPrints server can download the photo from. |
photos[].caption | string | no | Optional note about this photo for the person building the proof. |
photos[].priority | one of high, normal, low | no | How much the user wants this photo used. It reaches the person building the proof as a note; it doesn't choose photos automatically. |
options | object | no | Option key to chosen value, from get_product_spec. |
text | object | no | Text field key to the text to print, from get_product_spec. Leave it out if the product has no text fields. |
instructions | string | no | The user's brief, in their words: what matters, what to avoid, the occasion. |
contact | object | yes | |
contact.email | string | yes | Where LabPrints sends word about the proof and the order. The shipping address is entered at checkout. |
idempotency_key | string | no | Any unique string of up to 64 letters, digits, '-' or '_'. Reuse it only to retry this same order. |
Returns
| Field | Type | Notes |
|---|---|---|
order_id | string | The order_id that create_order returned. |
status | one of proofing, proof_ready, revising, awaiting_payment, paid, in_production, shipped, cancelled | |
proof_eta | object or null | When the next proof is expected; null if none is being made. |
proof_eta.earliest | string | |
proof_eta.latest | string | |
price_quote | object | The list price, before shipping and sales tax, which the checkout page adds. |
price_quote.amount_cents | integer | |
price_quote.currency | always "USD" | |
next_step | string |
get_proof
Read. Changes nothing. Repeating the same call has the same effect as making it once.
Get the proof for an order: an image of each part of the product (a Fold has a front and a back), and the details an approval would cover (product, options, text and price). Show the user every image and those details before asking them to approve. In test mode the images are samples, and each label says so. Image links may expire within minutes; call again for fresh ones. Returns the latest version unless you pass an earlier one. Changes nothing.
Inputs
| Field | Type | Required | Notes |
|---|---|---|---|
order_id | string | yes | The order_id that create_order returned. |
version | integer | no |
Returns
| Field | Type | Notes |
|---|---|---|
order_id | string | The order_id that create_order returned. |
version | integer | |
created_at | string | |
status | one of proofing, proof_ready, revising, awaiting_payment, paid, in_production, shipped, cancelled | |
sample | boolean | True in test mode: the images are samples, not a proof of this order. |
approval_required | boolean | True while this proof is waiting for the user's approval; false once it is approved, superseded or cancelled. |
pages | array | |
pages[].label | string | What the image shows, for example "Fold, front". In test mode it starts "Sample proof (test mode): ". |
pages[].image_url | string | |
order_summary | object | |
order_summary.product_id | string | |
order_summary.product_name | string | |
order_summary.options | object | |
order_summary.text | object | |
order_summary.price | object | The list price, before shipping and sales tax, which the checkout page adds. |
order_summary.price.amount_cents | integer | |
order_summary.price.currency | always "USD" |
request_proof_changes
Write. Changes data or acts for the user. Not idempotent: repeating the call can have a new effect.
Ask for a revised proof, in the user's words: for example a different cover photo. Optionally list photos to leave out or to favour. The current proof can no longer be approved; call get_proof when the new version is ready. Places no order and charges nothing.
Inputs
| Field | Type | Required | Notes |
|---|---|---|---|
order_id | string | yes | The order_id that create_order returned. |
instructions | string | yes | What to change, in the user's words. |
exclude_photos | array | no | Positions in the photos list sent to create_order; 1 is the first photo. They reach the person building the proof as a note, not as an automatic choice. |
prefer_photos | array | no | Positions in the photos list sent to create_order; 1 is the first photo. They reach the person building the proof as a note, not as an automatic choice. |
Returns
| Field | Type | Notes |
|---|---|---|
order_id | string | The order_id that create_order returned. |
status | always "revising" | |
proof_eta | object | |
proof_eta.earliest | string | |
proof_eta.latest | string | |
version_expected | integer |
approve_order
Sensitive write. Consequential: the assistant must get the user's approval every time. Repeating the same call has the same effect as making it once. Reaches beyond LabPrints (downloads the user's photo links or opens a checkout).
Record that the user approves one proof version, lock it, and get the link to its LabPrints checkout page; the order then awaits payment. The order is placed only when the customer pays on that page, which adds shipping and sales tax to the price; this call charges nothing. Then, if you can, open checkout_url in your own browser and complete payment there, with the customer confirming it; otherwise give the link to the customer. Call it only after the user has seen that exact proof version, including its price, and has explicitly told you to go ahead; ask every time. Requires the current proof_version and confirm: true. Fails if a newer proof exists or anything the user saw has changed; then show the new proof and ask again. Calling it again for the same version returns the same link while the order awaits payment; once a live order is paid or cancelled, status says so and there is no link. In test mode, pay on that page with Stripe test card 4242 4242 4242 4242, any future expiry, any CVC: nothing is charged, and the order shows paid.
Inputs
| Field | Type | Required | Notes |
|---|---|---|---|
order_id | string | yes | The order_id that create_order returned. |
proof_version | integer | yes | The version the user saw and approved. |
confirm | always true | yes | Must be true: the user explicitly approved this proof. |
Returns
| Field | Type | Notes |
|---|---|---|
order_id | string | The order_id that create_order returned. |
status | one of proofing, proof_ready, revising, awaiting_payment, paid, in_production, shipped, cancelled | |
approved_version | integer | |
price | object | The list price, before shipping and sales tax, which the checkout page adds. |
price.amount_cents | integer | |
price.currency | always "USD" | |
checkout_url | string or null | Open this link in your own browser and complete payment there if you can, with the customer confirming; otherwise give it to the customer. Shipping is chosen on that page. In test mode, pay there with Stripe test card 4242 4242 4242 4242, any future expiry, any CVC; nothing is charged. null once a live order is no longer waiting for payment. |
order_status
Read. Changes nothing. Repeating the same call has the same effect as making it once.
Check where an order is (proofing, proof ready, revising, awaiting payment, paid, in production, shipped or cancelled), with the time of each step, the current proof version, and tracking once it has shipped. Changes nothing.
Inputs
| Field | Type | Required | Notes |
|---|---|---|---|
order_id | string | yes | The order_id that create_order returned. |
Returns
| Field | Type | Notes |
|---|---|---|
order_id | string | The order_id that create_order returned. |
status | one of proofing, proof_ready, revising, awaiting_payment, paid, in_production, shipped, cancelled | |
proof_version | integer or null | null until the first proof is ready. |
timestamps | array | Each status the order has entered, oldest first. |
timestamps[].status | one of proofing, proof_ready, revising, awaiting_payment, paid, in_production, shipped, cancelled | |
timestamps[].at | string | |
timestamps[].proof_version | integer | Set on proof_ready. |
tracking | object | |
tracking.carrier | string | |
tracking.tracking_number | string | |
tracking.url | string |
Limits on text
Lengths count Unicode characters after normalisation. get_product_spec reports
the same numbers.
| Field | Limit (characters) |
|---|---|
instructions_max_length | 1500 |
photo_caption_max_length | 150 |
photo_url_max_length | 2048 |
email_max_length | 60 |
Order statuses
| Status | Meaning |
|---|---|
proofing | The order was received and the first proof is being made. |
proof_ready | A proof is waiting for the user's review. |
revising | Changes were requested and the next proof version is being made. |
awaiting_payment | A proof version was approved and locked; its checkout page is waiting for payment. |
paid | The customer paid on the checkout page: the order is placed. In test mode, payment is made with a Stripe test card, and nothing is charged. |
in_production | The order is being printed. |
shipped | The order has shipped; tracking is available. |
cancelled | The order was cancelled. |
Errors
A failed call returns {code, message, how_to_fix}; the code is stable.
The service reports stripe_checkout as its payment mode.
| Code | Meaning |
|---|---|
unauthorized | The API key is missing, wrong or revoked. |
rate_limited | Too many requests; wait and retry. |
production_access_on_request | This is a production key, and production access is available on request; use a test key meanwhile. |
invalid_input | The arguments do not match the tool's input schema. |
product_not_found | There is no product with that id. |
product_unavailable | The product exists but cannot be ordered right now. |
option_invalid | An option, or its value, is not one the product offers. |
text_invalid | A text field is too long or has characters the product cannot print. |
too_few_photos | Fewer photos than the product's minimum. |
too_many_photos | More photos than the product's maximum. |
photo_url_not_allowed | A photo link is not an https link to a public address. |
photo_unreachable | A photo link could not be downloaded. |
photo_invalid | A photo is the wrong type, too large, or too small. |
email_invalid | The contact email is not a valid address. |
order_not_found | There is no such order for this API key. |
order_cap_reached | This API key has reached its order limit. |
idempotency_conflict | The idempotency_key was already used for a different order. |
invalid_order_state | The order's state does not allow this call. |
proof_not_ready | The proof is not ready yet; try again after the expected time. |
proof_version_not_found | That proof version does not exist. |
proof_version_stale | A newer proof exists than the version sent; show the user the new one. |
proof_changed | Something the user saw has changed since that version; show the user the new proof. |
confirmation_required | approve_order was called without confirm: true. |
internal_error | A fault on our side; retry later. |
service_unavailable | The service is down or restarting (HTTP 503). |
timeout | The request took too long (HTTP 504). |
request_too_large | The request body is too large (HTTP 413). |
not_found | No such path; the connector is served only at /mcp (HTTP 404). |
Rate limits
- 60 requests per minute per API key.
- 20 requests per second per IP address, with a burst of 40.
Over the limit, a call fails with rate_limited (HTTP 429).
Data handling
What the connector handles:
- photos, downloaded from the links you send
- the brief and options for the order
- a contact email for word about the proof and the order
- LabPrints uses all of the above to make the proof and the order, which professional print partners print; payment is taken by Stripe on the LabPrints checkout page, where the shipping address is entered
Photo links are used once to download the photo and are not stored.
The connector's own copies are deleted on this schedule. Proofs and LabPrints' own order records are kept under the LabPrints privacy policy; Stripe keeps payment records under its own.
| Data | Kept for | Counted from |
|---|---|---|
| customer photos | 45 days | the photos are received, whatever the order's state |
| proof images made for the order | 45 days | the proof image is uploaded |
| the contact email | 45 days | the order is created |
| the order's brief, photo captions and preferences, options, change requests, and a keyed fingerprint of the request and its idempotency key (for safe retries) | 45 days | the order is created (deleted with the photos) |
| everything for a test-mode order: its photos, any proof images and its record | 14 days | the order is created |
| the connector's order records: ids, product, status and timestamps | 365 days | the order is created |
| an API key holder's contact email | 30 days | the account is closed |
| service logs | 30 days | the log line is written |
Roadmap
Planned, not available:
- Photo intake from gallery links: planned, not available
- Automated proofs: planned, not available
- OAuth sign-in with PKCE, in place of API keys: planned, not available
- More products: planned, not available