# What a MyCV document can contain

A MyCV document is one JSON object. The editor, the in-app agent, connected AI apps (MCP), the PDF, Word and LaTeX exports and the published page all read the same object, so anything described here works everywhere. This page is the reference for people and for agents; the MCP server hands out the same text through its `get_cv_guide` tool.

## Header and contacts

| Field | What it is |
| --- | --- |
| `full_name` | The name at the top of the page. |
| `tagline` | The headline under the name, e.g. "Senior Backend Engineer". |
| `email`, `phone`, `location` | Printed in the contact line. Email opens a mail client, phone dials, on every export. |
| `linkedin`, `website`, `github`, `twitter`, `behance`, `dribbble` | Addresses without `https://` are fine (`linkedin.com/in/ada`). Each one is a working link in the PDF, Word, LaTeX and published page. |
| `custom_contacts` | Up to six extra `{label, value}` links: portfolio, Google Scholar, ORCID, a booking page. |
| Photo | Uploaded by the person with the camera button on the CV. Designs with a photo header or sidebar show it. Agents never set it. |

## Sections

Every section is optional. What is printed, and in what order, is decided by `section_order`; the order of keys in the JSON means nothing.

| Section | Shape | Limits |
| --- | --- | --- |
| `summary` | One paragraph of text, 2 to 5 sentences. Takes inline formatting and links. | 3,000 characters |
| `experience` | Entries of `role`, `company`, `location`, `date`, `bullets`. Most recent first. | 40 entries |
| `education` | Entries of `degree`, `subtitle` (optional: concentration, NYSC role, class of degree), `institution`, `location`, `date`, `bullets`. | 20 entries |
| `projects` | Entries of `title`, `institution` (the organisation or client), `location`, `date`, `bullets`. | 30 entries |
| `skills` | An object of category to comma-separated list: `{"Languages": "Python, Go", "Cloud": "AWS, Terraform"}`. | 12 categories |
| `skill_levels` | Optional 1 to 5 proficiency per category, drawn as dots on designs that have them. | |
| `awards` | Items of `year`, `title`, `issuer`, and an optional `description` (why it was awarded, same formatting as a bullet). | 20 items |
| `custom_sections` | Anything else, see below. | 10 sections |
| `target_job` | The posting this CV is aimed at, as plain text. The ATS score uses it for its job-fit group whenever a score is not given a posting of its own, so it only has to be pasted once (the editor's ATS modal, the free check page and connected apps all save it here). | 20,000 characters |

Dates are free text printed exactly as written: `Jan 2021 - Present`, `2019`, `Summer 2023`. Prefer month and year (`Mar 2021 - Jun 2024`): many applicant tracking systems count months of experience, and a bare year is read as January.

Every entry can carry `bullets`: up to 40 short achievement lines, each up to 1,500 characters. A good bullet starts with a verb and carries a number where one is true.

Every entry can carry a `description`: a short paragraph under the heading and before the bullets, for what the company or team does, the scope of the role, or the goal of the project. It takes the same formatting and links as a bullet. Every entry can also carry `tags`: a short row of the tools, languages or skills used on it (`["Python", "Docker", "AWS"]`), printed small under the entry. In the editor, hover an entry: the three buttons on its right add a link, a description and a skills row.

Every entry, and every award, can also carry a `url`. The title then opens that address: the company's site on a job, the repository or live demo on a project, the certificate page on a certification, the paper on a publication. In the editor, hover an entry and press its link button; the title shows in the design's accent colour, and it is a working link in the PDF, Word, LaTeX and published page. Agents set `"url"` on the entry.

### Custom sections

A custom section covers what the built-ins do not: certifications, languages, publications, volunteering, references, interests. Each has an `id` starting with `cs_` (for example `cs_certifications`), a `title`, and one of four `section_type` bodies.

| `section_type` | Body | Good for |
| --- | --- | --- |
| `entries` | `entries` of `title`, `subtitle`, `location`, `date`, `bullets`, plus for certifications `expiry`, `credential_id` and a `url` to the verification page. In the editor, a certifications section offers "Fill from Credly / Coursera link", which reads the badge page into these fields. | Certifications, publications, volunteering, courses |
| `bullets` | A plain `bullets` list | Achievements, memberships, references on request |
| `keyval` | `keyval` pairs of `label` and `value` | Languages (English: Native), personal details |
| `tags` | A `tags` list of short words | Interests, tools, keywords |

### Order, titles and hidden sections

| Key | What it does |
| --- | --- |
| `section_order` | Ids in page order: `summary`, `experience`, `education`, `projects`, `skills`, `awards`, plus each custom section id. Omit it to use the design's default order. |
| `section_titles` | Rename a built-in heading: `{"experience": "Work History"}`. |
| `hidden_sections` | Ids kept in the document but not printed. |
| `page_breaks` | What starts on a new page: section ids (`"projects"`) and single entries as `section.index` (`"experience.2"`, `"cs_certifications.1"`). In the editor, hover a section heading and press its page-break button, or open an entry's ⋯ menu and choose "Start on a new page". The PDF, Word and LaTeX all break in the same place. The Layout tab's "Fit to 1 page / 2 pages" tightens spacing, font scale and margins until the CV fits, and says what sits on the last page when it cannot. |

## Text formatting and links

The summary and every bullet may carry a small amount of inline markup. Everything else (names, titles, dates, skills, contacts) is plain text.

| You want | Write | Stored as |
| --- | --- | --- |
| Bold, italic, underline | In the editor: select text and use the B / I / U bar, or Ctrl+B, Ctrl+I, Ctrl+U. Agents: `<b>`, `<i>`, `<u>`. | `<b>text</b>` |
| A clickable label | In the editor: select the words, press the link button on the bar or Ctrl+K, paste the address. Agents: `[Verify credential](https://www.credly.com/badges/...)`. | `<a href="https://...">Verify credential</a>` |
| An address as itself | Just type it: `github.com/ada/engine` or `ada@example.com`. | Plain text, linked automatically on export. |
| A line break | In the editor: Shift+Enter (Enter alone finishes the field). Agents: `<br>`. Useful for a two-paragraph summary. | `first part<br>second part` |

Labelled links are the right way to point at a certificate, a portfolio piece, a publication, a live demo or a repository: the page shows the label instead of a long address, and the label opens the address in the PDF, the Word file, the LaTeX build and the published page. The label prints in the design's accent colour so it reads as a link; in the PDF and on the published page a small arrow follows it as well.

Links accept `http`, `https`, `mailto` and `tel` addresses. A bare domain (`credly.com/badges/...`) is given `https://`; anything else is kept as plain text.

In the editor, click into a link to see its address, change it or remove it. An address typed as text is not linked on screen while you edit, so the words stay easy to click into, but it is linked on every export.

## Layout

The `layout` object tunes the page without changing the design.

| Key | Values |
| --- | --- |
| `pageSize` | `a4`, `letter` |
| `margin` | `narrow`, `normal`, `wide` |
| `density` | `compact`, `normal`, `relaxed` |
| `fontScale` | 0.8 to 1.25 |
| `fontFamily` | `auto`, `serif`, `sans`, `modern` |
| `headingCase` | `auto`, `upper`, `normal` |
| `divider` | `auto`, `line`, `thick`, `none` |
| `durations` | `off`, `on`. On, each experience entry prints how long it lasted next to its dates, read from the dates as typed: `Jan 2021 - Present · 5 yrs 9 mos`, `2019 - 2021 · 2 yrs`. Dates that cannot be read (`Summer 2023`) print as typed. Layout tab, "Dates". |
| `skillsFormat` | `grid` (default), `lines`. Grid prints skills as a category / items grid, which the Word export writes as a two-column table. Lines prints each category on its own line with no table, the shape older applicant tracking systems read reliably. The ATS score checks the actual Word file and offers this switch when it finds the table. Layout tab, "Skills". |
| `rail` | Two-column designs only: the section ids printed in the side column, in order, e.g. `["skills", "education", "awards"]` (custom sections by id). Everything else prints in the main column. Leave it out for the design's default: skills, or skills and education on the designs with a full-width header. The summary stays where the design puts it. In the editor, the column button on a section's toolbar moves it between the columns. |

## Designs

There are 30 designs, numbered 1 to 30. Designs 21 to 25 are Pro-exclusive; the rest come with any paid download. Designs 6, 16, 18, 20, 25, 26, 27, 29 and 30 are two-column, with a sidebar; some applicant tracking systems parse two-column pages less reliably, so single-column designs are the safer choice for online applications. The gallery on the landing page shows every design; connected AI apps can show the same gallery with `show_template_gallery`.

## Exports and publishing

| Output | Notes |
| --- | --- |
| PDF | Rendered from the editor's own page, so it is exactly what you see. |
| Word (.docx) | Pro. Rebuilt in the same design with the same fonts embedded; links stay links. |
| LaTeX | Pro. A zip with `main.tex` and the fonts, compiled with XeLaTeX (Overleaf works). |
| JSON backup | Pro. The document itself, for import elsewhere or safekeeping. |
| Published page | Pro. A public page at `/cv/<your-link>`, or on a domain you own. |

## The published page

A published CV is a web page of its own, read by people rather than by applicant tracking systems, so it can be designed separately from the PDF. The page is a *page design*: a style, a theme and an ordered list of blocks. Blocks that read from the CV (experience, education, projects, skills, awards, a custom section) stay in sync with it; the others (an intro, facts, free text, an image gallery, links, the PDF itself, contact) are the page's own content. The page builder in the dashboard edits it visually; connected apps use `get_page_design`, `set_page_design`, `preview_page_design` (a screenshot at desktop or phone width) and `list_page_styles`.

| Part | What it holds |
| --- | --- |
| `style` | `paper`, `column`, `dark`, `editorial`, `minimal`, `studio`, `sunset`, `ocean` or `mono`. Each is a designed look; switching one brings its own palette and fonts. |
| `base` | `styled` (the default) or `bare`: structure and theme tokens only, none of the style's decoration, for a layout of your own. |
| `theme` | `accent`, `background`, `surface`, `ink`, `muted` (hex colours); `font_display` and `font_body` from the font list; `radius` 0-40; `width` 480-1280; `spacing` `compact`, `normal` or `airy`; `mode` `light`, `dark` or `auto` (follows each visitor's system); `toggle` to offer a light/dark switch; `palettes` `{light, dark}` of `background`, `surface`, `ink`, `muted`, `line`, `field`, `accent` (leave one out and it is derived); `preset` (`x`, `ink`, `slate`) fills both palettes. |
| `blocks` | `{id, type, props}` in page order. Types: `hero`, `facts`, `section`, `text`, `gallery`, `links`, `document`, `contact`, `form`, `divider`, and the version 2 blocks `header`, `profile`, `tabs`, `html` (below). A `form` block is a contact form: messages are emailed to the owner and kept under Published pages → Messages, with a hidden field and rate limits against bots. |
| `css` | Optional custom CSS for the page, written against the hooks below. Imports, anything that runs, and images from outside MyCV are refused, with the line and column of the problem; escapes such as `content:'\2600'` are fine. |
| `nav` | `true` for the automatic section dock, `false` for none, or a configured nav (below). |
| `seo` | `title` (70 characters), `description` (160) and `favicon` (a MyCV upload, square PNG) for the browser tab, search results and link previews. Blank falls back to the name, headline and summary. |

Hero fields left blank fall back to the CV (name, tagline, summary, photo), so a page never needs the person's details typed twice. Hero buttons open the PDF (`pdf`), a contact card download (`vcard`), the person's email, phone, LinkedIn or website, or any `link`; each takes an `icon` (`auto` picks one for the action; `none`, `download`, `mail`, `phone`, `linkedin`, `globe`, `arrow`, `github`, `calendar`, `file` or `card`). Views, downloads and contact clicks are counted per page (the owner's own are not) and shown on the dashboard and in `get_page_design`. Images in a gallery or the hero must be MyCV uploads. Setting a design saves a draft; the page goes live only when the person publishes, or when a connected app is asked to publish explicitly, and a preview link is returned either way. Every reply carries a `version` of the design and readability warnings when the theme's text would be hard to read; a write can pass `if_version` so a page edited in the builder meanwhile is not overwritten.

### Advanced blocks

A design that uses any of the following is on schema version 2. The page then loads MyCV's own small behaviours script; a design never carries JavaScript of its own.

| Block | What it is |
| --- | --- |
| `header` | A sticky, frosted top bar: `title` (blank is the name), `badge`, `subtitle`, `leading` and `trailing` actions. Always the first block. |
| `profile` | A social-style profile: `cover` and `avatar` images, `name`, `badge` (`verified`, `verified-gold`, `verified-grey`), `handle`, `bio` (blank is the CV summary), a `meta` row of icon + text (+ link), `stats`, `actions`, and a `layout` of `social`, `centered` or `split`. |
| `tabs` | Shows other blocks inside tabs: `tabs` is `[{name, label, blocks: [block ids]}]`, with `style` `underline`, `pills` or `segmented`. Keyboard, screen readers and `#tab-<name>` links work; without JavaScript every panel is simply on the page. |
| `html` | Your own markup. With `template: true` it is a Liquid template over the live CV. |

An *action* is `{label, action, url, target, icon, variant, keep_on_mobile}`. `action` is `link`, `email`, `phone`, `linkedin`, `website`, `pdf`, `vcard`, `theme-toggle`, `share`, `copy-link` or `scroll-to` (with `target: "#blockId"`); `variant` is `primary`, `outline`, `accent`, `ghost` or `icon`. `nav` may be an object: `{type: dock|bar|sidebar|bottom, items: [{label, icon, target | href | action}], mobile, show_labels}`. Every block also takes `class`, extra class names for its root element.

**Markup.** Text and HTML blocks accept ordinary structure (`div`, `span`, `section`, `article`, headings, lists, tables, `figure`, `img`, `picture`, `video`, `button`, `details`), `id`, `class`, `aria-*`, `data-*`, and inline SVG. Scripts, `style` attributes, event handlers, iframes and forms are removed, and every removal is reported back, never silent. Images come from MyCV uploads. Ids you write become `u-<blockId>-<id>` on the page (references inside the same block follow), so style with classes. Links that leave the site open in a new tab; `#fragment`, relative, `mailto:` and `tel:` links stay in the page.

**Icons.** Any name from the icon set (about a hundred interface, status and brand glyphs) works wherever a design takes an icon, as `<svg data-icon="map-pin" class="icon"></svg>` in markup and as `{% icon "map-pin" %}` in templates. Note `x` is the X/Twitter logo; the close glyph is `close`.

**Behaviours.** Interactivity is declared, not written: `data-theme-toggle`, `data-theme-set`, `data-tabs` / `data-tab` / `data-tab-panel`, `data-tab-link`, `data-scroll-to`, `data-copy`, `data-share`, `data-disclosure`, `data-dismiss`, `data-sticky`, `data-reveal`, `data-local-time`.

**Templates.** `{{ cv.full_name }}`, `{% for p in cv.projects %}…{% endfor %}`, `cv.experience`, `cv.education`, `cv.skills` (`category`, `items`), `cv.awards`, `cv.sections` and `cv.section["cs_id"]`, `page`, `assets["name"]`. Values are escaped; rich CV fields (summary, description, bullets) go through `| html`. Filters include `hashtag`, `date`, `relative_date`, `where`, `sort_by`, `slugify`, `markdown`, `truncate`. `{% render "badge" %}`, `"avatar"`, `"entry-card"`, `"post-card"` and `"skill-chips"` are built in. Email and phone are empty unless the page shows them, and placeholders like `[your.email@example.com]` are always empty. Because the template reads the CV when the page is served, editing the CV updates the page with no design change.

**Tokens.** `--pg-background`, `--pg-surface`, `--pg-ink`, `--pg-muted`, `--pg-line`, `--pg-field`, `--pg-accent`, plus computed `--pg-on-accent`, `--pg-accent-text`, `--pg-accent-soft`, `--pg-hover`, `--pg-bar`, `--pg-primary-bg`, `--pg-primary-ink`, and `--pg-radius`, `--pg-width`, `--pg-display`, `--pg-body`, `--pg-space-1` to `--pg-space-6`. They switch with the palette, so CSS written against them works in light and dark.

The full DOM of every block is in [page-dom.md](page-dom.md). Every saved design is kept as a version (the last 50, and all that went live) and can be restored.

### CSS hooks

Custom CSS should only use these selectors; anything else may change without notice.

| Selector | What it is |
| --- | --- |
| `.pg-main` | The page column (its max-width comes from `theme.width`). |
| `.pg-block` | Every block; also `.pg-hero`, `.pg-facts`, `.pg-section`, `.pg-text`, `.pg-gallery`, `.pg-links`, `.pg-contact`, `.pg-divider`. |
| `.pg-hero-inner`, `.pg-hero-media`, `.pg-hero-copy` | The intro grid, its photo and its text column. |
| `.pg-eyebrow`, `.pg-title`, `.pg-subtitle`, `.pg-lead` | The intro text, top to bottom. |
| `.pg-buttons`, `.pg-btn`, `.pg-btn.is-primary` | Button rows and buttons; the first hero button is primary. |
| `.pg-contacts` | The contact line under the intro. |
| `.pg-h2`, `.pg-intro`, `.pg-section-head` | A block heading, its intro line and the row holding them. |
| `.pg-facts-list`, `.pg-fact` | The facts grid and one label/value pair. |
| `.pg-entries`, `.pg-entry`, `.pg-entry-date`, `.pg-entry-title`, `.pg-entry-org`, `.pg-entry-desc`, `.pg-bullets`, `.pg-tags` | A CV section and the parts of one entry. |
| `.pg-skills`, `.pg-skill-group` | The skills block and one category. |
| `.pg-prose` | A text block body. |
| `.pg-gallery-grid`, `.pg-tile` | The gallery grid and one tile. |
| `.pg-link-list`, `.pg-link-label`, `.pg-link-note` | The links block. |
| `.pg-nav` | The floating section dock. |
| `.pg-header`, `.pg-header-inner`, `.pg-header-title`, `.pg-header-sub` | The sticky top bar and its text. |
| `.pg-profile`, `.pg-cover`, `.pg-avatar`, `.pg-actions`, `.pg-name`, `.pg-handle`, `.pg-bio`, `.pg-meta`, `.pg-stats` | The profile block, top to bottom. |
| `.pg-act`, `.pg-act.is-primary` (`is-outline`, `is-accent`, `is-ghost`, `is-icon`) | Buttons in the profile, header and nav. |
| `.pg-tabs`, `.pg-tablist`, `.pg-tab`, `.pg-tab[aria-selected="true"]`, `.pg-tabpanel` | The tabs block. |
| `.pg-html` | A free HTML block; your own classes live inside it. |
| `.pg-nav.is-dock` (`is-bar`, `is-sidebar`, `is-bottom`), `.pg-nav-item` | A configured nav and one item. |
| `.icon`, `.pg-badge` | An icon from the set, and the verified badge. |
| `[data-block-id]`, `[data-block-type]`, `[data-block-index]`, `[data-entry-id]`, `[data-entry-index]`, `[data-skill-group]` | Stable attributes on every block, entry and skill group, so rules never need `:first-child`. |
| `:root[data-theme="dark"]`, `[data-base="bare"]` | The theme a visitor toggled to, and the bare base. |
| `[data-style="paper"]` | The `html` element carries the style id, for rules scoped to one style. |
| `--pg-accent`, `--pg-background`, `--pg-surface`, `--pg-ink`, `--pg-muted`, `--pg-radius`, `--pg-display`, `--pg-body` | The theme as CSS variables on `:root`. |

## For agents

The in-app agent and connected apps (ChatGPT, Claude, Cursor and any MCP client) write the same object.

- Follow the `cv_data` schema published by the MCP server's `create_cv` and `update_cv` tools. `update_cv` merges: send only the keys that change.
- Section order comes from `section_order` only.
- `skills` is an object of category to comma-separated list, not an array.
- Never invent employers, dates, degrees or numbers. Leave an unknown detail out and say so.
- Write links as `[label](https://url)` in the summary or a bullet. They are stored as anchors and are clickable in every export.
- Set `"url"` on an entry or award to make its title a link. Use `<br>` for a line break inside a summary or bullet. List section ids or `section.index` entries in `page_breaks` to start them on a new page; `preview_cv` and `fit_layout` report which page each section lands on.
- Give an entry a `"description"` (one short paragraph) and `"tags"` (the tools and skills used) when the person's story has them; set `layout.durations` to `"on"` when they want the length of each role shown; set `layout.skillsFormat` to `"lines"` when the CV is going through an applicant tracking system. On a two-column design, set `layout.rail` when the person wants a section in, or out of, the side column.
- Save the posting the person is applying to as `target_job`; `score_cv` then scores job fit without being handed the text each time.
- Tell the person whenever the design or the section order changes; `update_cv` says so in its reply.
- To shape the published page, read it with `get_page_design`, edit the object and send it back with `set_page_design` (pass its `version` as `if_version`), then look at the result with `preview_page_design` before showing the person the preview link, and publish only with `publish: true` once they agree. A site they admire can be echoed through the style, theme and custom CSS, never by writing HTML.
- `import_github_projects` adds a public GitHub profile's repositories to the projects section (name, link, description, language, topics, active years); nothing is invented, and the person should be told what was added. `upload_page_image` stores a public image address or base64 bytes on MyCV's CDN for the page design, and can set it as the intro photo or the tab icon directly. `list_page_messages` reads what visitors sent through a `form` block.
- Every write takes a version snapshot first, so the person can undo it from Progress in the editor.
- The photo is the person's own; do not set or clear `profile_photo_url`.
