tangibl.
BUILD WITH TANGIBL

Made for agents.
Grounded in the real world.

Assistants list products, save a customer’s image as a draft, show a preview on the actual product, and start a Stripe checkout, through a REST API or an MCP server with the same operations.

Muse, Claude, ChatGPT and other assistants connect through the MCP server. The REST API offers the same operations for custom integrations. Ordering is open, with payment on a Stripe checkout page.

One service. Two ways in.

The website and the connector share the same customer, draft and order records. Wall art (photo print, matte poster, framed print, canvas gallery wrap and metal print, up to 24×36 inches) and merchandise (T-shirt, hoodie, mug, stickers, blanket and playing cards), one image each. listProducts returns each product’s artwork rules and live price. Every preview is drawn from the exact file that gets printed.

Connection

Assistants connect to the MCP server at https://gettangibl.com/api/mcp (Streamable HTTP) and register themselves through OAuth dynamic client registration, so customers only add the address and sign in. /connect describes setup for assistants; /connect/usage explains day-to-day use. Connector configuration:

mcp_server_url: https://gettangibl.com/api/mcp
transport: Streamable HTTP
auth_scheme: oauth2_code (PKCE S256)
authorization_url: https://gettangibl.com/oauth/authorize
token_url: https://gettangibl.com/oauth/token
registration_url: https://gettangibl.com/oauth/register
revoke_url: https://gettangibl.com/oauth/revoke
token_endpoint_auth_method: none
scopes: drafts

The MCP tools are the operations below under the same names. The REST API is described by an OpenAPI document at /api/v1/openapi and uses the same OAuth sign-in. Instead of file uploads, MCP clients save new images with uploadDraftImage (a file the app passes, such as ChatGPT’s openai/fileParams), uploadDraftFromUrl (a public https link) or createUploadLink, a single-use page where the customer picks the image (getUploadLink returns the result).

Accounts & permissions

Customers sign in to Tangibl with Google, Facebook or email and approve the connection. They can disconnect it from My Tangibl at any time.

  • Customer identity comes from the access token, never from an agent-supplied customer ID.
  • Every draft and order is checked against the signed-in owner.
  • Supplier credentials, payment secrets and print files stay server-side.
  • Disconnecting stops future access; it does not cancel paid orders.

Operations

OperationRequestPurpose
listProductsGET /api/v1/productsProducts, artwork rules and generation hints
uploadDraftPOST /api/v1/uploadsmultipart/form-data: file (JPEG/PNG), product_id, name, optional occasion and note; header Idempotency-Key (new UUID); optional ?parent_id=
uploadCanvasPrintFilePOST /api/v1/canvas/uploadsmultipart/form-data: file (3300×3900), product_id=canvas-print, name, optional occasion and note; header Idempotency-Key
listMyDraftsGET /api/v1/draftsRecent drafts with their names, occasions and notes
reviseDraftProductPOST /api/v1/draftsapplication/json: {"parent_id", "product_id", optional "name", "occasion", "note"}; header Idempotency-Key. Same artwork, another product; not for new images
getDraftGET /api/v1/drafts/{id}One draft
deleteDraftDELETE /api/v1/drafts/{id}Permanently delete a creation (not possible once it has an order). Only when the customer clearly asks, after confirming which one
updateDraftDetailsPATCH /api/v1/drafts/{id}application/json: any of {"name", "occasion", "note"}; empty string clears
getDraftPreviewGET /api/v1/drafts/{id}/previewArtwork image (JPEG)
getDraftProofGET /api/v1/drafts/{id}/proofPreview status
getDraftProofImageGET /api/v1/drafts/{id}/proof/imageProduct preview image (JPEG)
requestSupplierMockupPOST /api/v1/drafts/{id}/supplier-mockupRetry a failed canvas or poster preview
startCheckoutPOST /api/v1/drafts/{id}/checkoutapplication/json: optional {"quantity"} (1–5), {"size"} (required for T-shirt and hoodie: ask the customer) and {"address_id"} (where it ships, from listAddresses; required once the customer has saved addresses); header Idempotency-Key (new UUID per order). Returns total, ship_to and a Stripe checkout_url
getCartGET /api/v1/cartThe saved cart: items, prices, one shipping line, total
addToCartPOST /api/v1/cart/itemsapplication/json: {"draft_id", optional "size" (required for T-shirt and hoodie), optional "quantity" 1–5}; same creation and size again sets its quantity. Returns the cart
updateCartItemPATCH /api/v1/cart/items/{cart_item_id}application/json: {"quantity"} 1–5. Returns the cart
removeCartItemDELETE /api/v1/cart/items/{cart_item_id}Remove one line (POST …/delete also works). Returns the cart
checkoutCartPOST /api/v1/cart/checkoutapplication/json: {"address_id"} (where it ships; required once the customer has saved addresses); header Idempotency-Key (new UUID per order). One order and one Stripe checkout_url for the whole cart; the cart empties once paid
listAddressesGET /api/v1/addressesThe address book: labelled US and Colombian addresses ("Me", "Mom") with address_id and the default
createAddressLinkPOST /api/v1/addresses/linksapplication/json: {"label"}. A single-use page (address_url, 30 minutes) where the customer enters the address; same label replaces a saved one
getAddressLinkGET /api/v1/addresses/links/{address_link_id}waiting, saved (with the address and address_id) or expired
updateAddressPATCH /api/v1/addresses/{address_id}application/json: {"label"} and/or {"is_default": true}
deleteAddressDELETE /api/v1/addresses/{address_id}Remove an address (POST …/delete also works). Only when the customer clearly asks
listMyOrdersGET /api/v1/ordersRecent orders and their status
getOrderGET /api/v1/orders/{id}One order: payment status, total, tracking; report_problem_url (paid orders) is where the customer reports a misprint with photos
sendFeedbackPOST /api/feedbackapplication/json: {"message", "topic", "agent_name", "context"}

Write operations take an Idempotency-Key. Prices come from the server; an agent cannot set a total or mark an order as paid.

Examples

Save an image from a public link as an 11 oz mug. Over MCP, send a JSON-RPC tools/call to https://gettangibl.com/api/mcp with an access token issued for that endpoint:

POST /api/mcp
Authorization: Bearer <access token>
Content-Type: application/json
MCP-Protocol-Version: 2025-11-25

{"jsonrpc": "2.0", "id": 3, "method": "tools/call",
 "params": {"name": "uploadDraftFromUrl",
  "arguments": {"image_url": "https://example.com/art.png", "product_id": "mug", "name": "Review Mug"}}}

The result carries the saved draft in structuredContent, and the same JSON as text in content. Selected fields (the full draft also includes image, proof and message):

{
  "draft_id": "3f2b8c1e-5d4a-4c7b-9e2f-1a6d0b9c8e7f",
  "name": "Review Mug",
  "product_id": "mug",
  "size": null,
  "review_url": "https://gettangibl.com/account/drafts/3f2b8c1e-5d4a-4c7b-9e2f-1a6d0b9c8e7f",
  "preview_url": "https://gettangibl.com/api/v1/drafts/3f2b8c1e-5d4a-4c7b-9e2f-1a6d0b9c8e7f/preview",
  "preview_requires_bearer": true
}

Then call getDraftProofImage with {"draft_id": "…"}; it returns the product preview as an image content block (JPEG). MCP calls take no Idempotency-Key: the server derives one from the connection, tool, arguments and a fixed 15-minute window, so an identical call on the same connection within that window returns the same draft. A retry that falls in the next window can create a new one.

Tool execution errors return isError: true with {"error": "<code>", "message": "…"} as text; the message says what to change. A missing or invalid token gets HTTP 401 with a WWW-Authenticate header, and a malformed message gets a JSON-RPC error.

The same upload over REST is a multipart request. Generate the key once and send the same key when retrying, so a retry does not save a second draft:

KEY=$(uuidgen)
curl https://gettangibl.com/api/v1/uploads \
  -H "Authorization: Bearer <access token>" \
  -H "Idempotency-Key: $KEY" \
  -F file=@art.png -F product_id=mug -F name="Review Mug"

REST errors use HTTP status codes with a {"error": "<code>", "message": "…"} body.

A token requested for one endpoint with the OAuth resource parameter (https://gettangibl.com/api/mcp or https://gettangibl.com/api/v1) is rejected by the other with HTTP 401. A token issued for https://gettangibl.com, or without a resource, works on both.

Limits

  • Images: JPEG, PNG or WebP, up to 4 MB. listProducts gives each product’s shape and minimum resolution.
  • MCP messages up to 64 KiB, batches up to 20 messages, 60 seconds per request.
  • Upload pages (createUploadLink): 30 per customer per hour. Address pages (createAddressLink): 20 per customer per hour. Both expire after 30 minutes.
  • sendFeedback: 5 per customer per hour.
  • Client registration (/oauth/register): 500 per hour.

Over a limit, the API returns HTTP 429 (over MCP, a tool error) with temporarily_unavailable or a specific code and a message saying when to try again. Make calls one at a time and back off on 429 or 503 rather than retrying immediately.

Orders

  1. The customer saves an image as a draft and sees the product preview.
  2. startCheckout returns the total and a Stripe checkout link. The customer pays on Stripe’s page, never in the chat.
  3. After payment, Tangibl makes and ships the order, and its status appears in My Tangibl and through listMyOrders.

Prices include US shipping. Made to order, so no change-of-mind returns; misprints are reprinted.

Integration questions →