Daymaker Partner API

Send a cake from your own backend

A custom cake — an edible print on top, baked by a vetted local bakery, hand-delivered to the recipient's door. One HTTP call creates the gift, routes it to a bakery near the address, renders the design and the card, and checks out.

Overview

Every request goes to one base URL, over HTTPS, with a JSON body:

https://daymaker.com/api/v1/partner

The machine-readable contract is /api/v1/partner/openapi.json — public, no key needed, so an agent framework can generate tool definitions before it has credentials. The reference at the bottom of this page is rendered from that document.

This or the MCP server

We run a public MCP server too, and for an assistant talking to us directly it is the better surface: conversational, self-describing, nothing to sign up for. It is the wrong surface for a platform embedding cake-sending in its own product, because every gift it opens belongs to a throwaway anonymous account.

MCP serverPartner API
CredentialPer-gift token, anonymousDurable dm_live_… key
AccountA fresh one every giftYours, plus a sub-account per end user
Retry safetyNone needed — a human is in the loopIdempotency-Key
PaymentA person pays a Stripe linkSaved card, prepaid balance, or a link

Both call the same code underneath, so they cannot drift on what a valid design is or whether a New York address needs a suite number.

Quickstart

1. Create a key

Open your Daymaker dashboard, go to Connect AI, and create a key under Building this into your own product? The account owner or an admin can — a key spends on behalf of the whole account. It is shown once; we store a hash, so a lost key is revoked and replaced rather than recovered.

Keys belong to that account, so everything they send appears in the campaign list the account already reads.

2. Check the key works

curl https://daymaker.com/api/v1/partner/catalog \
  -H "Authorization: Bearer $DAYMAKER_KEY"

/catalog returns sizes, shapes, from-prices and the design rules. It writes nothing and costs nothing.

3. Send a cake

curl -X POST https://daymaker.com/api/v1/partner/cakes \
  -H "Authorization: Bearer $DAYMAKER_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": {
      "company": "Acme Robotics",
      "contact_name": "Sara Chen",
      "address": "500 Howard St",
      "address2": "Suite 400",
      "city": "San Francisco",
      "state": "CA",
      "zip": "94105",
      "phone": "+14155550123"
    },
    "design": { "message_text": "CONGRATS SARA!" },
    "card":   { "message_text": "Congratulations on the raise.",
                "message_signature": "— the Acme team" },
    "cake_size": "8in"
  }'

201 comes back with a gift_id, the routing result, preview URLs for the design and the card, and a checkout block — either a Stripe URL to hand your user, or confirmation that the card on file was charged. Add "draft_only": true to build everything and stop before payment.

Authentication

Authorization: Bearer dm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

X-Api-Key: dm_live_… also works, as does a bare key with no Bearer — both are common first attempts, and a 401 on a key you are holding correctly is a miserable first five minutes.

Server-side only. A key in a browser is a key anyone can spend. Revoke one from the same Connect AI page; it stops working immediately.

A missing, malformed, unknown or revoked key answers 401. Nothing else on the API is anonymous.

End users

Every endpoint accepts an optional end_user — your own opaque id for the person a gift belongs to. Send it in the body, or as X-End-User on any request (the body wins if you send both).

  • Omit it. The gift belongs to your platform account and your card pays. This is the shape when you resell cakes and bill your users yourself.
  • Send it. The gift belongs to that user's own sub-account, with their own saved card, their own checkout links and their own history.

Sub-accounts are created on first use and namespaced to your key: two platforms both using "user_1" get two unrelated accounts, and one of your users can never read another's gift. The value is never matched against an email or an existing customer, so naming someone@else.com gets you a fresh row in your own namespace, not that person's account.

It is a per-call field. You do not have to decide up front.

Send a cake

POST /cakes runs the whole loop: create, route the recipient to a local bakery, render the design and the card, and check out. It requires an Idempotency-Key, because it creates and charges in one request.

It stops rather than guessing

If a step fails, the response carries completed: false and stopped_at, and nothing is charged. The gift_id still comes back, so the draft is recoverable with the step-by-step endpoints.

stopped_atWhat happened
recipientUndeliverable address, or a New York address with no suite line and no phone
designThe artwork was rejected — bad SVG, unfetchable URL, too large
cardThe card you asked for could not be rendered
checkoutNot payable — no size set, spend cap reached

A card that was asked for and not produced stops the call. Shipping a cardless cake with a warning nobody reads is worse than failing loudly.

No address? Send a website

"recipient": {
  "company": "Acme Robotics",
  "contact_name": "Sara Chen",
  "website_url": "https://acme.com"
}

We find and verify the company's mailing address. Only a high-confidence match proceeds — anything less stops the call and hands back the candidates for you to choose from, because a cake delivered to the wrong building is not something anyone can undo.

contact_name is required and is not the company name. A delivery addressed only to a company reaches a reception desk with nobody to hand it to; if there is no specific person, name the receiving department. A phone number is optional and is the difference between a delivered cake and a returned one — the bakery calls it when nobody answers the door.

Your own ids

Every gift-creating endpoint takes a metadata object. We store it, hand it back verbatim on reads and in webhooks, and never look inside it, so you do not have to keep a gift-id-to-your-object-id table. Limits: 30 keys, 4000 characters of JSON, flat values.

{ "metadata": { "crm_deal_id": "deal_4821", "campaign": "q3-launch" } }

Designs and inscriptions

design (the cake top) and card (the 4×6 card in the box) each take exactly one artwork field:

  • message_text — we typeset it. A few words on the cake; a real sentence on the card.
  • image_svg — an SVG you author, rasterized print-ready. No scripts, no external URLs.
  • image_url — a public https PNG, JPEG or WebP, up to 10MB.
  • image_base64 — the same bytes inline.

Sending two is a 400 rather than a precedence rule: silently printing one of them puts the wrong thing on a cake that cannot be recalled. Responses include print_fit_warnings — resubmit until they are clean.

A cake does not have to carry a print at all. Send inscription and the bakery writes those words on it in icing, by hand, up to 60 characters. That is the right shape for anything recurring: nobody wants to commission artwork on three hundred employee birthdays a year.

You sendThe cake gets
inscription onlyA plain iced cake with the words piped on it. No print.
One artwork fieldA printed design, no writing.
BothThe print, and the words piped alongside it.
NeitherA 400. A cake has to carry something.

On a gift going to several people the gift-wide inscription is a default and each recipient can override it, which is how one order carries ten different names.

Idempotency

POST /cakes requires an Idempotency-Key header. Every other write accepts one, and you should send it. Any unique string works; a UUID is ideal, and a deterministic key you can reconstruct (birthday-emp_8891-2026-09-10) is better still.

SituationResponse
Same key, same bodyThe original response, with Idempotent-Replay: true
Same key, different body422 — that is a bug on your side, and hiding it behind an unrelated response would be worse
Same key, first call still running409. Retry in a moment

Keys are scoped to your API key and kept for 24 hours.

Money and checkout

Drafts are free. Nothing is charged and nothing is baked until checkout. At checkout we charge the card on file if there is one, and otherwise return a Stripe URL for a person to pay in a browser. You never handle card details.

By default there is always a human in the loop: an agent looping out of control produces unpaid drafts and unpaid links, and nothing bakes.

That default is wrong for a genuinely recurring workflow — a link per birthday has automated nothing. A key can instead be set to settle from a prepaid balance, which comes back paid with no URL and no human. Ask us to turn it on; it carries a required 24-hour spend ceiling, and checkout answers 429 over the ceiling with the draft saved.

curl https://daymaker.com/api/v1/partner/balance \
  -H "Authorization: Bearer $DAYMAKER_KEY"

settles_from_balance is the field people misread first: a healthy balance on a key that does not settle from it still returns a payment link on every order. It is a property of the key, not the account. Running dry is silent — checkout quietly goes back to returning links — so set low_balance_usd_cents when you register a webhook and hear about it on the way down.

Webhooks

Rather than polling GET /gifts/{id}, register an endpoint and we will tell you. Subscriptions are partner-scoped: one feed covers every end user beneath your key, and a sub-account cannot register its own endpoint, because that would be a way to receive a sibling's deliveries.

curl -X POST https://daymaker.com/api/v1/partner/webhooks \
  -H "Authorization: Bearer $DAYMAKER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://yours.app/hooks/daymaker",
       "events": ["gift.paid", "gift.delivered"]}'

The response carries the signing secret once. Omit events to receive all of them, which also means new event types reach you without a change on your side. The url must be https — the payload carries recipient names and addresses.

Events

EventFires when
gift.paidCheckout completed. The order is committed and the bakery is briefed
gift.deliveredIt landed. Carries the cake photo and the handover photo
gift.delivery_failedIt could not be handed over. Carries failure_reason
balance.lowThe prepaid balance fell below your threshold. Once per dry spell
thing.*, order.*, checkout.*Gift-campaign fulfilment, for partners using the CRM endpoints

Your metadata is echoed in every payload, so you can route an event without a lookup.

Verifying

X-Daymaker-Event      gift.delivered
X-Daymaker-Signature  t=1754300000,v1=<64 hex chars>
X-Daymaker-Timestamp  1754300000

v1 is HMAC-SHA256 of `${t}.${rawBody}` under your secret. Compare in constant time and reject a timestamp more than five minutes old. It is the same scheme Stripe uses, so an existing helper will work.

// Node, on the raw body — not the parsed one
const [t, v1] = header.split(",").map(p => p.split("=")[1]);
const expected = crypto.createHmac("sha256", secret)
  .update(`${t}.${rawBody}`).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
  && Math.abs(Date.now() / 1000 - Number(t)) < 300;

Delivery

At least once. A retry repeats the same event_key — dedupe on it, or one cake landing becomes two rows in your database. Failures back off over 8 attempts; an endpoint that fails 10 attempts consecutively is switched off and says so, with disabled_reason, in GET /webhooks. Return 2xx promptly: we time out at 10 seconds.

Errors

{
  "error": {
    "code": "validation_error",
    "message": "Some fields are missing or invalid.",
    "request_id": "req_…",
    "fields": [{ "field": "recipient.contact_name", "problem": "Required" }]
  }
}
StatusMeaning
400Validation. fields says which
401Missing, malformed, unknown or revoked key
403A sub-account tried to act for another user
404No such gift on this account — check end_user
409Gift already paid, recipient cap reached, or an idempotent call in flight
422Idempotency key reused with a different body
429The key's 24-hour spend cap would be exceeded
5xxOurs. The write did not complete — retry with the same key

request_id is in every response and every response header. Quote it and we can find the exact call.

Guide: a cake on every birthday

The recurring-occasion integration end to end. You hold the roster; we never see a birthday, only an order for a date.

Ask about coverage before you ask the employer. There is no point offering a cake to someone whose employee lives where we cannot reach, and finding out after they said yes is the one outcome that makes the feature feel broken.

# 1 — two weeks out, is that address deliverable on that day?
curl -X POST https://daymaker.com/api/v1/partner/coverage \
  -H "Authorization: Bearer $DAYMAKER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"address": "1100 Sanchez St", "city": "San Francisco",
       "state": "CA", "zip": "94114", "delivery_date": "2026-09-10"}'

covered: true comes back with prices_by_size — the real local price, which is what you show the employer. Anything else means don't ask: no_coverage (no bakery reaches them), lead_time_unavailable or capacity_full (a bakery covers them but not that day — earliest_delivery_date says when). It is free, and no order exists yet.

2 — Ask the employer. Your UI, your product. Nothing is created here.

# 3 — they said yes
curl -X POST https://daymaker.com/api/v1/partner/cakes \
  -H "Authorization: Bearer $DAYMAKER_KEY" \
  -H "Idempotency-Key: birthday-emp_8891-2026-09-10" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": {
      "company": "Roam", "contact_name": "Sarah Chen",
      "address": "1100 Sanchez St", "city": "San Francisco",
      "state": "CA", "zip": "94114", "phone": "+14155550123",
      "delivery_date": "2026-09-10"
    },
    "design": { "inscription": "Happy Birthday Sarah" },
    "card":   { "message_text": "Happy birthday from all of us.",
                "message_signature": "— the Roam team" },
    "cake_size": "8in",
    "metadata": { "employee_id": "emp_8891", "occasion": "birthday" }
  }'

No flavour to pick — the bakery bakes what it is good at — and no print to design. Make the idempotency key deterministic, as above: a retry, a redeploy mid-run, or a scheduler that fires twice then all collect the same cake instead of sending two.

4 — Photos and status. Every bakery photographs the cake and the handover, and gift.delivered brings you both with your metadata attached.

Two things differ when you deliver to homes rather than offices, and both bite. A phone number matters much more, because nobody mans a front desk at a house. And coverage is a real filter rather than a formality — employees live where they live, so run step 1 across the whole upcoming window and you will know months ahead who you can offer this to.

All endpoints

Rendered from the OpenAPI document this deployment is serving, so it is the contract itself rather than a copy of it. Every endpoint takes the Authorization header; every write takes Idempotency-Key.

Loading the endpoint reference…

Questions, a raised spend cap, a prepaid balance, or an integration you want us to look at: contact@daymaker.com. Prefer to let an assistant do it conversationally? That is the MCP server.