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
- Sign up, create an org and a project, generate a CI token.
- Connect an integration (Notion, ClickUp, Confluence, Jira, or S3) in the dashboard.
- Add the GitHub Actions step to your workflow.
- 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.
| Field | Required | Description |
|---|---|---|
id | No | Stable identifier. Used for deduplication. Falls back to file path. |
type | No | 'wiki' or 'task'. Falls back to project default_type (wiki by default). |
integration | No | Target integration. Falls back to project default_integration. |
parent | No | Alias, native ID, or URL. Falls back to integration root. |
wiki and task are supported. Specs declaring other types are rejected with a clear error. Additional types (ADR, RFC, runbook, etc.) ship later.type:
Determines whether the spec is transformed before publishing.
| Value | Behaviour |
|---|---|
wiki | Published as raw markdown. No transformation. |
task | Transformed 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:
| Format | Example | Notes |
|---|---|---|
| Alias | parent: eng-docs | Recommended. Defined in Dashboard → Integrations → Aliases. |
| Native ID | parent: abc123def456 | Raw page/list/folder ID from the target system. |
| URL | parent: https://notion.so/Engineering-abc123 | Resolved 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.
npx mdspeci — not npx mdspec. The latter installs an unrelated third-party package.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
| Flag | Description |
|---|---|
--project <id> | Required. Your mdspec project ID. |
--all | Walk the entire repo and publish every file with frontmatter, ignoring git diff. Useful for first-time setup. |
Environment
| Variable | Required | Description |
|---|---|---|
MDSPEC_TOKEN | Yes | Your project token (mds_...). Add as a GitHub Actions secret. |
GITHUB_EVENT_BEFORE | In CI | Base ref for git diff. Set automatically by GitHub Actions. |
MDSPEC_API_URL | No | Override 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.
| Type | Template |
|---|---|
wiki | None — publish as-is. |
task | Task 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
- Dashboard → Integrations → Notion → Connect.
- Approve the OAuth flow and select the pages the integration can access.
- 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
# Headingin the file, falling back to the filename.
ClickUp integration
OAuth-based. Two publishing modes driven by spec type:
| type | Mode | `parent:` is |
|---|---|---|
wiki | ClickUp Doc with one page | space or folder ID |
task | ClickUp task | list 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
- Dashboard → Integrations → Confluence → Connect.
- Approve the Atlassian OAuth flow.
- 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
- Dashboard → Integrations → Jira → Connect.
- Approve the Atlassian OAuth flow.
- 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
- Dashboard → Integrations → S3 → Connect.
- Provide an Access Key ID and Secret Access Key with
s3:PutObjectpermission on the bucket. - 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
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.