Skip to content

Configuration ​

Every setting, where it comes from and its default. markitai config list prints the values in effect; the CLI, markitai serve, the MCP server and the Python API read the same file.

Configuration Priority ​

The first source that sets a value wins:

Command-line flagsMARKITAI_* switchesPreset--config-jsonConfig fileBuilt-in defaults

A preset sets only the LLM, OCR, alt, desc and screenshot switches. Variables that supply a value the file leaves unset:

VariableRule
Provider keys (OPENAI_API_KEY, ...), <PREFIX>_API_BASEUsed when a model entry has no api_key / api_base
JINA_API_KEY, CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_IDUsed only when the config file leaves the value unset
MODELUsed only when llm.model_list is empty
MARKITAI_RECORD_HISTORY, MARKITAI_LOG_DIR, MARKITAI_LOG_FORMATOverride the file; a flag still wins
MARKITAI_PURE=1Turns pure mode on over the file (an off value is ignored); --pure / --no-pure still win
MARKITAI_NO_REMOTE_FETCH=1Blocks every remote fetch service, even an explicit -s

Configuration File ​

markitai reads one file, the first it finds:

  1. --config <path> (must exist; config set and config edit create it)
  2. MARKITAI_CONFIG (a missing file: a warning and the defaults)
  3. ./markitai.json in the current directory
  4. ~/.markitai/config.json (MARKITAI_HOME/config.json)

A markitai.json found in the current folder

It may come from a download or a cloned repository. A model there whose api_base is not its provider's own address uses only a key written in the entry: no key variable, and env: becomes markitai-no-key, with a warning. markitai doctor and --verbose say where the file was found.

bash
markitai init                         # write a config with the detected models (--yes, --local)
markitai config list                  # effective settings, secrets redacted (-f table, -f yaml)
markitai config get llm.enabled
markitai config set llm.enabled true  # validated; an unknown key is refused
markitai config edit                  # terminal editor
markitai config validate              # also warns about keys that have no effect

Redaction covers keys, tokens, passwords, cookies and every HTTP header value. Credentials and addresses can reference an environment variable as env:VAR_NAME.

Full Configuration Example ​

Every setting with its default, as markitai config list prints it:

markitai.json with all defaults
json
{
  "batch": {
    "concurrency": 10,
    "heavy_task_limit": 0,
    "scan_max_depth": 5,
    "scan_max_files": 10000,
    "state_flush_interval_seconds": 10,
    "url_concurrency": 5
  },
  "cache": {
    "enabled": true,
    "fetch_ttl_seconds": 86400,
    "global_dir": "~/.markitai",
    "max_size_bytes": 536870912,
    "no_cache": false,
    "no_cache_patterns": []
  },
  "fetch": {
    "cloudflare": {
      "api_token": null,
      "account_id": null,
      "cache_ttl": 0,
      "convert_enabled": false,
      "timeout": 30000,
      "wait_until": "networkidle0"
    },
    "defuddle": {
      "rpm": 20,
      "timeout": 30
    },
    "domain_profiles": {},
    "fallback_patterns": ["twitter.com", "x.com", "instagram.com", "facebook.com", "linkedin.com", "threads.net"],
    "jina": {
      "api_key": null,
      "no_cache": false,
      "rpm": 20,
      "timeout": 30
    },
    "playwright": {
      "cookies": null,
      "extra_wait_ms": 3000,
      "session_mode": "isolated",
      "session_ttl_seconds": 600,
      "timeout": 30000,
      "wait_for": "domcontentloaded"
    },
    "policy": {
      "enabled": true,
      "inherit_no_proxy": true,
      "local_only_patterns": [],
      "max_strategy_hops": 5
    },
    "remote_consent": "always",
    "strategy": "auto"
  },
  "history": {
    "record": false
  },
  "image": {
    "alt_enabled": false,
    "compress": true,
    "desc_enabled": false,
    "filter": {
      "deduplicate": true,
      "min_area": 5000,
      "min_height": 50,
      "min_width": 50
    },
    "format": "jpeg",
    "max_height": 99999,
    "max_width": 1920,
    "quality": 75,
    "stdout_fetch_external": false,
    "stdout_persist": true,
    "stdout_persist_dir": "~/.markitai/assets"
  },
  "llm": {
    "concurrency": 10,
    "enabled": false,
    "keep_base": false,
    "max_cost_per_document_usd": 0,
    "max_requests_per_document": 50,
    "max_vision_pages_per_document": 0,
    "model_list": [],
    "on_failure": "fallback",
    "providers": [],
    "pure": false,
    "router_settings": {
      "fallbacks": [],
      "num_retries": 2,
      "routing_strategy": "simple-shuffle",
      "timeout": 120
    }
  },
  "log": {
    "dir": null,
    "format": "text",
    "level": "INFO",
    "retention": "7 days",
    "rotation": "10 MB"
  },
  "ocr": {
    "enabled": false,
    "lang": "en",
    "per_page_routing": true
  },
  "office": {
    "macos_fallback": true
  },
  "output": {
    "allow_symlinks": false,
    "dir": null,
    "format": "markdown",
    "on_conflict": "rename",
    "page_markers": false,
    "profile": null,
    "report": null,
    "text_only": false,
    "wikilinks": false
  },
  "presets": {},
  "prompts": {
    "dir": "~/.markitai/prompts"
  },
  "screenshot": {
    "enabled": false,
    "max_height": 10000,
    "quality": 75,
    "screenshot_only": false,
    "tile_height": 2000,
    "viewport_height": 1080,
    "viewport_width": 1920
  },
  "security": {
    "pdf_sanitize": "warn"
  },
  "update": {
    "check": true
  }
}

llm.model_list starts empty and fills itself with --llm (how); markitai init writes entries, the web workspace writes llm.providers. ~/.markitai paths follow MARKITAI_HOME.

Environment Variables ​

API Keys ​

VariableService
OPENAI_API_KEYOpenAI
ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN, sent as a Bearer token)Anthropic
GEMINI_API_KEY (or GOOGLE_API_KEY, which wins)Google Gemini
DEEPSEEK_API_KEYDeepSeek
OPENROUTER_API_KEY (or OR_API_KEY)OpenRouter
AZURE_API_KEY, GROQ_API_KEY, MISTRAL_API_KEY, XAI_API_KEY, ...The other prefixes (Providers); used only for a model you name
JINA_API_KEYJina Reader (optional)
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_IDCloudflare

Markitai Settings ​

Switches are on with 1, true, yes or on.

VariableEffect
MARKITAI_HOMEInstead of ~/.markitai: config, .env, caches, browser, OCR models, history (process environment only)
MODELModel when model_list is not set
MARKITAI_CONFIGConfig file path
MARKITAI_LOG_DIR, MARKITAI_LOG_FORMATLog directory; text or json
MARKITAI_LANGTerminal language: zh… Chinese, anything else English
MARKITAI_PUREPure mode on (it cannot turn it off)
MARKITAI_RECORD_HISTORYRecord CLI runs in the web workspace history
MARKITAI_NO_REMOTE_FETCHNo remote fetch services, even with -s
MARKITAI_NO_VLM_OCR--ocr --llm OCRs locally and sends only the text
MARKITAI_OCR_BACKENDpaddle (default) or, on macOS, vision
MARKITAI_BROWSER_EXECUTABLE, PLAYWRIGHT_BROWSERS_PATHThe Chrome/Chromium to use; extra browser folders to search
MARKITAI_SERVE_TOKENFixed markitai serve access token (process environment only)
COPILOT_CLI_PATH, CLAUDE_CLI_PATH, CODEX_CLI_PATHWhere the subscription CLIs are (Subscriptions)
HTTPS_PROXY, HTTP_PROXY, ALL_PROXY, NO_PROXYProxies

The setup script reads its own: MARKITAI_VERSION, MARKITAI_INSTALL_DIR, MARKITAI_INSTALL_OPTIONAL, MARKITAI_DOWNLOAD_URL, MARKITAI_UNINSTALL.

.env File Loading ​

markitai loads ./.env, then ~/.markitai/.env. The environment wins over both, and ./.env over ~/.markitai/.env.

A folder's .env may not be yours (a download, a cloned repository), so ./.env may set keys and options but not programs, paths, endpoints, proxies, home folders or credential files. One warning lists what it ignored; set those in the environment or ~/.markitai/.env.

Names ./.env may not set

*_PATH (also *_CLI_PATH, PATH), *_EXECUTABLE, LD_*, DYLD_*, NODE_OPTIONS, PYTHON*, *_API_BASE, *_BASE_URL, *_ENDPOINT, HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY (any case), HOME, *_HOME, *_CONFIG_DIR, XDG_*, TMPDIR, TMP, TEMP, AWS_*_FILE, GOOGLE_APPLICATION_CREDENTIALS, CLOUDSDK_CONFIG, MARKITAI_CONFIG, MARKITAI_HOME, MARKITAI_LOG_DIR and the certificate-bundle variables.

LLM Configuration ​

Supported Providers ​

markitai speaks each provider's API itself. A model is prefix/model:

PrefixesReached through
openai/, anthropic/, gemini/, deepseek/, openrouter/, azure/The provider's own API, with its key variable
groq/, mistral/, xai/, together_ai/, fireworks_ai/, moonshot/, perplexity/, ...OpenAI-compatible endpoints (about 90 prefixes in all)
ollama/, lm_studio/, hosted_vllm/A server of your own, no key
bedrock/, vertex_ai/AWS and Google Cloud credentials of this machine
claude-agent/, copilot/, chatgpt/A subscription, through its official CLI
kimi_code/A Kimi Code membership key (KIMI_API_KEY), at Kimi's Messages API

The full list with key and address variables is in docs/llm.md; the web workspace's Add models offers every one. Another prefix fails with the route to use instead: openai/<model> with an api_base.

Kimi keys belong to a service and a region. A Kimi open platform key is moonshot/<model> with MOONSHOT_API_KEY: one from platform.kimi.ai works at the default https://api.moonshot.ai/v1, one from the China platform (platform.kimi.com) needs api_base: https://api.moonshot.cn/v1 or MOONSHOT_API_BASE. A Kimi Code membership key is kimi_code/<model> (kimi.com; a kimi.ai key needs https://api.kimi.ai/coding/v1). In the web workspace both are one Kimi card in Add models: paste either key, and the open platform's and Kimi Code's addresses are tried in turn (only Kimi's own hosts); the models are saved under the service and address that accept it. Kimi asks that the membership serve interactive use, so batch conversions belong on an open platform key.

SubscriptionPrefixNeedsSign in
Claude Codeclaude-agent/Claude Code 2.1.284+markitai auth claude login
GitHub Copilotcopilot/Copilot CLI 1.0.90-2+markitai auth copilot login
ChatGPTchatgpt/Codex CLI 0.159.0+markitai auth chatgpt login

Install commands: Claude and Copilot, ChatGPT. Gemini has no subscription: use gemini/ with a key or openrouter/google/....

Model Naming ​

provider/model, such as anthropic/claude-sonnet-5-5, ollama/llama3.2 or copilot/claude-haiku-5.5, plus the defaults below.

Defaults markitai picks for you ​

markitai init and key detection pick each provider's cheap, fast tier:

ProviderDefault model
Claude Codeclaude-agent/haiku
GitHub Copilotcopilot/auto
ChatGPTthe lightest model Codex lists (chatgpt/gpt-6-luna today)
Anthropicanthropic/claude-haiku-5-5
OpenAIopenai/gpt-6-luna
Geminigemini/gemini-flash-lite-latest
DeepSeekdeepseek/deepseek-flash
OpenRouteropenrouter/google/gemini-3.5-flash-lite

copilot/auto is Copilot's Auto selection (the only choice on Free and Student plans), billed as the model it picks; paid plans can pin one (copilot/claude-haiku-5.5).

How markitai picks models ​

With --llm and an empty llm.model_list:

yesnoyesnosomenone--llmmodel_list set?Use model_listMODEL set?That one modelSigned-in subscriptions, thenfound keysOne shared poolNo model is configured: exit 1
yesnoyesnosomenone--llmmodel_list set?Use model_listMODEL set?That one modelSigned-in subscriptions, thenfound keysOne shared poolNo model is configured: exit 1
  • The pool mixes vendors, even within one document; stderr says so at startup (--quiet hides it).
  • markitai init leaves out the Anthropic key beside a signed-in Claude Code (both are Claude Haiku).
  • markitai doctor shows sign-in status and keys no model uses.

Image analysis (--alt, --desc) needs a vision model; a model counts as one unless its entry says "model_info": {"supports_vision": false}.

Custom API Endpoint ​

api_base points a provider at a self-hosted server, regional proxy or gateway; it accepts env:VAR_NAME:

json
{
  "llm": {
    "model_list": [
      {
        "model_name": "default",
        "litellm_params": {
          "model": "openai/your-model-name",
          "api_key": "env:YOUR_API_KEY",
          "api_base": "https://your-api-endpoint.com/v1"
        }
      }
    ]
  }
}
json
// Local Ollama: no key, http://localhost:11434/v1 unless api_base says otherwise
{ "model": "ollama/llama3.2" }

// Azure OpenAI: the model is your deployment name, not a model ID
{
  "model": "azure/your-deployment-name",
  "api_key": "env:AZURE_API_KEY",
  "api_base": "https://your-resource.openai.azure.com",
  "api_version": "2025-02-01-preview"
}
  • Without api_key, an entry sends the provider's key variable (OPENAI_API_KEY for openai/) to its api_base: give a foreign endpoint its own key.
  • markitai serve saves keyless connections as "api_key": "markitai-no-key" (no credential), so the server's key never reaches an address a settings caller chose. Delete it here to use the environment's key.
  • Subscriptions take no api_key, api_base, max_tokens or reasoning_effort.

Model Settings ​

FieldDefaultDescription
litellm_params.max_tokensnullMax output tokens per request (else model_info.max_tokens, else the provider's limit)
litellm_params.reasoning_effortnullnone, minimal, low, medium, high, xhigh or max. Unset: thinking off for DeepSeek, OpenAI models that accept none and Claude Haiku 5.5; the model's default elsewhere
litellm_params.weight110 ten times as likely, 0 disabled
model_info.max_input_tokensnullContext window; long documents are split to fit it
model_info.supports_vision—false keeps the model out of image analysis

temperature, top_p and top_k are not settings and are never sent.

LLM Requests ​

Requests enter the model group default (an entry's model_name); with no fallbacks, every model is in it.

answersfails after retriesfailsfallbackfailRequestGroup default, weighted pickEnhanced .mdFallback groupon_failurePlain .md, warningItem fails
SettingDefaultDescription
llm.enabledfalse--llm
llm.router_settings.routing_strategysimple-shuffleOr least-busy, usage-based-routing, latency-based-routing
llm.router_settings.num_retries2Retries per request; a timeout after sending is resent once at most
llm.router_settings.timeout120Request timeout (s); Copilot stretches it with the prompt's size, up to 600 s
llm.router_settings.fallbacks[]e.g. [{"default": ["backup"]}]; other groups then get traffic only through fallback
llm.concurrency10Concurrent LLM requests (--llm-concurrency)
llm.max_requests_per_document50Then keep the plain output. 0 = no limit
llm.max_cost_per_document_usd0Spend cap per document; with a cap, unpriced models and subscriptions are refused (Prices). 0 = no limit
llm.max_vision_pages_per_document0Page images per document; a larger PDF sends none. 0 = no limit
llm.on_failurefallbackfallback: keep the plain .md, warn. fail: fail the item (exit 1, batch 10)
llm.purefalse--pure
llm.keep_basefalse--keep-base

Image Configuration ​

SettingDefaultDescription
image.alt_enabledfalseAlt text via LLM (--alt)
image.desc_enabledfalseImage descriptions (--desc)
image.compresstrueCompress extracted images
image.quality75JPEG/WebP quality (1–100)
image.formatjpegjpeg, png or webp
image.max_width1920Max width in pixels
image.max_height99999Max height in pixels
image.filter.min_width50Skip narrower images
image.filter.min_height50Skip shorter images
image.filter.min_area5000Skip smaller images
image.filter.deduplicatetrueStore a repeated picture once
image.stdout_persisttrueStdout mode: keep pictures in a store, linked as file://
image.stdout_persist_dir~/.markitai/assetsThat store (files under blobs/)

Screenshot Configuration ​

PDF pages, Office pages and slides as JPEG, URLs as full-page captures, in .markitai/screenshots/.

SettingDefaultDescription
screenshot.enabledfalse--screenshot
screenshot.screenshot_onlyfalse--screenshot-only
screenshot.viewport_width1920Browser viewport for URLs
screenshot.viewport_height1080Browser viewport for URLs
screenshot.quality75JPEG quality (1–100)
screenshot.tile_height2000Tile height for tall URL captures (for vision models)
screenshot.max_height10000Height cap when tile_height is 0

Presets ​

Besides minimal, standard and rich, define your own (each key defaults to false; names are case-insensitive, and a built-in name can be redefined) and run markitai document.pdf --preset my-preset:

json
{
  "presets": {
    "my-preset": { "llm": true, "ocr": false, "alt": true, "desc": false, "screenshot": true }
  }
}

OCR Configuration ​

Local OCR is built in: PaddleOCR models run inside markitai on every system. They download once (about 45 MB) on the first --ocr, or ahead of time with the setup script's optional parts or markitai doctor --fix. On macOS, MARKITAI_OCR_BACKEND=vision uses the system's Vision instead. --ocr --llm with a vision model needs neither: the model reads the pages.

SettingDefaultDescription
ocr.enabledfalse--ocr
ocr.langenA value below; anything else is an error
ocr.per_page_routingtrueOCR only pages whose text layer fails the checks (details); false OCRs every page
ocr.langReads
enThe multilingual model (Latin scripts, Chinese, Japanese), retrying Korean when that reads better
zh, zh_tw, ja, fr, de, es, it, pt, nl, pl, tr, vi, ...The multilingual model, with this language
latinLatin script only
ko, ar, th, el, ta, teKorean, Arabic, Thai, Greek, Tamil, Telugu
ru (or uk, be)Russian, Ukrainian, Belarusian
cyrillicOther Cyrillic languages
hi (or mr, ne), devanagariHindi, Marathi, Nepali

A region suffix is accepted (pt-BR). Vision takes single languages (en-US, zh, ja, ko, ...), not latin or cyrillic. On macOS 15, Vision under the default language may read Japanese with its Chinese recognizer: set ocr.lang: ja for Japanese there.

Batch Configuration ​

SettingDefaultDescription
batch.concurrency10Concurrent files (-j)
batch.url_concurrency5Concurrent URLs, counted apart from files
batch.scan_max_depth5Directory scan depth
batch.scan_max_files10000Max files per run
batch.state_flush_interval_seconds10How often --resume state is saved

URL Fetch Configuration ​

Fetch Strategies ​

fetch.strategyDescription
auto (default)Static, then the local browser; remote readers once you opt in (Fetch Policy)
staticPlain HTTP, built-in extractor, no JavaScript
playwrightA local Chrome or Chromium renders the page
defuddleDefuddle API, free, no key
jinaJina Reader API
cloudflareCloudflare Browser Rendering, extracted locally

Credentials can stay in the environment or be referenced:

json
{
  "fetch": {
    "jina": { "api_key": "env:JINA_API_KEY" },
    "cloudflare": { "api_token": "env:CLOUDFLARE_API_TOKEN", "account_id": "env:CLOUDFLARE_ACCOUNT_ID" }
  }
}

fetch.remote_consent decides whether auto may send a public URL to Defuddle, Jina or Cloudflare when local fetching fails:

yesnonothing (default)alwaysaskneverURLPrivate, intranet or credentials?Local strategies onlyremote_consent you wroteRemote fallback allowedAsk once per runLocal only, -s refused too
yesnonothing (default)alwaysaskneverURLPrivate, intranet or credentials?Local strategies onlyremote_consent you wroteRemote fallback allowedAsk once per runLocal only, -s refused too
SettingDefaultDescription
fetch.strategyauto-s
fetch.remote_consentalwaysShown as always, but only an always you write enables the fallback; ask asks on a terminal; never also refuses -s remote readers

markitai config set fetch.remote_consent always turns the fallback on. Details: Fetch Policy.

Browser Settings ​

markitai drives Chrome or Chromium itself; the keys keep their 1.x name playwright.

SettingDefaultDescription
fetch.playwright.timeout30000Load timeout (ms), also bounding work on the loaded page
fetch.playwright.wait_fordomcontentloadedload, domcontentloaded or networkidle
fetch.playwright.extra_wait_ms3000Max wait for JavaScript; ends once nothing loads or changes for 0.5 s
fetch.playwright.session_modeisolatedOr domain_persistent (reuse per domain within one run)
fetch.playwright.session_ttl_seconds600Persistent session lifetime
fetch.playwright.wait_for_selectornullCSS selector to wait for
fetch.playwright.cookiesnull[{name, value, domain, path}] (or url)
fetch.playwright.reject_resource_patternsnullBlock requests, e.g. ["**/*.css"]
fetch.playwright.extra_http_headersnulle.g. {"Accept-Language": "zh-CN"}; initial origin only
fetch.playwright.user_agentnullCustom User-Agent
fetch.playwright.http_credentialsnull{username, password}

Jina Settings ​

SettingDefaultDescription
fetch.jina.api_keynullAPI key (env: works); optional, raises the rate limit
fetch.jina.timeout30Seconds
fetch.jina.rpm20Requests per minute
fetch.jina.no_cachefalseBypass Jina's cache
fetch.jina.target_selectornullCSS selector to extract
fetch.jina.wait_for_selectornullCSS selector to wait for

Defuddle Settings ​

Defuddle returns the article body as Markdown with frontmatter. Free, no key.

SettingDefaultDescription
fetch.defuddle.timeout30Seconds
fetch.defuddle.rpm20Requests per minute

Cloudflare Settings ​

Browser Rendering (-s cloudflare) fetches rendered HTML; Workers AI toMarkdown (-b cloudflare) converts files in your account, replacing the native readers for the formats it takes.

SettingDefaultDescription
fetch.cloudflare.api_tokennullToken (env: works)
fetch.cloudflare.account_idnullAccount ID (env: works)
fetch.cloudflare.timeout30000Browser Rendering timeout (ms)
fetch.cloudflare.wait_untilnetworkidle0load, domcontentloaded or networkidle0
fetch.cloudflare.cache_ttl0Browser Rendering cache TTL (s)
fetch.cloudflare.convert_enabledfalseEnable toMarkdown for files

reject_resource_patterns (regexes; unset blocks style sheets and fonts), user_agent, cookies (with a url), wait_for_selector and http_credentials work as for the browser.

Set-up: the account ID is in the dashboard URL; create a custom API token with Browser Rendering: Edit and Workers AI: Read; enable Workers & Pages → Browser Rendering (Free plan included).

bash
export CLOUDFLARE_API_TOKEN="your-api-token"
export CLOUDFLARE_ACCOUNT_ID="your-account-id"

Fetch Policy, Domain Profiles and Fallback Patterns ​

The policy engine orders strategies per domain and learns which need a browser: options, profile fields and built-in profiles are in Fetch Policy. A domain_profiles entry overrides only the fields you set. Domains in a fallback_patterns list you write start with the browser; the built-in list has no effect.

Proxies ​

HTTPS_PROXY, HTTP_PROXY, ALL_PROXY and NO_PROXY apply (lower-case names too). Without them markitai uses the system proxy. A SOCKS proxy works only for the browser. SSL_CERT_FILE / SSL_CERT_DIR replace the system certificates.

Cache Configuration ​

FileHoldsReuse
cache.dbLLM results, image analyses includedConverting again costs nothing
fetch_cache.dbFetched HTML and text pagesWith ETag/Last-Modified: revalidated every run. Otherwise: fetch_ttl_seconds
SettingDefaultDescription
cache.enabledtrueCache LLM results and pages
cache.no_cachefalseSkip reads, keep writing (--no-cache)
cache.no_cache_patterns[]Globs on paths and URLs that bypass it (--no-cache-for)
cache.fetch_ttl_seconds86400Reuse time for pages without validators; 0 = refetch
cache.max_size_bytes536870912Per cache file (512 MB)
cache.global_dir~/.markitaiCache directory

Models served by Claude Code or Copilot are not cached.

bash
markitai cache stats --verbose         # LLM entries by model, plus fetched pages
markitai cache clear                   # empties cache.db and fetch_cache.db
markitai document.pdf --no-cache       # bypass for one run
markitai ./docs --no-cache-for "*.pdf"

Output Configuration ​

SettingDefaultDescription
output.dirnullOutput directory for folders and .urls lists without -o (a single file still prints)
output.on_conflictrenameOr overwrite, skip
output.allow_symlinksfalseFollow symbolic links in batches and output paths
output.reportnullJSON report: true, false, null = batch runs only
output.profilenullrag, obsidian or okf (Output profiles)
output.wikilinksfalseWith obsidian: ![[assets/x.jpg]] image links
output.formatmarkdownjson also writes the document JSON (-f json)
output.page_markersfalseKeep <!-- Page number: N --> and slide-number comments (--page-markers)
output.text_onlyfalsePictures and PDFs: only the recognised text (--text-only)

Log Configuration ​

Logs go to files only when log.dir is set.

SettingDefaultDescription
log.levelINFODEBUG, INFO, WARNING, ERROR, CRITICAL
log.formattextOr json, one record per line
log.dirnullLog directory
log.rotation10 MBStart the next file at a size (10 MB, 1.5 KiB), after an interval (1 week, 10 hours), at a local time (00:00, monday at 12:00, sunday) or hourly/daily/weekly/monthly/yearly
log.retention7 daysDelete older logs; a duration such as 7 days, 1 month or 1 week, 3 days

markitai config validate refuses any other form. Logs never hold document text, full model requests or keys.

Security Configuration ​

SettingDefaultDescription
security.pdf_sanitizewarnText a reader cannot see in a PDF (transparent, 1 pt or smaller, white on white) could reach the Markdown and LLM prompts. warn removes it and quotes it in a notice, remove also removes white text whose background cannot be checked, off keeps it without notices

A searchable scan's OCR layer (invisible text over a picture covering the page) does not count.

History ​

SettingDefaultDescription
history.recordfalseRecord CLI runs in the web workspace history (--record-history)
update.checktrueCheck for a newer release once a day and name it on the terminal (markitai update); MARKITAI_NO_UPDATE_CHECK=1 also turns it off

Settings Without Effect ​

Accepted for 1.x files; markitai config validate warns when you set them:

SettingInstead
office.macos_fallbackOffice page images always come from LibreOffice
batch.heavy_task_limit-j / batch.concurrency
image.stdout_fetch_externalNo terminal images; pictures on stdout are kept as files

Custom Prompts ​

Each LLM task has a system prompt (role, rules) and a user prompt (content template). Override one with a file of its name in prompts.dir (~/.markitai/prompts/), or point a setting at a file:

json
{
  "prompts": {
    "dir": "~/.markitai/prompts",
    "cleaner_system": "/path/to/my-cleaner-system.md"
  }
}
PromptTask
cleanerDocument cleaning
url_enhanceWeb page cleaning
image_captionAlt text
image_descriptionImage descriptions
image_analysisCaption, description and image text in one request
document_processDocument processing
document_visionDocument processing with page images
screenshot_extract--screenshot-only extraction (file only, no setting)

Each comes as _system and _user: file cleaner_system.md, setting cleaner_system.