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 theteam_orderscapability. 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.
| collection | Name | What it is |
|---|---|---|
| thank-you | The Thank You | Candle gift set and a printed card. Client appreciation, event follow-ups. |
| welcome | The Welcome | Candle and mug set, notebook, card. New hires and day-one desks. |
| milestone | The Milestone | The anniversary and promotion tier. |
| executive | The Executive | The 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, valueBearer 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
30per60000ms, 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.
| Field | Meaning | |
|---|---|---|
| idempotency_key | required | A 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_name | required | Shown on the invitation and the parcel. |
| recipient_email | required | Where the private claim link goes. |
| recipient_company | optional | Kept on the team order and the manifest. |
| collection | one of these four | A 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_id | Your own product instead of a collection (what custom-listings and gift-sets return). | |
| message | optional | The personal line on the invitation; with printed_card: true it is also printed on a 5x7 card in the parcel (1000 characters). |
| sender | optional | The name the recipient sees. Defaults to your Printonic for Teams team name, then the partner name on the key. |
| printed_card | optional | true puts the message on a printed card (needs message). |
| destination_countries | optional | Where the recipient may be: US (default), CA, GB, AU, DE, FR, NL, IT, ES. A comma separated string is fine. |
| claim_expiry_days | optional | 7 to 90 days for the recipient to enter an address (default 30). |
| reference | optional | Your reference on every read and in the manifest (defaults to the idempotency_key). |
| name | optional | The team order name in the dashboard (defaults to the collection name and the recipient). |
| return_claim_link | optional | true 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_keyis 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; a402leaves 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
| HTTP | code | Meaning |
|---|---|---|
| 400 | idempotency_key_required, recipient_name_required, recipient_email_invalid, gift_required, invalid_gift, gift_message_required, invalid_destination_countries, invalid_claim_expiry_days, sender_required | Fix the row; nothing was created or charged. |
| 401 / 403 | Missing key, or a key without the team_orders capability. | |
| 402 | insufficient_funds, credit_limit_exceeded, daily_spend_limit_exceeded, card_charge_failed | The 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. |
| 404 | unknown_collection, sku_not_found, team_order_not_found | The body lists the collections; a sku_code must be on your account. |
| 409 | idempotency_payload_mismatch, collection_unavailable | The row id was already used with a different gift, or a kit product is unavailable right now. |
| 429 | Per-key rate limit. Set the column's rate limit below it (for example 30 requests per 60000 ms) and re-run the errored cells. | |
| 503 | idempotency_unavailable | Retry shortly with the same key. |