Skip to main content
Browse documentation

Type to search the documentation. Press Esc to close.

Scribble cards API: send and look up a handwritten card

For developers

POST /api/v1/cards sends one handwritten card and spends one credit. GET /api/v1/cards lists what you have sent, and GET /api/v1/cards/{card_code} returns one card with its per-step production progress and its arrival window.

On this page

#POST/api/v1/cards

Send one handwritten card. Spends one credit.

Requires scope cards:send

Body parameters

FieldTypeDescription
recipientrequiredobjectThe postal address. See the sub-fields below.
recipient.namerequiredstringWho the envelope is addressed to.
recipient.line1requiredstringStreet line.
recipient.line2stringApartment, suite, floor or unit.
recipient.cityrequiredstringCity.
recipient.staterequiredstringTwo-letter US state code. Upper-cased for you.
recipient.ziprequiredstringFive digits, optionally with a +4.
recipient.countrystringDefaults to US. Scribble mails inside the US only.
messagerequiredstringWhat gets written on the card, in ink. Send the finished text with merge fields already substituted — a message still containing something field-shaped, like {{first_name}} or [[company]], fails with 422 broken_merge_field and nothing is sent and nothing is charged. It used to be accepted and written on the card exactly as it appeared, behind a 201 that said it had worked. Maximum 500 characters; line breaks are honored. Aim for 250–400 — 500 is the ceiling, not a target, and a long name can push a message that fits in testing over the edge in production.
handwritingstringWhich hand to write in, by name — theodore, ruth, miriam. Case does not matter. Call GET /v1/handwriting for the full list. An unrecognized name is refused, not substituted: you get 422 unknown_handwriting and nothing is sent. A typo should cost you an error, not a thousand cards in the wrong hand. Leaving the field out is not an error — that means your house hand.
design_idstringWhich card front this goes on. Two kinds of id work. One of your saved designs, from GET /api/v1/designs — its artwork and inside layout are both used. Or a card from Scribble's public collection, written card: and the slug from the card's page address: card:brushstroke-thanks for scribblecards.com/cards/business/brushstroke-thanks. A range card is copied into your own designs the first time you send it, so it shows up in GET /api/v1/designs from then on and you can use its uuid after that. Any active card in the public collection is available this way. An unavailable card or an unknown id returns 404 design_not_found; saved design ids are still restricted to your organization. Leaving the field out uses your most recent saved design; an organization with none at all gets 422 no_design_available, because every card is a greeting card with a printed front. Fronts of your own are uploaded in the web app, not over the API; see uploading your own artwork for the size.
arrive_onstringYYYY-MM-DD. The date the card should arrive by. Production is planned backwards from it and the quoted window ends on it. Leave it out for the soonest date we can honor.
referencestringFree text, echoed back in the response so you can correlate without storing our ids. It does not deduplicate. Truncated to 40 characters.
Send a card
curl -X POST https://scribblecards.com/api/v1/cards \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "recipient": {
      "name": "Priya Raman",
      "line1": "1 Fifth Avenue",
      "line2": "Apt 12B",
      "city": "New York",
      "state": "NY",
      "zip": "10003"
    },
    "message": "Congratulations on the new role, Priya.\n\nEveryone at Northwind",
    "reference": "crm-deal-8812"
  }'
201 Created
{
  "id": "SC-NH4DERB",
  "object": "card",
  "status": "scheduled",
  "held_reason": null,
  "recipient": { "name": "Priya Raman" },
  "arrives_between": {
    "earliest": "2026-08-10",
    "latest": "2026-08-14"
  },
  "reference": "crm-deal-8812",
  "note": "One credit was charged. Arrival is a window, not a promised day.",
  "url": "https://scribblecards.com/app/orders/SC-NH4DERB"
}
  • status is scheduled when the card is going to be made, or held when something needs a person. A held card is not charged, and held_reason says why in plain words.
  • arrives_between is a window, never a promised date. It spans four to five days and always ends on the arrive-by date — arrive_on if you sent one, otherwise the soonest date we can honor. We aim to be early rather than late, because once a card is with USPS the last mile is not ours to control.
  • id is the card code. It looks like SC-NH4DERB: the prefix SC-, six payload characters and a check character, in an alphabet with no I, L, O or U so nothing can be misread aloud.

#GET/api/v1/cards

List the cards this organization has sent, newest first.

Requires scope cards:read

Query parameters

ParameterTypeDescription
limitintegerDefault 50, maximum 200. Anything larger is clamped.
statusstringFilter by production status. See the status table below.
referencestringFind cards by the reference you sent on the POST — your own invoice number, CRM record id or ticket id.
starting_afterstringThe card code of the last item on the previous page. Use next_cursor from the response rather than building this yourself.
List the two most recent cards
curl "https://scribblecards.com/api/v1/cards?limit=2" \
  -H "Authorization: Bearer sk_live_..."
200 OK
{
  "object": "list",
  "has_more": true,
  "data": [
    {
      "id": "SC-2APXR1Z",
      "object": "card",
      "status": "in_production",
      "recipient": { "name": "Tomas Nowak", "city": "Boston", "state": "MA" },
      "arrives_between": { "earliest": "2026-08-10", "latest": "2026-08-14" },
      "mailed_at": null,
      "created_at": "2026-08-05T06:48:57.080629+00:00"
    },
    {
      "id": "SC-5DKF1SW",
      "object": "card",
      "status": "written",
      "recipient": { "name": "Aisha Bello", "city": "Austin", "state": "TX" },
      "arrives_between": { "earliest": "2026-08-10", "latest": "2026-08-14" },
      "mailed_at": null,
      "created_at": "2026-08-05T06:19:04.555985+00:00"
    }
  ]
}
  • has_more is true when the page came back full, and next_cursor carries the id to pass as starting_after for the next page. It is null on the last page, so a client can loop on that alone.
  • Pagination is by cursor rather than by page number on purpose. These lists are newest-first and rows are added at the newest end, so offset paging would show you the same card twice and skip another — which for a nightly sync means a missing card nobody notices.
  • reference is returned on every row, and ?reference= filters by it, so you can find a card by your own identifier without storing ours.

#GET/api/v1/cards/{card_code}

One card: status, per-step production progress, arrival window.

Requires scope cards:read

Look up one card
curl https://scribblecards.com/api/v1/cards/SC-NH4DERB \
  -H "Authorization: Bearer sk_live_..."
200 OK
{
  "id": "SC-NH4DERB",
  "object": "card",
  "status": "pending",
  "held_reason": null,
  "recipient": {
    "name": "Priya Raman",
    "line1": "1 Fifth Avenue",
    "line2": "Apt 12B",
    "city": "New York",
    "state": "NY",
    "zip": "10003",
    "country": "US"
  },
  "handwriting": "miriam",
  "arrives_between": {
    "earliest": "2026-08-10",
    "latest": "2026-08-14"
  },
  "steps": [
    { "step": "card_stock_prepared", "status": "pending", "completed_at": null },
    { "step": "card_written",        "status": "pending", "completed_at": null },
    { "step": "envelope_addressed",  "status": "pending", "completed_at": null },
    { "step": "packed",              "status": "pending", "completed_at": null },
    { "step": "stamped",             "status": "pending", "completed_at": null },
    { "step": "posted",              "status": "pending", "completed_at": null }
  ],
  "mailed_at": null,
  "created_at": "2026-08-05T09:37:33.549357+00:00",
  "url": "https://scribblecards.com/app/orders/SC-NH4DERB"
}
  • Card codes are case-insensitive on lookup and are normalized, so sc-nh4derb resolves to the same card.
  • A card belonging to another organization returns 404, not 403. Existence is not leaked across tenants.
  • steps always contains all six entries in production order. A step that has not started is pending; a retried step reports its current state rather than the failure it recovered from.

#DELETE/api/v1/cards/{card_code}

Stop a card before it is made, and get the credit back.

Requires scope cards:send

Cancel a card
curl -X DELETE https://scribblecards.com/api/v1/cards/SC-NH4DERB \
  -H "Authorization: Bearer sk_live_..."
200 OK
{
  "id": "SC-NH4DERB",
  "object": "card",
  "status": "cancelled",
  "cancelled": true,
  "credits_refunded": 1,
  "note": "Canceled before anything was made. The credit is back on your balance."
}
  • Works while the card is pending, queued or held. Those are the states where nothing physical exists yet.
  • Canceling an already-canceled card is a 200, not an error. Retrying a cancel is safe and refunds nothing twice.
  • Once the card has reached a machine you get 409 too_late_to_cancel, and the message says where it actually is so you know whether to call us.

#Card statuses

Statuses returned by the GET endpoints
StatusMeaning
pendingCreated and scheduled. Nothing physical has happened yet.
queuedReleased to the floor for a production run.
in_productionSomewhere in the six steps.
writtenThe pen has finished the card and the envelope.
packedIn its envelope, stamped, in a tray.
postedHanded to USPS. mailed_at is set.
blockedHeld. Needs a person. held_reason says why. Not charged.
cancelledStopped before production.
failedCould not be produced.

#Common questions

How do I know when a card has been mailed?
Subscribe to the card.mailed webhook — it fires when the card is handed to the carrier, and it is the event most systems act on. Polling this endpoint and watching mailed_at also works, but poll only the cards you care about, on a sensible interval.
Can I cancel a card sent through the API?
Yes — DELETE /api/v1/cards/{card_code}, and the credit goes back. It works while the card is still pending, queued or held. Once a pen is on the paper you get a 409 explaining where the card actually is: there is no operation that un-writes a card, and we would rather say so than return a cheerful 200 you would act on.
Can I send the same card to several recipients in one request?
No. One request, one card. For a batch, either loop within the rate limit or build a campaign.
Is there a test mode?
No sandbox and no test keys. Develop against an account with a deliberately small balance.

Last checked against the product on . Something wrong or missing? Tell us.