Skip to main content
Machines cares about privacy—and this extends to API partners. We use end‑to‑end encryption for card UI metadata so only your users can read it. Use our helper endpoints so you don’t have to implement cryptography yourself. What’s encrypted for cards:
  • Card label (name) → encryptedName
  • Color → encryptedColor
  • Emoji → encryptedEmoji
  • Memo/notes → encryptedMemo
Card secrets (PAN/CVC) are different. Use POST /cards/secrets/session + POST /cards//secrets. See Cards → Secrets.
Prerequisite: have a session token. See Authentication.

API flows (step‑by‑step)

1

Encrypt a card label (server-side)

Endpoint: POST /encryption/encrypt
Body: JSON with a value string (e.g., “Ops Card”).
Response: returns an object with value (EncryptedField)
curl -s \
  -d '{"value":"Ops Card"}' \
  https://dev-api.machines.cash/partner/v1/encryption/encrypt
2

Decrypt a card label (server-side)

Endpoint: POST /encryption/decrypt
Body: JSON with a value EncryptedField. Response: plaintext card label in value
curl -s \
  -d '{"value":{"v":1,"iv":"Qz4f2m7Hk1P9x0ab","ct":"Hdbb2yT2F4xWZL5m5bJX3eJb4n8wVhjPzqLw2h7i"}}' \
  https://dev-api.machines.cash/partner/v1/encryption/decrypt

Fields

value
string
required
Plaintext to encrypt. For decryption, pass the encrypted object returned by /encryption/encrypt.

Response (encrypt)

value.v
number
required
Schema version.
value.iv
string
required
Base64url IV.
value.ct
string
required
Base64url ciphertext + auth tag.

Card labels are optional

  • You can omit encryptedName when creating a card and set it later with PATCH /cards/.
  • When you do set a card label, always use /encryption/encrypt and /encryption/decrypt (no manual cryptography needed).

Default flow (server-side)

Example (server-side)

const labelRes = await fetch("https://dev-api.machines.cash/partner/v1/encryption/encrypt", {
  method: "POST",
  headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json" },
  body: JSON.stringify({ value: "Ops Card" }),
});
const { data: encrypted } = await labelRes.json();

await fetch("https://dev-api.machines.cash/partner/v1/cards", {
  method: "POST",
  headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json" },
  body: JSON.stringify({ encryptedName: encrypted.value }),
});
  1. List cards: GET /cards
  2. Decrypt each encryptedName with POST /encryption/decrypt
  3. Send plaintext labels to your frontend
const cardsRes = await fetch("https://dev-api.machines.cash/partner/v1/cards", {
  headers: { Authorization: `Bearer ${sessionToken}` },
});
const { data: cardsData } = await cardsRes.json();

const labels = await Promise.all(
  cardsData.cards.map(async (card: { encryptedName: unknown; cardId: string }) => {
    const decryptRes = await fetch("https://dev-api.machines.cash/partner/v1/encryption/decrypt", {
      method: "POST",
      headers: { Authorization: `Bearer ${sessionToken}`, "Content-Type": "application/json" },
      body: JSON.stringify({ value: card.encryptedName }),
    });
    const { data } = await decryptRes.json();
    return { cardId: card.cardId, label: data.value };
  }),
);
Use /encryption/decrypt only for card labels and metadata. Card secrets (PAN/CVC) use /cards/secrets/session + /cards//secrets.

Advanced (client-side, optional)

If you need client-side encryption, fetch a per-user data key via GET /encryption/data-key (requires encryption.read) and encrypt locally using AES-256-GCM.

Encrypted field shape

{
  "v": 1,
  "iv": "<base64url>",
  "ct": "<base64url>"
}
  • Canonical order is v, iv, ct (order doesn’t matter in JSON).
  • v: schema version (currently 1)
  • iv: 12-byte IV, base64url encoded
  • ct: ciphertext + auth tag, base64url encoded

Example (Node.js)

import { createCipheriv, randomBytes } from "node:crypto";

function base64Url(buf: Buffer) {
  return buf.toString("base64").replace(/\\+/g, "-").replace(/\\//g, "_").replace(/=+$/g, "");
}

export function encryptField(plaintext: string, dataKeyBase64Url: string) {
  const key = Buffer.from(dataKeyBase64Url, "base64url");
  const iv = randomBytes(12);
  const cipher = createCipheriv("aes-256-gcm", key, iv);
  const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  const combined = Buffer.concat([ciphertext, tag]);
  return { v: 1, iv: base64Url(iv), ct: base64Url(combined) };
}
Card PAN/CVC secrets use a secrets session and AES-128-GCM. See Cards for that flow.