| name | extracto-cli |
| description | Use when the user wants to extract text from PDFs or images, manage OCR jobs, or work with output presets via the local Extracto OCR webapp. Talks HTTP to a running Extracto instance using the bundled `extracto` CLI. |
Extracto CLI — Agent Skill Reference
What Extracto is
Extracto is a self-hosted OCR webapp that extracts text from PDFs and images using LLMs (Ollama on the host machine, Mistral OCR API, OpenRouter, or any OpenAI-compatible endpoint). It exposes both a browser UI and a stable headless HTTP surface under /api/v1/. The extracto CLI is a thin Bash wrapper around that HTTP surface plus the Docker lifecycle commands.
Prerequisites
- Container running — confirm with
extracto status. If nothing is up, extracto on brings it up via docker compose up -d --build.
- API token — required for everything under
/api/v1/. Get one with:
extracto api-key create <email> <name>
Save the printed key in either:
EXTRACTO_TOKEN env var, or
~/.extracto/config as a line EXTRACTO_TOKEN=<key>
- Base URL — defaults to
http://127.0.0.1:3000. Override with EXTRACTO_URL=https://my.host:port if Extracto runs elsewhere.
The CLI dies with a clear error if either the URL is unreachable or the token is missing.
Common workflows
Extract text from a single file
extracto ocr ./invoice.pdf --model mistral-ocr-latest
The --model flag is required — /api/v1/ocr/batch rejects submissions without one. Pick a model that's available to the user's currently-configured provider (see extracto settings get).
The CLI submits the file, polls until the job leaves QUEUED/RUNNING, and prints the final job JSON. Use --out result.json to save instead of printing, --no-wait to return as soon as the job is queued.
Supported types: .pdf, .png, .jpg/.jpeg, .webp. The CLI base64-encodes the file as a data URL and sends it to POST /api/v1/ocr/batch. Files larger than 32 MiB are rejected client-side — use the web UI instead, since the OS arg-list limit gets hit before the API does.
More model examples
extracto ocr ./scan.png --model llava:13b
extracto ocr ./report.pdf --model mistral-ocr-latest
extracto ocr ./photo.jpg --model openai/gpt-4o
v0.4.0 quality flags
extracto ocr ./paper.pdf --model anthropic/claude-3.5-sonnet --pages 1-5,7
extracto ocr ./invoice.pdf --model openai/gpt-4o --preset invoice
extracto ocr ./paper.pdf --model anthropic/claude-3.5-sonnet --preset academic
extracto ocr ./scan.pdf --model llava:13b --no-text-layer
--preset accepts generic, academic, invoice, contract, form. The default is generic. Born-digital PDFs use the text-layer fast-path automatically (no VLM call) unless you pass --no-text-layer.
Estimate cost before running
extracto estimate ./report.pdf --model anthropic/claude-3.5-sonnet
extracto estimate ./scan.png --model mistral-ocr-latest
extracto estimate --pages 50 --model openai/gpt-4o
extracto estimate ./report.pdf --model openai/gpt-4o --post-model anthropic/claude-3.5-sonnet --post-format json
extracto estimate calls POST /api/v1/ocr/estimate and returns the dollar total before you commit. Page counts are auto-detected (PDFs via pdfinfo, images = 1 page) or you can pass --pages N directly. Pricing sources: OpenRouter live API, LiteLLM community mirror for OpenAI-compatible models, static table for Mistral OCR (per-page billed; Mistral has no pricing API), $0 for Ollama. Self-hosted OpenAI-compatible endpoints with no mirror entry return $0 plus a warning so you know the estimate excludes provider cost. Pass --post-model NAME to add a post-processing pass to the estimate.
Translate or summarize the OCR output
extracto ocr ./report.pdf --model openai/gpt-4o --post-template translate --target-language Italian
extracto ocr ./meeting.pdf --model openai/gpt-4o --post-template summarize-3sentence
extracto ocr ./meeting.pdf --model openai/gpt-4o --post-template summarize-executive --post-model anthropic/claude-sonnet-4.6
extracto ocr ./meeting.pdf --model openai/gpt-4o --post-template extract-actions --post-format markdown
--post-template selects a server-built post-processing instruction so you don't have to write it yourself. Supported: translate (requires --target-language), summarize-3sentence, summarize-executive, extract-actions, or custom (the default; pairs with the free-form instruction in your stored settings). --post-model overrides the post-processing model; --post-format selects markdown or json output. The same fields are accepted on POST /api/v1/ocr/batch (postProcessing.template, postProcessing.targetLanguage) and the ocr_submit MCP tool.
Compare multiple models on the same input
extracto compare run --file ./invoice.pdf --models "mistral-ocr-latest,openai/gpt-4o,anthropic/claude-sonnet-4.6"
extracto compare get <comparison-id>
extracto compare run calls POST /api/v1/ocr/compare with the file + 2 to 4 model ids. Returns a comparisonId plus the per-model jobIds. extracto compare get returns each model's job (status, extractedText, processingMs, error if any) and a server-computed all-pairs diff: one entry per unordered job pair (N(N-1)/2 entries for N models), each with { baselineJobId, candidateJobId, segments, summary, truncated? }. Segments are { op: "equal"|"insert"|"delete", text }; summary is { equalChars, insertedChars, deletedChars, similarity }. The browser dialog lets you click any model to make it the baseline; the reverse-direction lookup flips insert/delete from the cached pair.
Get model recommendations from your own history
extracto recommend
extracto recommend --days 30
extracto recommend calls GET /api/v1/recommendations and groups your recent COMPLETED+FAILED jobs by document type. For each kind (invoice, receipt, contract, academic, form, id, generic) the response returns the highest success-rate model with successRate, attempts, meanMs, plus up to 3 alternatives, and an insufficientData: true flag when there aren't enough samples (less than 3 attempts per model). Default lookback is 90 days, override with --days N (max 365).
Redact PII from a chunk of text
extracto redact --text "Call Alice at 555-123-4567 or alice@example.com"
extracto redact --file ./meeting-notes.md
extracto redact calls POST /api/v1/pii/redact. Detects emails, phones (>=7 digits), Luhn-valid credit cards, IBAN-shaped strings, IPv4 addresses, URLs, dates of birth, and SSNs. Returns the redacted text with [REDACTED:KIND:N] placeholders plus an audit (kind + char offsets only — never the original values).
You can also turn on redaction per OCR job (settings field piiRedaction: true); the pipeline applies redaction to the final markdown and persists the audit on the job's metadata.piiAudit.
End-to-end encrypt a result with your registered public key
extracto e2e status
extracto e2e encrypt --text "secret OCR result"
extracto e2e encrypt --file ./result.md
Register an RSA-2048+ public key first (browser-only via PUT /api/v1/e2e/key with {publicKeyPem}); the server then accepts encryption requests and returns the sealed envelope (encryptedKey, iv, authTag, ciphertext, publicKeyFingerprint). Decrypt client-side with your private key. Pipeline auto-encryption of OCR results is scaffolded but disabled by default in v1.0; the user is responsible for key generation, escrow, and rotation.
List recent jobs
extracto jobs list
extracto jobs list 100
extracto jobs list --status COMPLETED
extracto jobs list --q invoice
extracto jobs list --model qwen
extracto jobs list --from 2026-01-01 --to 2026-01-31
extracto jobs list --tags tagid1,tagid2
Filters AND-combine across distinct keys; --tags is comma-separated and OR-combines within itself. Returns a JSON list with id/status/fileName/model/timestamps/preview/tags.
Inspect or wait on one job
extracto jobs get <job-id>
extracto jobs wait <job-id>
wait polls every 2 seconds and prints the final state when the status leaves QUEUED/RUNNING.
Get form fields from a form-shaped job
extracto jobs form-fields <job-id>
extracto jobs form-fields calls GET /api/v1/jobs/{id}/form-fields. Reads the job's structured result and returns the form block flattened into { field, value, page? } entries plus a flat byField map. Works best when the job ran with documentPreset: "form" so the OCR prompt asks the model to populate fields.form. Returns source: "absent" when no form data was captured.
Pull equations from an academic job
extracto jobs equations <job-id>
extracto jobs equations calls GET /api/v1/jobs/{id}/equations. Parses the job's extractedText for $$..$$ (display) and $..$ (inline) math, ignoring math inside fenced or inline code spans. Best results when the job ran with documentPreset: "academic" so the OCR prompt asked the model to wrap math in TeX delimiters. Returns each equation with its kind, latex, and char offsets.
Edit a page (with version history)
extracto jobs edit-page <job-id> <page-number> --text "Cleaned-up markdown..."
extracto jobs edit-page <job-id> <page-number> --from-file path/to/page.md
extracto jobs page-history <job-id> <page-number>
edit-page replaces the markdown of one page on a COMPLETED job and re-stitches the job's extractedText. The previous text is appended to the page's edit history (max 20 entries; newest first). The job is flagged userEdited: true, and prior KB/S3 exports are marked stale, you'll need to re-export to update them.
Export a job to a downloadable file
extracto jobs export <job-id> --format md
extracto jobs export <job-id> --format docx --out report.docx
extracto jobs export <job-id> --format xlsx
extracto jobs export <job-id> --format csv
extracto jobs export <job-id> --format rtf
extracto jobs export <job-id> --format txt
extracto jobs export <job-id> --format html
extracto jobs export <job-id> --format json
extracto jobs export <job-id> --format obsidian
extracto jobs export <job-id> --format zip
extracto jobs export calls GET /api/v1/jobs/{id}/export?format=.... Supported formats: md, json, txt, html, docx, rtf, csv, xlsx, obsidian, zip. CSV emits the first markdown table; if the document has multiple tables the file contains all of them with # table N separators. XLSX puts each markdown table on its own sheet (and falls back to a single text-dump sheet for prose-only documents). The obsidian format returns a zip with a per-job folder (date-prefixed for sortability), an index note with frontmatter, per-page notes under pages/ for multi-page jobs, and attachments under attachments/ when the source preview is available. The zip format is a flat archive: top-level index.md with page links, one pages/page-NNN.md per page from the structured result, and all-pages.md with everything joined. Only works on COMPLETED jobs (returns 409 otherwise).
Cancel or delete a job
extracto jobs cancel <job-id>
extracto jobs delete <job-id>
Tag jobs
Tags are user-owned labels you can attach to jobs to organize the History panel. The CLI talks to /api/v1/tags and /api/v1/jobs/{id}/tags.
extracto tags list
extracto tags create "Invoices" blue
extracto tags update <tag-id> --name "Q1 invoices" --color green
extracto tags delete <tag-id>
extracto jobs set-tags <job-id> <tag-id> <tag-id> ...
extracto jobs set-tags <job-id>
extracto jobs bulk-tag --jobs id,id,... --tags id,id,...
extracto jobs bulk-tag --jobs id,id --tags id --mode replace
extracto jobs bulk-tag --jobs id,id --tags "" --mode replace
jobs list and jobs get include a tags: [{id, name, color}] array.
Saved searches
Persist a filter set under a name and recall it later. Useful when an agent or operator runs the same History query repeatedly.
extracto searches list
extracto searches save "Q1 invoices" --q invoice --from 2026-01-01 --to 2026-03-31 --tags tagid1
extracto searches save "Latest failures" --status FAILED --from 2026-04-01
extracto searches rename <id> "Q2 invoices"
extracto searches delete <id>
Saving with a name that already exists overwrites that search's filters. The list endpoint self-heals: tag ids in filters.tagIds that no longer exist (deleted tags) are stripped from the response so the search doesn't silently match nothing. Apply a saved search by reading its filters JSON and passing those flags to extracto jobs list.
Manage output presets
Presets are saved post-processing instructions (e.g. "Extract all tables as JSON"). Useful for repeated downstream pipelines.
extracto presets list
extracto presets create "Tables to JSON" "Extract every table as a JSON array of rows" json
extracto presets delete <preset-id>
Inspect provider settings
extracto settings get
Settings (provider, endpoint, hasApiKey) are per-user and stored on the filesystem next to the SQLite DB. To change them, use the web UI — the CLI deliberately does not write secrets.
S3-compatible storage
Configure the bucket once via Settings → S3 in the UI (any S3-compatible endpoint: AWS S3, R2, Backblaze, MinIO, Garage, Ceph, SeaweedFS, etc.). Then from the CLI:
extracto s3 export <job-id>
extracto s3 export <job-id> --prefix scans
extracto s3 ls
extracto s3 ls --prefix invoices --all
extracto s3 download <key> [out-file]
Endpoint is server-side validated against SSRF (cloud-metadata IPs and link-local always blocked). Loopback / RFC1918 hosts require S3_ALLOW_LOOPBACK=1 (global) or S3_ALLOWED_HOSTS=foo.internal,*.bar.internal (granular).
Output formats
- All commands emit JSON straight from the API surface, no transformation. Pipe to
jq for filtering.
- The OCR job result includes:
extractedText: the final markdown.
result: structured JSON. result.structured.pages[] is the per-page array; each entry has pageNumber, durationMs, markdown, and (when detection succeeds) language (ISO 639-3, e.g. eng/ita) plus languageName (English name).
metadata: provider, model, timing, post-processing info, pageResults[] mirroring the per-page fields above, and (when first-page heuristics succeed) a document sub-object with title, date, authors[], and keywords[]. May also include documentType { kind, confidence } where kind is one of invoice, receipt, contract, academic, form, id, or generic. A page entry may include degenerateRetry: { reason, succeeded } when the server detected degenerate output (char-run / no-whitespace / token-loop / provider-noise) and re-OCR'd the page once without text-layer anchoring. The reason is the failure mode that triggered the retry. Retries are budgeted per job so a pathologically broken document cannot double the cost of an entire batch.
Cloud storage integrations
Operators register an OAuth app per provider (Dropbox, Google Drive, OneDrive) at the provider's developer console, then set the client id/secret in docker.env and a PUBLIC_BASE_URL matching the URL users hit in a browser. Users connect their account from Settings; tokens are stored encrypted with an AUTH_SECRET-derived key.
extracto dropbox list
extracto dropbox list /Apps/Extracto/incoming
extracto dropbox import --path /Apps/Extracto/invoice.pdf --model mistral-ocr-latest
extracto dropbox push --job <job-id> --folder /Apps/Extracto/output --format docx
extracto dropbox disconnect
Import downloads the file from Dropbox, queues it for OCR, and returns the jobId. Push renders a COMPLETED job to the chosen format and uploads it back to the chosen folder. Same surface is exposed on POST /api/v1/integrations/dropbox/{import,push} and the dropbox_* MCP tools.
extracto gdrive list
extracto gdrive list <folder-id>
extracto gdrive import --file <file-id> --model openai/gpt-4o
extracto gdrive push --job <job-id> --parent <folder-id> --format docx
extracto gdrive disconnect
Google Drive uses the least-privilege drive.file scope: the app can only see files the user picks via the connected app or files the app itself created. While the operator's OAuth project is in Google's "Testing" status, refresh tokens expire after 7 days; submit the project for Basic Verification (no security audit) to remove that limit. Same surface on POST /api/v1/integrations/google_drive/{import,push} and the google_drive_* MCP tools.
extracto onedrive list
extracto onedrive list <item-id>
extracto onedrive import --file <item-id> --model openai/gpt-4o
extracto onedrive push --job <job-id> --parent <item-id> --format docx
extracto onedrive disconnect
OneDrive uses the least-privilege Files.ReadWrite.AppFolder scope plus User.Read and offline_access. The app gets its own /Apps/Extracto/ subfolder under the user's personal OneDrive — work / school accounts are out of scope for v0.11.0 (they require admin consent and the common authority instead of consumers). Same surface on POST /api/v1/integrations/onedrive/{import,push} and the onedrive_* MCP tools.
Watched folders
Extracto can sweep a Dropbox / Google Drive / OneDrive folder, or a local folder under LOCAL_WATCH_ROOT/<userId>/, and auto-submit any new file (pdf, png, jpg, webp; up to 64 MiB) to the OCR queue. Configure from the Settings → Integrations tab in the browser, or via REST/CLI/MCP:
extracto integrations list
extracto integrations watchers list
extracto integrations watchers add --provider dropbox --name inbox --folder /Inbox --model mistral-ocr-latest --interval 300
extracto integrations watchers add --provider google_drive --name inbox --folder <folder-id> --model mistral-ocr-latest
extracto integrations watchers add --provider onedrive --name inbox --folder <item-id> --model mistral-ocr-latest
extracto integrations watchers add --provider local --name inbox --folder inbox --model mistral-ocr-latest
extracto integrations watchers pause <id>
extracto integrations watchers resume <id>
extracto integrations watchers delete <id>
REST: GET/POST /api/v1/integrations/watchers, PATCH/DELETE /api/v1/integrations/watchers/{id}. MCP: watchers_list, watchers_create, watchers_update, watchers_delete, plus integrations_status. Each watcher entry surfaces provider, name, folderPath, model, intervalSeconds, active, lastPolledAt, lastError, and ingestedCount (number of files this source has ingested). Sweep cadence is global (30 s); each watcher only polls when its own intervalSeconds has elapsed since lastPolledAt. Five consecutive list failures auto-pause the watcher and surface lastError. The local provider sandboxes each user under LOCAL_WATCH_ROOT/<userId>/ (defaults to <DATABASE_URL_DIR>/local-watch); .. and absolute paths are rejected.
Personal OAuth credentials
When the operator has not set DROPBOX_CLIENT_ID / GOOGLE_CLIENT_ID / ONEDRIVE_CLIENT_ID in docker.env, each user can paste their own OAuth client_id and client_secret. They are encrypted with AUTH_SECRET and stored per-user. Server-wide creds keep working for users who do not paste their own.
extracto integrations oauth-app get --provider dropbox
extracto integrations oauth-app set --provider dropbox --client-id ID --client-secret SECRET
extracto integrations oauth-app clear --provider dropbox
REST: GET/PUT/DELETE /api/v1/integrations/{provider}/oauth-app. MCP: oauth_app_status, oauth_app_set, oauth_app_clear. The redirect URI Extracto registers is <PUBLIC_BASE_URL>/api/integrations/<provider>/callback.
Webhooks
Register HTTP receivers that get an HMAC-signed POST when jobs change state (job.created, job.completed, job.failed) or when a watcher ingests a file (watcher.ingested). Failed deliveries retry on a 1m / 5m / 30m / 2h / 12h schedule (six attempts). After 20 consecutive failures the webhook auto-disables.
extracto webhooks list
extracto webhooks create --url https://example.com/hooks --events job.completed,job.failed
extracto webhooks update <id> --url https://new.example.com/hooks --active false
extracto webhooks test <id>
extracto webhooks rotate-secret <id>
extracto webhooks deliveries <id> --limit 50
extracto webhooks delete <id>
REST: GET/POST /api/v1/webhooks, PATCH/DELETE /api/v1/webhooks/{id}, POST /api/v1/webhooks/{id}/test, POST /api/v1/webhooks/{id}/rotate-secret, GET /api/v1/webhooks/{id}/deliveries. MCP: webhooks_list, webhooks_create, webhooks_update, webhooks_delete, webhooks_test, webhooks_rotate_secret, webhooks_deliveries.
The signing secret is shown once on create (and again on rotate-secret) and never afterwards. Verify the X-Extracto-Signature: t=<unix>,v1=<hex> header by computing HMAC-SHA256(secret, "${t}.${rawBody}") and timing-safe comparing against <hex>. Reject signatures whose timestamp is more than 5 minutes off. Webhook signing secrets are encrypted at rest (AES-GCM keyed off AUTH_SECRET).
Output presets, API keys, metrics
extracto presets list
extracto presets create <name> <instruction> [markdown|json]
extracto presets delete <id>
extracto api-key list
extracto api-key create <email> <name>
extracto api-key revoke <id>
extracto keys rotate <id>
extracto metrics
REST: GET/POST /api/v1/presets, DELETE /api/v1/presets/{id}; GET/POST /api/v1/keys, DELETE /api/v1/keys/{id}, POST /api/v1/keys/{id}/rotate; GET /api/v1/metrics. MCP: presets_list, presets_create, presets_delete, keys_list, keys_create, keys_delete, keys_rotate, metrics_get.
Disconnect a cloud integration
extracto dropbox disconnect
extracto gdrive disconnect
extracto onedrive disconnect
REST: DELETE /api/v1/integrations/{dropbox|google_drive|onedrive}. MCP: integration_disconnect. Best-effort revokes the OAuth token at the provider before deleting the local row; OneDrive has no per-token revoke API so the token expires by inactivity (~90 days).
Operator-only env flags
These flags are read at server boot from docker.env (or the host process env). They have no REST/MCP/CLI surface.
Lifecycle / runtime
MIGRATE_ON_START=1 — run bun run db:push on container start. Default 1; set 0 to skip schema sync (advanced).
OCR_WORKER_CONCURRENCY — max concurrent in-process OCR jobs. Default 1.
OCR_ORPHAN_JOB_STALE_MS — how stale a PROCESSING row must be before the boot/timer sweep flips it to FAILED. Default 1800000 (30m).
RESULT_STORAGE — inline (default) or s3 to offload large extractedText/result blobs to S3.
MAX_REQUEST_BODY_MB — request body cap for upload routes.
Auth / signup / sessions
AUTH_SECRET — required, min 32 chars, signs sessions and gates AES-GCM at-rest.
ALLOW_SIGNUP=1 — allow new account registration. Default 1; set 0 to lock signup on a deployment.
COOKIE_SECURE=true — secure cookie attribute. Disable only for localhost HTTP installs.
TRUSTED_PROXY_HEADERS=1 — trust X-Forwarded-For for client IP detection. Only set this when the server is actually behind a reverse proxy you control.
Outbound network policy (SSRF allowlists)
OLLAMA_ALLOWED_HOSTS / MISTRAL_ALLOWED_HOSTS / OPENROUTER_ALLOWED_HOSTS / OPENAI_COMPAT_ALLOWED_HOSTS — comma-separated host allowlists for provider endpoints. Empty = block-only-private-hosts.
VECTOR_STORE_ALLOWED_HOSTS — same shape, governs KB export targets (Chroma, Qdrant, Weaviate, etc.).
S3_ALLOWED_HOSTS / S3_ALLOW_LOOPBACK=1 — S3-style endpoint allowlist + opt-in loopback for self-hosted MinIO.
WEBHOOK_ALLOWED_HOSTS — comma-separated allowlist for outbound webhook delivery URLs (advisory layer on top of the SSRF guard).
PUSH_ALLOWED_HOSTS — overrides the default push-service allowlist (FCM, Mozilla, Apple, Microsoft). Set when self-hosting a push service.
OLLAMA_HOST_FALLBACKS — comma-separated additional candidates tried when the configured Ollama URL fails. See CLAUDE.md for the full network-mode story.
Background workers
CLOUD_PUSH_ENABLED=1 — opt in to per-source Dropbox longpoll workers. Each active dropbox watcher seeds a cursor, longpolls notify.dropboxapi.com, and triggers an immediate poll on change. Off by default; the standard 30-second poll keeps working.
CLOUD_WATCHER_ENABLED=1 — enable the cloud-folder polling worker (Dropbox / Drive / OneDrive). Default 1.
WEBHOOK_DELIVERY_RETENTION_DAYS=30 — how long delivered or exhausted WebhookDelivery rows are kept before pruning. Pending rows older than 2 × this value get flipped to exhausted with body cleared. Defaults to 30, capped at 3650.
RETAIN_JOBS_DAYS — daily sweeper deletes OcrJob rows older than this. Unset or 0 disables.
LOCAL_WATCH_ROOT — root for local-provider watchers. Defaults to <DATABASE_URL_DIR>/local-watch. Each user is sandboxed under <root>/<userId>/.
WATCH_FOLDER / WATCH_FOLDER_USER_ID / WATCH_FOLDER_INTERVAL_SECONDS / WATCH_FOLDER_MIN_AGE_MS — legacy filesystem watcher (single shared folder). New deployments should use the per-user watcher UI instead.
Search / KB
SEARCH_OFFLOADED_SCAN_LIMIT — cap on jobs scanned by instr() text search. Default 5000.
KB_EXPORT_ENABLED=1 — gate the /api/v1/export/kb endpoint and extracto kb export CLI.
Metrics / push
METRICS_TOKEN — bearer token for /api/v1/metrics when called outside an auth'd session (e.g. Prometheus scraper).
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT — Web Push VAPID keypair. Auto-generated to <DATABASE_URL_DIR>/.vapid_keys.json if not set.
SMTP (forgot-password, email change confirmation)
SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASSWORD / SMTP_FROM — SMTP connection. Forgot-password and email-change features are silently skipped when SMTP is unset.
OAuth app credentials (recommended self-host path; ENV-first)
DROPBOX_CLIENT_ID / DROPBOX_CLIENT_SECRET — Dropbox OAuth app, redirect URI ${EXTRACTO_URL}/api/integrations/dropbox/callback.
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET — Google OAuth app, redirect URI ${EXTRACTO_URL}/api/integrations/google_drive/callback.
ONEDRIVE_CLIENT_ID / ONEDRIVE_CLIENT_SECRET — Microsoft OAuth app, redirect URI ${EXTRACTO_URL}/api/integrations/onedrive/callback.
When ENV creds are set, the integrations panel hides the per-user "Add OAuth app" form by default, exposing only a primary Connect button.
Lifecycle commands (no token required)
extracto on
extracto off
extracto status
extracto logs
extracto uninstall
Knowledge-base export (Chroma / Qdrant / Weaviate)
When the user wants to push extracted text into a vector store for retrieval, use extracto kb. Two sub-commands:
extracto kb test-connection \
--store chroma|qdrant|weaviate \
--store-url URL \
[--store-key KEY]
extracto kb export <job-id> \
--collection NAME \
--store-url URL \
--embed-model MODEL \
[--store chroma|qdrant|weaviate] \
[--store-key KEY] \
[--strategy paragraph|sentence|hierarchical|semantic|fixed]
Always run kb test-connection before kb export. The export pipeline chunks, embeds, then upserts: if the store is unreachable or the api-key is wrong, you only find out after the embedding cost. The test-connection probe targets an auth-required endpoint per store (Chroma /api/v1/collections, Qdrant /collections, Weaviate /v1/schema) so a 401 here means the upsert later will also 401. No data is written; safe to call repeatedly.
KB export needs KB_EXPORT_ENABLED=1 on the server. If kb export returns 503, the server has the feature off.
Error handling
✖ no API token found — set EXTRACTO_TOKEN or create ~/.extracto/config.
✖ file not found: X — local file check before any HTTP call.
✖ unsupported file type: X — only pdf|png|jpg|jpeg|webp are accepted.
curl: (7) Failed to connect — the container isn't running. Run extracto on first.
HTTP 401 — the token is wrong or revoked. Make a new one.
HTTP 403 Missing required scope: X — the API key lacks the scope for that endpoint. Recreate with the right scopes via api-key create.
Environment summary
| Variable | Default | Used by |
|---|
EXTRACTO_URL | http://127.0.0.1:3000 | All /api/* calls |
EXTRACTO_TOKEN | (read from ~/.extracto/config) | /api/v1/* calls |
EXTRACTO_PROJECT_DIR | repo root | compose commands |
EXTRACTO_LOG_DIR | ~/.local/state/extracto/logs | run-step output |
When NOT to use this skill
- For changing API provider settings or API keys — use the web UI for those (the CLI deliberately doesn't write secrets).
- For real-time job streaming — the
wait command polls every 2s, not via SSE. For sub-second latency, hit /api/jobs/<id>/stream directly.
- For creating users —
extracto api-key create <email> requires the user to already exist (signup happens through the web UI).