Developers / API
A beta, artist-scoped JSON API and outbound webhooks. This surface may still evolve.
The public API is scoped to the artist who owns each key. It never returns client identities, briefs, prices from other artists, or demo-room data. Manage keys and webhook endpoints from /dashboard/developer.
Authentication
Send your API key as a Bearer token. Keys use the format yk_live_ followed by 40 hex characters. Plaintext is shown once at creation; Yubikiri stores only a SHA-256 hash.
Authorization: Bearer yk_live_0123456789abcdef0123456789abcdef01234567
GET /api/v1/me
Returns your public handle, display name, track-record stats, and slot summary.
curl -s https://yubikiri.art/api/v1/me \ -H "Authorization: Bearer yk_live_..."
{
"data": {
"handle": "your-handle",
"displayName": "Your Name",
"trackStats": {
"completedCount": 3,
"withDeadlineCount": 2,
"onTimeCount": 2,
"onTimePct": 100,
"lateCount": 0
},
"slots": {
"total": 3,
"occupied": 1,
"open": 2,
"effectiveOpen": true,
"estimatedAvailability": null
}
}
}GET /api/v1/commissions
Lists your non-demo commissions with milestone summaries. Query params: status, limit (default 20, max 100), and cursor for id-based pagination.
curl -s "https://yubikiri.art/api/v1/commissions?status=IN_PROGRESS&limit=20" \ -H "Authorization: Bearer yk_live_..."
{
"data": {
"commissions": [
{
"id": "cm123",
"title": "Character portrait",
"status": "IN_PROGRESS",
"createdAt": "2026-07-18T00:00:00.000Z",
"updatedAt": "2026-07-18T01:00:00.000Z",
"deadline": "2026-08-01T23:59:59.000Z",
"milestones": {
"count": 4,
"currentStageName": "Lineart",
"items": [
{
"id": "ms1",
"name": "Sketch",
"order": 0,
"status": "APPROVED",
"paymentStatus": "PAID"
}
]
}
}
],
"pagination": {
"limit": 20,
"nextCursor": "cm123",
"hasMore": false
}
}
}GET /api/v1/commissions/[id]
Returns one commission in the same shape plus recent activity entries (event type and timestamp only — no free-text messages).
GET /api/v1/queue
Returns the same anonymous queue snapshot shown on your public artist page: slot totals, open capacity, and per-slot progress without client details.
Errors
Successful responses use { "data": ... }. Errors use { "error": { "code", "message" } } with an appropriate HTTP status. All responses set Cache-Control: no-store.
Outbound webhooks
Subscribe to events from your dashboard. Each delivery is a POST with JSON:
{
"id": "evt_abc123",
"event": "milestone.approved",
"createdAt": "2026-07-18T01:00:00.000Z",
"data": {
"commission": {
"id": "cm123",
"title": "Character portrait",
"status": "IN_PROGRESS"
},
"milestone": {
"id": "ms1",
"name": "Sketch",
"order": 0,
"status": "APPROVED",
"paymentStatus": "PAID"
}
}
}Supported event names:
commission.createdcommission.status_changedcommission.completedcommission.cancelledmilestone.approvedmilestone.revision_requestedmilestone.payment_status_changed
Requests include a yubikiri-signature header:t=<unix>,v1=<hmac-sha256 hex of "<t>.<body>"> using your endpoint secret.
import crypto from "node:crypto";
function verifyYubikiriWebhook(secret, rawBody, signatureHeader) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((part) => part.trim().split("="))
);
const timestamp = parts.t;
const digest = parts.v1;
if (!timestamp || !digest) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "utf8"),
Buffer.from(digest, "utf8")
);
}Endpoints auto-disable after 20 consecutive delivery failures. Delivery is best-effort — plan for retries on your side if you need stronger guarantees.
Rate expectations
Be gentle. This is a beta API without published rate limits today, but abusive or noisy keys may be revoked. Prefer caching on your side and poll only as often as you need.
Passport (public, no API key)
Every public artist profile also exposes a Passport — portable track-record JSON for Carrd, Discord bots, and other integrations. No authentication. Never includes clients, briefs, prices, or payment data.
curl -s https://yubikiri.art/api/passport/demo
Response shape: { "data": { handle, displayName, profileUrl,
passportUrl, badgeUrl, trackStats, avgRating, avgTurnaroundDays, badges,
slots, disclaimer } }. Cached with Cache-Control: public, s-maxage=300. Share page: /a/[handle]/passport. SVG badge: /api/badge/[handle].
Queue widget (public SVG, no API key)
GET /api/embed/queue/[handle] returns a live, privacy-safe SVG with open capacity and queue status. It never includes client names, briefs, artwork, or prices. Use ?theme=light for light surfaces.
<img src="https://yubikiri.art/api/embed/queue/your-handle" alt="Commission availability" />
Per-file records / content credentials (public, no API key)
GET /api/verify/credential/[id] returns the public process-provenance record for a completed final, its signature status, and its disclaimer. The UI calls this a file id; the API retains “credential” for compatibility. It never returns a client identity or brief. What a record covers is set out in Disclaimers.
curl -s https://yubikiri.art/api/verify/credential/yk_cc_...
New credentials are dual-signed: Ed25519 (publicly verifiable) plus HMAC for platform checks. Fetch the active public keys at /.well-known/yubikiri-keys.json and verify the Ed25519 signature offline against the canonical claim JSON (object keys sorted alphabetically, then JSON.stringify).
curl -s https://yubikiri.art/.well-known/yubikiri-keys.json
Keepsake protocol
Full open specification, archive layout, Merkle rules, and offline tools: /developers/keepsake. Single-file offline verifier: /keepsake-verify.html.
Keepsake Seal embeds
GET /api/embed/seal/[yk_cc_…] returns a live SVG Keepsake Seal for a completed final. Privacy-safe (artist handle + completion month only). Wrap it in a link to /verify?credential=…. Optional ?theme=light.
<a href="https://yubikiri.art/verify?credential=yk_cc_..."> <img src="https://yubikiri.art/api/embed/seal/yk_cc_..." alt="Yubikiri Keepsake Seal" /> </a>
Signed-in users can also download portable Keepsake records JSON from GET /api/kept-passport (session cookie) — sealed rooms as artist and as client, with tag, Seal, and check URLs. The legacy API name remains stable.
Keepsake (sealed process record, no API key)
GET /api/verify/keepsake/[id] returns a sealed Keepsake for a completed commission: signature status, activity-chain tip integrity, final-delivery fingerprints, and (when anchored) a Merkle inclusion proof against the daily transparency log. It never returns a client identity or brief. A Keepsake documents process provenance. Checking a Keepsake is free today — no account required.
curl -s https://yubikiri.art/api/verify/keepsake/yk_ks_...
Transparency log (public Merkle roots, no API key)
Each UTC day, newly sealed Keepsakes are hashed into an append-only Merkle tree. The published root is a commitment Yubikiri cannot silently rewrite. The log contains hashes and roots only — no client names, artwork, or amounts. Directory: /.well-known/yubikiri-log.json. Day records: GET /api/keepsake-log/YYYY-MM-DD and GET /api/keepsake-log/latest.
curl -s https://yubikiri.art/.well-known/yubikiri-log.json curl -s https://yubikiri.art/api/keepsake-log/latest
Optional off-platform mirror: set KEEPSAKE_LOG_GITHUB_TOKEN (contents:write) and KEEPSAKE_LOG_GITHUB_REPO (owner/repo) on Vercel. The daily cron then pushes days/YYYY-MM-DD.json and LATEST.json to that public repository.