Partners

Send gifts from Clay

One HTTP API enrichment, one gift per row. Clay finds the person, Printonic makes and ships the gift, the row shows when it was claimed, shipped and delivered.

What you need

  • A partner key (Authorization: Bearer pk_live_...) bound to your Printonic account with the team_orders capability. Gifts are charged to that account exactly like team orders: prepaid wallet (then the saved card) or net terms.
  • A Clay table with the recipient's name and email, plus a stable row id or CRM id for the idempotency_key.
  • A gift: one of the four Printonic for Teams collections below, or your own product by sku_code.
collectionNameWhat it is
thank-youThe Thank YouCandle gift set and a printed card. Client appreciation, event follow-ups.
welcomeThe WelcomeCandle and mug set, notebook, card. New hires and day-one desks.
milestoneThe MilestoneThe anniversary and promotion tier.
executiveThe ExecutiveThe top tier for executives and key accounts.

The recipient enters their own address through a private claim link (emailed by Printonic) and picks options such as size there; nothing ships until the address arrives, and the link expires after claim_expiry_days.

The HTTP API enrichment

In your Clay table click Add enrichment, search for HTTP API and open the Configure tab. Column values are referenced by typing / in a field and picking the column; Clay shows the reference as /Column Name, which is how the samples below are written. Put quotes around every string reference (Clay requires them; numbers and booleans go without).

  • HTTP method POST
  • API endpoint URL https://printonic.com/api/external/team-orders/quick
  • Header fields add a header account instead of typing the key: Select header account > + Add account, key Authorization, value Bearer pk_live_..., name it and save. Clay stores it encrypted at the workspace level (Settings > Connections) and reuses it across columns; a header typed into the column is visible to everyone who can see the table. Content type is set by Clay (application/json).
  • JSON body the template below with your columns referenced
  • Field paths to return team_order_id, claim_status, order_id, charged_cents (or leave it empty and pick fields from the cell details afterwards with Add as column)
  • Rate limiting for example request limit 30 per 60000 ms, below your key's limit
{
  "idempotency_key": "/Row ID",
  "recipient_name": "/Full Name",
  "recipient_email": "/Email",
  "recipient_company": "/Company",
  "collection": "welcome",
  "message": "/Gift message",
  "sender": "Acme Inc",
  "printed_card": true
}

A missing or blank required value answers a 400 for that row only. To ship your own product instead of a collection, swap collection for sku_code:

{
  "idempotency_key": "/Row ID",
  "recipient_name": "/Full Name",
  "recipient_email": "/Email",
  "sku_code": "PRNTC-7K2M-9QX4",
  "message": "/Gift message"
}

Click Test to run one row (use a colleague's email: the claim email arrives within a minute and money moves on every 201), then run the column on the rows you mean to send. Keep the table's auto-run off, or use Only run if with a formula that checks a "Send" column, so a new row does not send a gift by itself.

FieldMeaning
idempotency_keyrequiredA stable id for the row (Clay's row id, or the contact's CRM id). Repeating it replays the original result instead of sending a second gift; the same key with a different body is a 409.
recipient_namerequiredShown on the invitation and the parcel.
recipient_emailrequiredWhere the private claim link goes.
recipient_companyoptionalKept on the team order and the manifest.
collectionone of these fourA Printonic for Teams kit: thank-you, welcome, milestone or executive. The recipient chooses options such as size on the claim page.
sku_code / merchant_sku_id / merchant_product_idYour own product instead of a collection (what custom-listings and gift-sets return).
messageoptionalThe personal line on the invitation; with printed_card: true it is also printed on a 5x7 card in the parcel (1000 characters).
senderoptionalThe name the recipient sees. Defaults to your Printonic for Teams team name, then the partner name on the key.
printed_cardoptionaltrue puts the message on a printed card (needs message).
destination_countriesoptionalWhere the recipient may be: US (default), CA, GB, AU, DE, FR, NL, IT, ES. A comma separated string is fine.
claim_expiry_daysoptional7 to 90 days for the recipient to enter an address (default 30).
referenceoptionalYour reference on every read and in the manifest (defaults to the idempotency_key).
nameoptionalThe team order name in the dashboard (defaults to the collection name and the recipient).
return_claim_linkoptionaltrue adds claim_url to the answer. It is the recipient's only credential; keep it out of shared tables.

The answer is one flat row

Every value is a scalar so a field path can pick it by name: team_order_id (keep it, the status poll uses it), claim_status, order_id, charged_cents. The full team order is nested under detail. A 200 with already_existed: true is the replay of a row that already ran; a 402 means the team order exists but is unpaid.

201 Created
{
  "team_order_id": 77,
  "status": "collecting_addresses",
  "reference": "row_8f3a",
  "recipient_id": 501,
  "recipient_name": "Jane Doe",
  "recipient_email": "jane@globex.com",
  "claim_status": "awaiting",
  "claim_label": "Waiting for address",
  "claim_expires_at": "2026-10-10T12:00:00.000Z",
  "invite_sent_at": "2026-09-10T12:00:01.000Z",
  "order_id": 9001,
  "order_external_id": "ord_00009001",
  "shipment_status": "submitted",
  "shipment_stage": "awaiting_address",
  "tracking_url": null,
  "shipped_at": null,
  "delivered_at": null,
  "total_cents": 5049,
  "charged_cents": 5049,
  "billing_mode": "net_terms",
  "collection": { "slug": "welcome", "name": "The Welcome" },
  "already_existed": false,
  "detail": { "team_order": { ... }, "recipients": [ ... ], "totals": { ... }, "billing": { ... } }
}

Claim, shipped and delivered status back in the table

Poll. Add a second HTTP API enrichment with method GET and the URL below, inserting the team_order_id column from the first call into the path. Return claim_status (awaiting, claimed, expired), shipment_stage, tracking_url, shipped_at and delivered_at. Re-run it on a schedule (table settings > Run settings > re-run columns on a schedule: daily on every plan, hourly on Enterprise) and give it an Only run if formula such as {{delivered_at}} == "" (run while the delivered date is still empty), so it stops once the gift is delivered. There is no charge for reads.

GET /api/external/team-orders//Team order id/quick
(the URL with the team_order_id column inserted after /team-orders/)

200 OK
{
  "team_order_id": 77,
  "status": "shipped",
  "claim_status": "claimed",
  "claimed_at": "2026-09-11T15:02:00.000Z",
  "shipment_stage": "shipped",
  "carrier": "USPS",
  "tracking_number": "9400...",
  "tracking_url": "https://www.aftership.com/track/9400...",
  "shipped_at": "2026-09-14T17:40:12.000Z",
  "delivered_at": null,
  ...
}

Or receive webhooks. Register an https endpoint with POST /api/external/webhooks for team_order.claimedteam_order.shippedteam_order.deliveredteam_order.needs_attentionand Printonic sends a signed POST as each gift is claimed, shipped and delivered. A Clay table can be the receiver: in a workbook click + Add, search for Webhooks, choose Monitor webhook and copy its URL (add the optional authentication token if you want Printonic's calls checked; a webhook table takes up to 50,000 submissions). Every event lands as a new row carrying data.team_order.id, data.team_order.reference (your row id) and the shipment, which a Lookup single row column in the sending table can join on. Details and the signature sample are on the team orders page.

Running it safely

  • idempotency_key is what makes re-running the column safe. Use a value that never changes for the row; never a timestamp. Clay re-runs cells when inputs change or when you update out-of-date cells, and it does not document automatic retries, so treat every run as a possible repeat: with the same key it is a replay, never a second gift.
  • Money moves on every 201. Check your wallet balance or credit limit before running a whole table; a 402 leaves the gift unpaid until you fund the account and re-run the row with the same key.
  • Cancel a gift before production with POST /api/external/team-orders/{team_order_id}/cancel; the charge goes back to the wallet.
  • Clay does not document escaping of referenced values inside a JSON body. Keep free text (the message) short and free of double quotes, or build it with a formula column that strips them.

Error codes

HTTPcodeMeaning
400idempotency_key_required, recipient_name_required, recipient_email_invalid, gift_required, invalid_gift, gift_message_required, invalid_destination_countries, invalid_claim_expiry_days, sender_requiredFix the row; nothing was created or charged.
401 / 403Missing key, or a key without the team_orders capability.
402insufficient_funds, credit_limit_exceeded, daily_spend_limit_exceeded, card_charge_failedThe team order exists unpaid (team_order_id in the body). Fund the account and run the row again with the SAME idempotency_key, or cancel it.
404unknown_collection, sku_not_found, team_order_not_foundThe body lists the collections; a sku_code must be on your account.
409idempotency_payload_mismatch, collection_unavailableThe row id was already used with a different gift, or a kit product is unavailable right now.
429Per-key rate limit. Set the column's rate limit below it (for example 30 requests per 60000 ms) and re-run the errored cells.
503idempotency_unavailableRetry shortly with the same key.

Team orders and webhooks |Step by step in the Help Center