Server-to-server
Your secret key never reaches a browser. Your backend mints sessions, reads designs and renders print files.
npm install @foldkit/node
Zero dependencies (node:crypto and the global fetch), Node 18+. Everything below is also plain HTTP against https://foldkit.theboxes.asia/api/v1 if you are not in Node — see the API reference.
1. Mint a session
This is what turns an anonymous embed into one that knows whose design it is.
import { Foldkit } from '@foldkit/node'
const foldkit = new Foldkit({ secretKey: process.env.FOLDKIT_SECRET_KEY })
app.post('/api/foldkit-session', async (req, res) => {
const { token, expiresAt } = await foldkit.embedSessions.create({
externalUserId: req.user.id, // your id for them, opaque to us
designId: req.body.designId, // optional: resume an existing design
permissions: ['designs:read', 'designs:write', 'exports:write'],
ttlSeconds: 600,
})
res.json({ token, expiresAt })
})- Mint per mount.A token is consumed by the browser’s exchange; caching one produces
token_already_usedon the second page load. - Authenticate the request first. This endpoint is on your server, and whoever can call it can open an editor as whoever it names. Treat it exactly like any other endpoint that acts on behalf of a logged-in user.
- Pass
designIdto pin the session. A pinned token cannot reach any other design, whatever its permissions say — which is what you want for “edit the design on this order line”.
Then hand the token to the page. The page passes it to Foldkit.mount() — as a function, so renewal happens by itself. See the SDK guide.
2. Read the design back
You learn a design id twice: from the saved event in the page, and from the design.saved webhook. Use the second for anything that must not be missed.
const design = await foldkit.designs.get(designId) design.name design.styleId design.doc // the document — treat as opaque, read it through us design.updatedAt
Store the id against your order line, not a copy of the document. Documents carry a schemaVersion and are migrated forward as the format evolves; a copy you took last year is a copy you have to migrate yourself.
Thumbnails
GET https://foldkit.theboxes.asia/api/v1/designs/:id/preview.png?width=400
A flat dieline render with the artwork, board colour and cut/crease lines. It carries an ETag, so a conditional request answers 304 and costs you nothing — worth wiring up if you render it on every cart view.
3. Render a print file
Exports are asynchronous: you ask, we render, you fetch. Four kinds — svg, dxf, dieline-pdf and print-pdf.
const job = await foldkit.exports.create({
designId,
kind: 'print-pdf',
options: { bleedMm: 3, cropMarks: true },
})
// Then either poll…
const finished = await foldkit.exports.get(job.id)
finished.status // 'queued' | 'processing' | 'completed' | 'failed'
finished.downloadUrl // a signed URL, valid for an hour, once completed…or, better, subscribe to export.completed and let us tell you. Polling a job that takes two minutes is a lot of requests to learn nothing.
Errors
try {
await foldkit.embedSessions.create({ externalUserId: user.id })
} catch (error) {
error.code // 'invalid_key' | 'insufficient_scope' | …
error.status // the HTTP status, when there was a response
error.requestId // quote this to support
error.retryAfterSeconds // on 'rate_limited'
}Every code is on the errors page. Two worth knowing before you meet them: 403 insufficient_scope means the key is fine and lacks a grant, and 404means “not yours” and “does not exist” identically — so never word it to your users as “deleted”.
Key hygiene
sk_in your server’s environment, and nowhere else — not a browser bundle, not a commit, not a chat message.- We show a secret key once, at creation. We store only its hash, so there is no page, no support ticket and no query that recovers it. Lost means rotate.
- Rotation has an overlap window you choose: both keys work until it closes, so you can deploy before you revoke.
- Grant the narrowest scope set that works. A key that only ever launches the editor needs
embed:createand nothing else.