Reference

Getting started & API reference

mdspec routes markdown specs to destinations using frontmatter declared in each file. No external config. Each spec declares its own type, integration, and parent. Files without frontmatter are silently skipped.

Quick start

  1. Sign up, create an org and a project, generate a CI token.
  2. Connect an integration (Notion, ClickUp, Confluence, Jira, or S3) in the dashboard.
  3. Add the GitHub Actions step to your workflow.
  4. Add frontmatter to any markdown file you want to sync.

Minimal spec:

---
type: wiki
integration: notion
---

# Auth flow

This document describes how authentication works...

Frontmatter schema

Every spec declares its routing in YAML frontmatter at the top of the file. All fields fall back to project-level defaults — an empty frontmatter block is valid as long as the project has a default type and integration set.

FieldRequiredDescription
idNoStable identifier. Used for deduplication. Falls back to file path.
typeNo'wiki' or 'task'. Falls back to project default_type (wiki by default).
integrationNoTarget integration. Falls back to project default_integration.
parentNoAlias, native ID, or URL. Falls back to integration root.

type:

Determines whether the spec is transformed before publishing.

ValueBehaviour
wikiPublished as raw markdown. No transformation.
taskTransformed by your org's Task Template (Claude Haiku) before publishing.

On ClickUp, type also picks the publishing mode: wiki → Doc, task → Task.

integration:

Picks the destination integration. Falls back to the project's default integration if absent.

notion
clickup
confluence
jira
s3

Set the project default in Dashboard → Project Settings → General. With a default set, integration: becomes optional in frontmatter.

parent:

Where the spec lands inside the integration. Three formats — auto-detected:

FormatExampleNotes
Aliasparent: eng-docsRecommended. Defined in Dashboard → Integrations → Aliases.
Native IDparent: abc123def456Raw page/list/folder ID from the target system.
URLparent: https://notion.so/Engineering-abc123Resolved to native ID on first publish.

If parent: is absent, the spec publishes at the integration root (Notion workspace root, Confluence space root, S3 bucket root, etc.).

id:

Optional stable identifier. Used for deduplication across renames and as the ledger key. If absent, the file path is used.

---
id: checkout-retry
type: task
integration: clickup
parent: dev-sprint-list
---

With an id:, renaming the file keeps it linked to the same published target. Without one, a rename creates a fresh published doc.

CLI reference

The CLI package is mdspeci (trailing i). Invoke via npx.

publish

npx mdspeci publish --project <project-id>

Reads git diff against the previous commit, parses frontmatter on each changed .md, posts the payload to the mdspec API.

Flags

FlagDescription
--project <id>Required. Your mdspec project ID.
--allWalk the entire repo and publish every file with frontmatter, ignoring git diff. Useful for first-time setup.

Environment

VariableRequiredDescription
MDSPEC_TOKENYesYour project token (mds_...). Add as a GitHub Actions secret.
GITHUB_EVENT_BEFOREIn CIBase ref for git diff. Set automatically by GitHub Actions.
MDSPEC_API_URLNoOverride the API host. Defaults to https://mdspec.dev.

CI setup

One job in your GitHub Actions workflow. Triggers on every push to main.

name: mdspec sync
on:
  push:
    branches: [main]

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - run: npx mdspeci publish --project <project-id>
        env:
          MDSPEC_TOKEN: ${{ secrets.MDSPEC_TOKEN }}
          GITHUB_EVENT_BEFORE: ${{ github.event.before }}

First publish: use --all once to pick up all existing files with frontmatter. After that, the diff-based default is fast and incremental.

Aliases

Aliases are short, human-readable names that map to native IDs in your integrations. Define them in Dashboard → Integrations → Aliases, then reference them as parent: in your specs.

Example: an alias eng-docs points to a Notion page ID.

---
type: wiki
integration: notion
parent: eng-docs
---

Aliases are scoped to both the org and a specific integration. eng-docs on Notion and eng-docs on ClickUp are independent.

Templates

Templates drive the agent transformation applied when type: task is set. Edit the Task Template at Dashboard → Templates.

TypeTemplate
wikiNone — publish as-is.
taskTask Template (default, editable). Transforms a spec into a structured task document.

The Task Template seeds automatically when your org is created. You can edit the prompt to shape how specs are restructured before publishing.

Notion integration

OAuth-based. The integration is granted access to specific pages in your workspace; pick one as the root at connect time.

Connect

  1. Dashboard → Integrations → Notion → Connect.
  2. Approve the OAuth flow and select the pages the integration can access.
  3. Choose a default root page. All specs without parent: publish here.

Per-spec parent

Use any Notion page ID, page URL, or alias. The page must be shared with the integration.

---
type: wiki
integration: notion
parent: https://notion.so/Engineering-abc123def4567890
---

Behaviour

  • Every spec becomes one Notion page under the resolved parent.
  • Updates replace the page's blocks (no merge).
  • Title comes from the first # Heading in the file, falling back to the filename.

ClickUp integration

OAuth-based. Two publishing modes driven by spec type:

typeMode`parent:` is
wikiClickUp Doc with one pagespace or folder ID
taskClickUp tasklist ID

Doc mode (type: wiki)

---
type: wiki
integration: clickup
parent: product-docs   # alias → space or folder ID
---

# API rate limits

...

Each spec becomes its own Doc with one page. The doc title and page title come from the first heading.

Task mode (type: task)

---
id: checkout-retry-task
type: task
integration: clickup
parent: dev-sprint-list   # alias → list ID
---

# Checkout retry policy

When a payment attempt fails...

The first heading becomes the task name; the full markdown body becomes the task description. Priority, status, tags, and due date are not set by mdspec in v1.

Custom task IDs

If your workspace has Custom Task IDs enabled, you can put the custom ID (e.g. CU-182) in id: to link the spec to an existing task.

Confluence integration

Atlassian OAuth. Bound to a single Confluence space, chosen at connect time. Per-spec parent: targets a page within that space.

Connect

  1. Dashboard → Integrations → Confluence → Connect.
  2. Approve the Atlassian OAuth flow.
  3. Pick the Atlassian site and the space to publish into.
---
type: wiki
integration: confluence
parent: arch-decisions   # alias → Confluence page ID
---

# ADR 001 — Queue technology choice

## Context
...

Markdown is converted to Confluence storage format (headings, lists, code blocks). When parent: is absent, the page lands at the space content root.

Jira integration

Atlassian OAuth. Bound to a single Jira project, chosen at connect time. Specs become Jira issues.

Connect

  1. Dashboard → Integrations → Jira → Connect.
  2. Approve the Atlassian OAuth flow.
  3. Pick the Atlassian site and the Jira project to publish into.
---
type: task
integration: jira
---

# Add bulk import endpoint

Description body becomes the issue description.

Issue type defaults to Task. Markdown is converted to Atlassian Document Format (ADF). parent: is not used by Jira in v1.

S3 integration

AWS access key pair. Specs are written to the configured bucket. parent: becomes the key prefix.

Connect

  1. Dashboard → Integrations → S3 → Connect.
  2. Provide an Access Key ID and Secret Access Key with s3:PutObject permission on the bucket.
  3. Provide the bucket name and region.
---
type: wiki
integration: s3
parent: docs/eng-specs   # S3 key prefix
---

# Auth flow

...

Object key: {parent}/{filename}.md. Without a parent, the file lands at the bucket root.

Behaviour notes

  • Trigger: only push: branches: [main]. No per-branch publishing in v1.
  • Idempotent updates: content hash is stored. Republishing an unchanged spec is a no-op.
  • Append-only: removing a file from the repo does not delete it from the target tool. Clean up manually if needed.
  • Renames: in v1, a renamed file is treated as a new file. Set id: to make renames safe — the published doc updates instead of duplicating.
  • Self-healing: if the stored external ID points to a deleted page or task, mdspec recreates it and updates the ledger.
  • Rate limits: enforced per-integration via QStash flow control. Slower integrations (Confluence, Jira, Notion) are throttled lower than fast ones (ClickUp, S3).
  • No content storage: only metadata (hashes, IDs, URLs, frontmatter) is persisted on our side. Your spec content flows through and never lands in our database.

Example scenarios

Engineering wiki across Notion and Confluence

Most docs go to your team wiki on Notion. Architecture decisions go to Confluence so they're reviewable alongside other Atlassian docs.

# docs/wiki/onboarding.md
---
type: wiki
integration: notion
parent: eng-wiki
---

# docs/adrs/0042-queue-choice.md
---
type: wiki
integration: confluence
parent: arch-decisions
---

Sprint specs as ClickUp tasks

Each spec in docs/sprints/ becomes a ClickUp task in the active sprint list. The agent reshapes the spec into a task brief.

---
id: checkout-retry
type: task
integration: clickup
parent: dev-sprint-list
---

# Checkout retry policy

When a payment attempt fails, we need to retry with backoff...

Static markdown to S3

Spec docs land in an S3 bucket as static files. The default integration is set to s3, so you can omit integration: in frontmatter.

# Project Settings → Default Integration: s3

# docs/specs/auth-flow.md
---
type: wiki
parent: docs/eng-specs
---

Tell your agent

Paste this snippet into your AI assistant's context (Cursor, Copilot Chat, Claude Code) so it can add mdspec frontmatter to your specs without further prompting.

When I create or edit a markdown file under the docs/ directory, add mdspec
frontmatter at the top of the file so it syncs to our docs tool.

Schema:
  ---
  type: <wiki | task>           # required (v1 supports wiki and task only)
  integration: <notion | clickup | confluence | jira | s3>   # optional
  parent: <alias or ID>         # optional
  id: <stable-id>               # optional, recommended for files that may be renamed
  ---

Rules:
- type: wiki for general docs and ADRs
- type: task for sprint specs that should land in ClickUp/Jira as a task
- If integration: is omitted, the project default is used
- If parent: is omitted, the spec publishes at the integration root
- Files without frontmatter are skipped silently

Don't run the publish CLI yourself — that's handled by CI on push to main.