On this page
Quickstart
This walks from an API key to a served site. It takes four API calls.
Before you start
You need an organization API key. Keys start with dsy_ and are provisioned for you; see Access. Keep it out of source control.
The API base URL is:
https://api.docsary.com/v1
Every request authenticates with a bearer token:
Authorization: Bearer dsy_XXXX
Set it up in your shell:
export DOCSARY_KEY=dsy_XXXX
export DOCSARY_API=https://api.docsary.com
1. Check the key
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
"$DOCSARY_API/v1/whoami"
{
"kind": "org",
"org": { "id": "org_...", "slug": "acme", "name": "Acme", "plan": "free" },
"scopes": "write"
}
A 401 {"error":"invalid token"} means the key is wrong or revoked. A 401 {"error":"missing bearer token"} means the header did not arrive.
plan is the set of limits your organization runs under, and scopes says whether this key may write or only read.
2. Create a site
A site is one docs property: one address, one navigation tree, one set of versions.
curl -s -X POST "$DOCSARY_API/v1/sites" \
-H "authorization: Bearer $DOCSARY_KEY" \
-H "content-type: application/json" \
-d '{"slug":"acme","name":"Acme Docs"}'
{ "id": "site_...", "slug": "acme", "subdomain": "acme.docsary.com" }
201 Created. Note the subdomain: your site already has a live address, allocated from the slug you just chose. Keep it — step 6 is opening it.
The slug must match ^[a-z0-9][a-z0-9._-]*$ and is unique within your org. A duplicate returns 409. Everywhere :site appears in the API you may pass either the slug or the site_... id.
3. Create a version
Pages belong to a version, never directly to a site. Create one before pushing.
curl -s -X POST "$DOCSARY_API/v1/sites/acme/versions" \
-H "authorization: Bearer $DOCSARY_KEY" \
-H "content-type: application/json" \
-d '{"slug":"1.0","name":"1.0"}'
{ "id": "ver_...", "slug": "1.0" }
The first version created on a site automatically becomes that site's default version, which means it serves from unprefixed URLs. See Versions.
4. Push a markdown folder
Lay out a folder. File paths become URL paths:
docs/
index.md → /
quickstart.md → /quickstart
guide/index.md → /guide
guide/install.md → /guide/install
With the push script
The push script walks a directory, chunks it, and posts it:
node scripts/push.mjs ./docs --site acme --version 1.0 --prune
pushed 24/24
done: 24 created, 0 updated, 0 unchanged, 0 pruned
It reads DOCSARY_KEY from the environment, or falls back to ~/.secrets/docsary-api-key, and reads DOCSARY_API for the base URL. --prune deletes any page in the version that is not in the folder you just pushed; omit it to add and update without deleting. The script exits non-zero if any file was rejected.
With a raw manifest POST
The push script is a convenience wrapper over one endpoint. The manifest is a JSON object mapping file paths to markdown source:
curl -s -X POST "$DOCSARY_API/v1/sites/acme/versions/1.0/import" \
-H "authorization: Bearer $DOCSARY_KEY" \
-H "content-type: application/json" \
-d '{
"files": {
"index.md": "---\ntitle: Home\norder: 0\n---\n\n# Acme Docs\n\nWelcome.\n",
"quickstart.md": "---\ntitle: Quickstart\norder: 1\n---\n\n# Quickstart\n\nInstall it.\n"
}
}'
{
"created": ["", "quickstart"],
"updated": [],
"unchanged": [],
"deleted": [],
"rejected": []
}
Paths in the response are normalized page paths, not filenames — index.md became the empty path, which is the version root.
Send roughly six files per request — keep chunks small. The push script uses six. Chunking is safe because import is additive: post as many chunks as you need, then send one final prune call listing every path that should survive.
curl -s -X POST "$DOCSARY_API/v1/sites/acme/versions/1.0/import" \
-H "authorization: Bearer $DOCSARY_KEY" \
-H "content-type: application/json" \
-d '{"prune":{"keep":["index.md","quickstart.md"]}}'
Re-pushing identical content is free. Each page is keyed by a hash of its markdown; a matching hash returns unchanged and nothing is written.
5. Verify
List what landed, with the generated navigation tree:
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
"$DOCSARY_API/v1/sites/acme/versions/1.0/pages"
Read one page back as rendered HTML:
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
"$DOCSARY_API/v1/sites/acme/versions/1.0/pages/quickstart?format=html"
Round-trip the whole version back to markdown:
curl -s -H "authorization: Bearer $DOCSARY_KEY" \
"$DOCSARY_API/v1/sites/acme/versions/1.0/export"
6. Open it
It is already live. Go to the subdomain from step 2:
https://acme.docsary.com/
That address was allocated when you created the site and serves over HTTPS. There is no sixth API call to make.
Your own domain, optionally
You can put the same site on a hostname you own. Attach it:
curl -s -X POST "$DOCSARY_API/v1/sites/acme/domains" \
-H "authorization: Bearer $DOCSARY_KEY" \
-H "content-type: application/json" \
-d '{"hostname":"docs.acme.com","primary":true}'
The response hands you two DNS records. Publish the TXT one to prove the hostname is yours, then confirm it:
curl -s -X POST "$DOCSARY_API/v1/sites/acme/domains/docs.acme.com/verify" \
-H "authorization: Bearer $DOCSARY_KEY"
{ "hostname": "docs.acme.com", "verified": true, "certificate": { "status": "created" } }
Then publish the second record and the cutover is done:
docs.acme.com CNAME ssl.docsary.com
The certificate is issued and renewed for you; there is nothing to install. Your docsary.com address keeps working as a staging URL. See Domains for the whole flow, primary versus staging hosts, and what to do when verification does not pass on the first try.
What you have now
https://acme.docsary.com/and/quickstartrendered with navigation, table of contents, search, and dark mode./quickstart.mdand/index.mdserving raw markdown./llms.txt,/llms-full.txt,/sitemap.xml,/robots.txt./search?q=installover an index built during the push.
Next: Writing docs.