Tiny CV

Documentation

Quickstart

Get a resume live in three moves.

Tiny CV is small on purpose: create a key, send a draft, publish it. Validation, schemas, claim links, and PDFs are there when you need them, not before.

1

Create a project and API key

No account required yet. Complete the challenge below to create your first project and reveal your live API key. Store it immediately, as it won't be shown again.

2

Create a draft resume

Send markdown or JSON to create a private draft resume. Tiny CV will return a resume ID that you'll use for all future moves.

import crypto from "node:crypto";

const response = await fetch("http://localhost:3000/api/v1/resumes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "input_format": "markdown",
  "markdown": "# Alex Morgan\nFounder & Product Engineer\nSan Francisco, CA | [alex@example.com](mailto:alex@example.com)\n\n## Summary\nProduct-minded builder.\n",
  "return_edit_claim_url": true,
  "template_key": "founder",
  "title": "Alex Morgan Resume",
  "webhook_url": "https://example.com/tinycv/webhooks"
}),
});

const data = await response.json();
console.log(data);
3

Publish to the web

Publish your draft when you're ready for a public URL. You can also queue a PDF job to get a professional printable file.

Overview

The mental model is simple.

Tiny CV keeps one canonical markdown document, publishes a hosted resume page, and can hand you back a PDF on demand.

Markdown as truth

Markdown is the source of truth. JSON is just a nicer way to generate it.

Private by default

Drafts stay private. Public URLs appear only when you explicitly publish.

Async PDFs

PDFs are async. Request one when you actually need the artifact.

Optional claims

Claim links are optional. Use them only when a human should take over editing.

Playground

Try it live.

Pick an endpoint, add auth when needed, and inspect the real response without leaving the page.

POST /api/v1/resumes

Response

Run a request to inspect the response here.

API reference

Endpoints

Start with create and publish. The rest is there when your integration needs more than the happy path.

Getting started

Start in the docs for self-serve access, or use the protected bootstrap flow for managed provisioning.

POST

/api/v1/projects/bootstrap

Bootstrap a project (protected)

Protected bootstrap flow for managed or internal provisioning. The public documentation experience is the easiest way to create your first project and API key.

Auth: Bootstrap secret
const response = await fetch("http://localhost:3000/api/v1/projects/bootstrap", {
  method: "POST",
  headers: {
    "x-tinycv-bootstrap-secret": process.env.TINYCV_PLATFORM_BOOTSTRAP_SECRET!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "api_key_label": "Production Agent Key",
  "name": "Acme Recruiting Agent",
  "slug": "acme-agent"
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "apiKey": {
    "key": "tcv_live_xxxxxxxxxxxxxxxxxxxxxxxx",
    "keyPrefix": "tcv_live_xxxxxxxxx",
    "label": "Production Agent Key"
  },
  "project": {
    "createdAt": "2026-04-15T10:12:00.000Z",
    "id": "proj_123",
    "name": "Acme Recruiting Agent",
    "slug": "acme-agent",
    "updatedAt": "2026-04-15T10:12:00.000Z"
  },
  "webhookSecret": "tcv_wsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Reference

Templates, schema discovery, and machine-readable docs.

GET

/api/v1/templates

List templates

List the built-in Tiny CV starter templates.

Auth: Public
const response = await fetch("http://localhost:3000/api/v1/templates", {
  method: "GET",
  headers: {
    
  },
  
});

const data = await response.json();
console.log(data);

Example response

{
  "templates": [
    {
      "badge": "Technical",
      "description": "Builder-first template.",
      "key": "engineer",
      "label": "Engineer"
    }
  ]
}
GET

/api/v1/templates/{key}

Fetch a single template

Fetch one template and its starter markdown.

Auth: Public
const response = await fetch("http://localhost:3000/api/v1/templates/designer", {
  method: "GET",
  headers: {
    
  },
  
});

const data = await response.json();
console.log(data);

Example response

{
  "badge": "Creative",
  "description": "A stronger portfolio-forward structure for designers.",
  "key": "designer",
  "label": "Designer",
  "markdown": "---\nstylePreset: creative\n...\n# Maya Chen"
}
GET

/api/v1/spec/markdown

Get markdown guide

Fetch the canonical markdown guide for Tiny CV resumes.

Auth: Public
const response = await fetch("http://localhost:3000/api/v1/spec/markdown", {
  method: "GET",
  headers: {
    
  },
  
});

const data = await response.json();
console.log(data);

Example response

{
  "format": "markdown",
  "guide": "# Tiny CV Markdown Guide\n..."
}
GET

/api/v1/spec/json-schema

Get JSON schema

Fetch JSON Schema for structured resume input.

Auth: Public
const response = await fetch("http://localhost:3000/api/v1/spec/json-schema", {
  method: "GET",
  headers: {
    
  },
  
});

const data = await response.json();
console.log(data);

Example response

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "additionalProperties": false,
  "properties": {
    "contact": {
      "items": {
        "additionalProperties": false,
        "properties": {
          "href": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "email",
              "phone",
              "location",
              "url",
              "linkedin",
              "github",
              "x",
              "text"
            ],
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "value": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "value"
        ],
        "type": "object"
      },
      "type": "array"
    },
    "headline": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "sections": {
      "items": {
        "oneOf": [
          {
            "additionalProperties": false,
            "properties": {
              "paragraphs": {
                "items": {
                  "type": "string"
                },
                "minItems": 1,
                "type": "array"
              },
              "title": {
                "type": "string"
              },
              "type": {
                "const": "summary",
                "type": "string"
              }
            },
            "required": [
              "type",
              "paragraphs"
            ],
            "type": "object"
          },
          {
            "additionalProperties": false,
            "properties": {
              "entries": {
                "items": {
                  "additionalProperties": false,
                  "properties": {
                    "bullets": {
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "meta_left": {
                      "type": "string"
                    },
                    "meta_right": {
                      "type": "string"
                    },
                    "paragraphs": {
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "title": {
                      "type": "string"
                    },
                    "title_extras": {
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "title"
                  ],
                  "type": "object"
                },
                "minItems": 1,
                "type": "array"
              },
              "title": {
                "type": "string"
              },
              "type": {
                "const": "entries",
                "type": "string"
              }
            },
            "required": [
              "type",
              "title",
              "entries"
            ],
            "type": "object"
          },
          {
            "additionalProperties": false,
            "properties": {
              "bullets": {
                "items": {
                  "type": "string"
                },
                "minItems": 1,
                "type": "array"
              },
              "title": {
                "type": "string"
              },
              "type": {
                "const": "bullets",
                "type": "string"
              }
            },
            "required": [
              "type",
              "title",
              "bullets"
            ],
            "type": "object"
          },
          {
            "additionalProperties": false,
            "properties": {
              "groups": {
                "items": {
                  "additionalProperties": false,
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "label",
                    "value"
                  ],
                  "type": "object"
                },
                "minItems": 1,
                "type": "array"
              },
              "title": {
                "type": "string"
              },
              "type": {
                "const": "skills",
                "type": "string"
              }
            },
            "required": [
              "type",
              "groups"
            ],
            "type": "object"
          }
        ]
      },
      "minItems": 1,
      "type": "array"
    }
  },
  "required": [
    "name",
    "sections"
  ],
  "type": "object"
}

Draft and publish

Validate input, create drafts, update them, and publish public URLs.

POST

/api/v1/resumes/validate

Validate input

Validate markdown or JSON input without persisting anything. Best used before draft creation.

Auth: Bearer token
const response = await fetch("http://localhost:3000/api/v1/resumes/validate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "input_format": "json",
  "resume": {
    "contact": [
      {
        "kind": "email",
        "value": "maya@example.com"
      }
    ],
    "headline": "Product Designer",
    "name": "Maya Chen",
    "sections": [
      {
        "paragraphs": [
          "Designer with strong systems and product instincts."
        ],
        "type": "summary"
      }
    ]
  },
  "style": {
    "accentTone": "plum",
    "stylePreset": "creative"
  },
  "template_key": "designer"
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "errors": [],
  "inferred_template_key": "designer",
  "normalized_markdown": "---\nstylePreset: creative\n...\n# Maya Chen",
  "valid": true,
  "warnings": []
}
POST

/api/v1/resumes

Create a draft

Create a Tiny CV draft from markdown or JSON. Returns the canonical markdown that Tiny CV stored. Send Idempotency-Key so retries do not create duplicates.

Auth: Bearer token· Idempotent
import crypto from "node:crypto";

const response = await fetch("http://localhost:3000/api/v1/resumes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "input_format": "markdown",
  "markdown": "# Alex Morgan\nFounder & Product Engineer\nSan Francisco, CA | [alex@example.com](mailto:alex@example.com)\n\n## Summary\nProduct-minded builder.\n",
  "return_edit_claim_url": true,
  "template_key": "founder",
  "title": "Alex Morgan Resume",
  "webhook_url": "https://example.com/tinycv/webhooks"
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "created_at": "2026-04-15T10:14:00.000Z",
  "editor_claim_url": "https://tinycv.app/claim/claim_123?token=tcv_claim_xxx",
  "external_resume_id": null,
  "input_format": "markdown",
  "markdown": "# Alex Morgan\nFounder & Product Engineer\n...",
  "pdf_url": null,
  "public_url": null,
  "published_at": null,
  "resume_id": "res_123",
  "status": "draft",
  "template_key": "founder",
  "title": "Alex Morgan Resume",
  "updated_at": "2026-04-15T10:14:00.000Z"
}
GET

/api/v1/resumes/{resume_id}

Get a resume

Read the current state of one draft or published resume.

Auth: Bearer token
const response = await fetch("http://localhost:3000/api/v1/resumes/res_123", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
  },
  
});

const data = await response.json();
console.log(data);

Example response

{
  "created_at": "2026-04-15T10:14:00.000Z",
  "input_format": "markdown",
  "markdown": "# Alex Morgan\nFounder & Product Engineer\n...",
  "pdf_url": null,
  "public_url": null,
  "published_at": null,
  "resume_id": "res_123",
  "status": "draft",
  "template_key": "founder",
  "title": "Alex Morgan Resume",
  "updated_at": "2026-04-15T10:14:00.000Z"
}
PATCH

/api/v1/resumes/{resume_id}

Update a draft

Update an existing draft using the same input contract as draft creation.

Auth: Bearer token· Idempotent
import crypto from "node:crypto";

const response = await fetch("http://localhost:3000/api/v1/resumes/res_123", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "input_format": "json",
  "resume": {
    "headline": "Founder & Product Engineer",
    "name": "Alex Morgan",
    "sections": [
      {
        "paragraphs": [
          "Founder who ships products and owns the narrative."
        ],
        "type": "summary"
      }
    ]
  },
  "style": {
    "density": "compact"
  }
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "created_at": "2026-04-15T10:14:00.000Z",
  "input_format": "json",
  "markdown": "# Alex Morgan\nFounder & Product Engineer\n...",
  "pdf_url": null,
  "public_url": null,
  "published_at": null,
  "resume_id": "res_123",
  "status": "draft",
  "template_key": "founder",
  "title": "Alex Morgan Resume",
  "updated_at": "2026-04-15T10:18:00.000Z"
}
POST

/api/v1/resumes/{resume_id}/publish

Publish a resume

Publish the current draft snapshot and get the public URL. Send Idempotency-Key so retries return the same publish result.

Auth: Bearer token· Idempotent
import crypto from "node:crypto";

const response = await fetch("http://localhost:3000/api/v1/resumes/res_123/publish", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "return_edit_claim_url": true,
  "webhook_url": "https://example.com/tinycv/webhooks"
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "created_at": "2026-04-15T10:14:00.000Z",
  "editor_claim_url": "https://tinycv.app/claim/claim_124?token=tcv_claim_xxx",
  "input_format": "markdown",
  "markdown": "# Alex Morgan\nFounder & Product Engineer\n...",
  "pdf_url": null,
  "public_url": "https://tinycv.app/SteadyBlueHeron",
  "published_at": "2026-04-15T10:20:00.000Z",
  "resume_id": "res_123",
  "status": "published",
  "template_key": "founder",
  "title": "Alex Morgan Resume",
  "updated_at": "2026-04-15T10:20:00.000Z"
}

Artifacts and handoff

Artifacts, claim links, and export-related flows.

POST

/api/v1/resumes/{resume_id}/pdf-jobs

Queue a PDF job

Queue durable PDF generation for a published resume. Returns a pollable job immediately; webhooks are delivered from the worker outbox.

Auth: Bearer token· Idempotent
import crypto from "node:crypto";

const response = await fetch("http://localhost:3000/api/v1/resumes/res_123/pdf-jobs", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "webhook_url": "https://example.com/tinycv/webhooks"
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "completed_at": null,
  "error_code": null,
  "error_message": null,
  "job_id": "job_123",
  "pdf_url": null,
  "requested_at": "2026-04-15T10:21:00.000Z",
  "resume_id": "res_123",
  "status": "queued"
}
GET

/api/v1/pdf-jobs/{job_id}

Get PDF job status

Check PDF generation status and get a signed PDF URL once the artifact is ready.

Auth: Bearer token
const response = await fetch("http://localhost:3000/api/v1/pdf-jobs/job_123", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
  },
  
});

const data = await response.json();
console.log(data);

Example response

{
  "completed_at": "2026-04-15T10:21:08.000Z",
  "error_code": null,
  "error_message": null,
  "job_id": "job_123",
  "pdf_url": "https://tinycv.app/api/v1/pdf-jobs/job_123/file?expires=...",
  "requested_at": "2026-04-15T10:21:00.000Z",
  "resume_id": "res_123",
  "status": "completed"
}
POST

/api/v1/edit-claims/{claim_id}/consume

Consume an edit claim

Consume a one-time edit claim and attach the resume into the current Tiny CV workspace.

Auth: Public
const response = await fetch("http://localhost:3000/api/v1/edit-claims/claim_123/consume", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "token": "tcv_claim_xxxxxxxxxxxxxxxxx"
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "cleanEditorUrl": "/studio/res_123"
}

Agent and MCP

Remote tool access for agents that want Tiny CV as infrastructure.

POST

/api/v1/mcp

Call the MCP server

Remote MCP endpoint for agent tools, resources, and prompts over JSON-RPC. Mutating tools use JSON-RPC ids as idempotency keys when no Idempotency-Key header is present.

Auth: Bearer token
const response = await fetch("http://localhost:3000/api/v1/mcp", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TINYCV_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": {}
}),
});

const data = await response.json();
console.log(data);

Example response

{
  "id": 1,
  "jsonrpc": "2.0",
  "result": {
    "tools": [
      {
        "name": "tinycv_create_resume_draft"
      },
      {
        "name": "tinycv_publish_resume"
      }
    ]
  }
}

Resources

Reference files

Useful for agents, SDKs, and anyone who prefers a spec over vibes.

Markdown guide preview

# Tiny CV Markdown Guide

Tiny CV resumes are plain markdown with optional YAML frontmatter.

## File shape

```md
---
stylePreset: editorial
accentTone: forest
density: standard
headerAlignment: left
contactStyle: compact
pageMargin: 1
showHeaderDivider: false
showSectionDivider: true
pageSize: letter
---

# Alex Morgan
Founder & Product Engineer
San Francisco, CA | [alex@example.com](mailto:alex@example.com) | [linkedin.com/in/alexmorgan](https://linkedin.com/in/alexmorgan)

## Summary
Product-minded builder with experience across product, engineering, and go-to-market.

## Experience
### Founder | Meridian Labs
*Remote | 2023 - Present*
- Built the first product surface and shipped the company to revenue.

## Skills
Languages: TypeScript, Python, SQL
Frameworks: React, Next.js, Node.js
```

## Rules

- The first `#` heading is the candidate name.
- The next non-empty line is treated as the headline unless it looks like contact info.
- Contact info can be plain text or markdown links and is usually separated with `|`.
- Top-level sections use `##`.
- Resume entries inside sections use `###`.
- Entry metadata goes on the next italic line, usually in the form `*Location | Dates*`.

...

Agent cookbook preview

# Tiny CV Agent Cookbook

## Happy path

1. Choose a template.
2. Fetch the markdown guide or JSON schema.
3. Validate the payload.
4. Create a draft.
5. Publish the draft.
6. Request a PDF if needed.

## Recommended workflow

- Use markdown if your agent already produces polished text.
- Use JSON if your agent starts from structured profile data.
- Send an `Idempotency-Key` on create, update, publish, and PDF job requests.
- Keep the public URL as the default output for end users.
- Only request an edit claim URL when the user should continue editing in Tiny CV.
- If a request returns `429`, wait for the `Retry-After` header before retrying.

## Example: JSON to published resume

1. `GET /api/v1/spec/json-schema`
2. `POST /api/v1/resumes/validate`
3. `POST /api/v1/resumes`
4. `POST /api/v1/resumes/:resume_id/publish`
5. `POST /api/v1/resumes/:resume_id/pdf-jobs`
6. `GET /api/v1/pdf-jobs/:job_id`