# Outworx Docs — Full Content Dump > Long-form companion to /llms.txt. Every guide and changelog > entry from docs.outworx.io, inlined as markdown so an AI agent > has one fetch for the project's full content context. Site: https://docs.outworx.io Generated at: 2026-09-30T10:58:20.899Z Outworx Docs is a hosted API documentation platform. Upload OpenAPI 3.x, Swagger 2.0, or GraphQL SDL specs and get an interactive docs site, a free hosted mock server, AI chat, an MCP server per project, a webhooks playground, drop-in SDK generation (TypeScript + Python), spec diff with a GitHub PR action, per-version uptime monitoring with public status pages, Postman-style saved requests + environments, an embeddable Try-It widget, and a Mintlify-style docs page builder (custom header + per-endpoint markdown + white-label option) for paid plans. Free for 2 projects, Pro $9/mo, Business $19/mo — flat pricing, no per-seat. --- # Guides Each guide is reproduced in full below. Canonical URLs given for deep links. Sorted newest-first. --- ## Outworx REST API — Manage Projects from CI URL: https://docs.outworx.io/guides/rest-api Category: Integration Published: 2026-05-09 Reading time: 7 min > Drive every Outworx Docs action from a script: create projects, push spec versions, promote a default. Personal access tokens, copy-paste curl, and the full OpenAPI spec. Every action you can take in the Outworx dashboard — creating projects, pushing new spec versions, promoting a default, soft-deactivating an old version — is also reachable from a `curl` one-liner. This guide walks through authentication, the request/response shape, the flows that matter most for CI pipelines, and where to find the OpenAPI spec. ## Why use the API The dashboard is for humans. The REST API is for everything else: pre-merge spec validation in CI, scripted onboarding for a new microservice, internal tooling that mirrors a project tree from GitHub into Outworx, or anything else where clicking through a UI would be a chore. Two surfaces cover roughly the same ground but with different boundaries: - **Per-project upload tokens** (the older `otwx_` token from Settings → API access) — scoped to spec upload on one project. Best when CI only needs to push a spec. - **Personal access tokens** (this guide) — account-scoped, do anything you can do via the dashboard, work across all your projects with one token. Best for everything else. ## Generate a personal access token 1. Open [Settings](https://docs.outworx.io/settings) in the dashboard. 2. Find the **API tokens** card. 3. Click **New token**, name it (`ci-pipeline`, `local-dev`, etc.), optionally set an expiry. 4. Copy the plaintext that appears — *we don't store it and we won't show it again*. The dashboard list afterwards only shows the prefix (`otwx_pat_…`) so you can recognise tokens at a glance, plus revoke them if a token leaks. Tokens act as the user who created them. A token belonging to a Pro-plan user has Pro limits; downgrade the user and the token's allowance drops with it. ## The shape of every request ```bash curl -H "Authorization: Bearer otwx_pat_..." \ https://docs.outworx.io/api/v1/projects ``` That's it. JSON in, JSON out. There's no separate API host — `docs.outworx.io/api/v1/*` is the canonical surface so you don't need a second DNS record. ### Success envelopes Single-resource endpoints return the resource directly: ```json { "id": "f1b7697f-...", "name": "Payments API", "slug": "payments-api", "...": "..." } ``` List endpoints wrap data in `{ data, pagination }`: ```json { "data": [ /* projects */ ], "pagination": { "total": 47, "limit": 50, "offset": 0 } } ``` ### Error envelope Every error response uses the same shape: ```json { "error": { "code": "VALIDATION_ERROR", "message": "`name` is required.", "details": { "...": "..." } } } ``` `code` is the stable machine-readable identifier — pattern-match on it. `message` is human-readable and may change. The full list of codes (`MISSING_TOKEN`, `INVALID_TOKEN`, `TOKEN_EXPIRED`, `NOT_FOUND`, `VALIDATION_ERROR`, `CONFLICT`, `PAYLOAD_TOO_LARGE`, `RATE_LIMITED`, `PLAN_LIMIT`, `UNSUPPORTED_FORMAT`) lives in the OpenAPI spec. We don't differentiate "doesn't exist" from "not yours" — both return `404 NOT_FOUND`. A 403 there would leak slug existence to anyone with a token. ## Common flows ### Create a project + push a spec from a fresh CI job ```bash # 1. Create the project. Slug auto-generates from name when omitted. curl -X POST -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Payments API","description":"Public Payments API"}' \ https://docs.outworx.io/api/v1/projects # 2. Push the OpenAPI spec as the first version. Body IS the spec — # NOT a JSON envelope. The first active version is auto-marked # as the default. curl -X POST -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H "Content-Type: application/json" \ --data-binary '@openapi.json' \ 'https://docs.outworx.io/api/v1/projects/payments-api/versions?label=v1' ``` That's the whole bootstrap. Two calls, no dashboard. ### Promote a new version on every release The single-default invariant is enforced server-side: setting `is_default: true` on a non-default version automatically demotes whatever was the default before. You don't need two PATCH calls. ```bash # Push v2 alongside v1, leaving v1 as the default visitors see. curl -X POST -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H "Content-Type: application/json" \ --data-binary '@openapi.json' \ 'https://docs.outworx.io/api/v1/projects/payments-api/versions?label=v2' # Run your QA. When ready, flip the default in one call. curl -X PATCH -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"is_default":true}' \ https://docs.outworx.io/api/v1/projects/payments-api/versions/v2 ``` If you instead try to **demote the default directly** (`is_default: false` on the current default), the API rejects it with `400 VALIDATION_ERROR` — the project would have no default, and the docs page would 404 for visitors. Promote a replacement; we'll demote the old one for you. `DELETE` on the default is also blocked with `409 CONFLICT` for the same reason. Promote first, delete second. ### Soft-deactivate a version without deleting it `is_active: false` hides the version from the docs page but keeps the row, the spec content, and the analytics intact. Useful for pulling back a release that broke something without losing the audit trail. ```bash curl -X PATCH -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"is_active":false}' \ https://docs.outworx.io/api/v1/projects/payments-api/versions/v2 ``` Reactivate by sending `{"is_active":true}`. ### PR preview deploys Push a spec with `?preview=true` and the new version is treated as PR-scoped: filtered out of the public version picker, kept out of the sitemap, marked `noindex`, and given a 14-day TTL. Reviewers click a link from the PR comment and see live docs for the proposed change without it bleeding into your release version history. Previews are **Pro+** (free plans get `402 PLAN_LIMIT`) with their own per-plan cap (`maxPreviewsPerProject`: 5 on Pro, 20 on Business). They don't count against your regular `maxVersions` cap. ```bash # CI on PR open / push: curl -X POST -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H "Content-Type: application/json" \ --data-binary '@openapi.json' \ "https://docs.outworx.io/api/v1/projects/$SLUG/versions?label=pr-${PR_NUMBER}&preview=true&pr=${PR_NUMBER}&commit=${COMMIT_SHA}&branch=${BRANCH_NAME}" ``` The response includes the version row with `is_preview: true` and a `preview` object holding the PR number, commit SHA, branch, and `expires_at`. The preview is reachable at: ``` https://{slug}.docs.outworx.io/preview/{PR_NUMBER}/ ``` (or your custom domain). Re-pushing the same label as new commits land on the PR **updates** the existing row (200 OK) instead of returning 409 — designed for CI to push on every commit. The TTL rolls forward each time, so an active PR keeps its preview indefinitely. #### GitHub Action ```yaml name: Outworx — PR preview docs on: pull_request: branches: [main] types: [opened, synchronize, reopened] pull_request_target: types: [closed] jobs: push-preview: if: github.event.action != 'closed' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Push preview spec env: OUTWORX_TOKEN: ${{ secrets.OUTWORX_TOKEN }} SLUG: my-project PR: ${{ github.event.pull_request.number }} SHA: ${{ github.event.pull_request.head.sha }} BRANCH: ${{ github.event.pull_request.head.ref }} run: | RESP=$(curl -sS --fail-with-body -X POST \ -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H 'Content-Type: application/json' \ --data-binary '@openapi.json' \ "https://docs.outworx.io/api/v1/projects/$SLUG/versions?label=pr-$PR&preview=true&pr=$PR&commit=$SHA&branch=$BRANCH") echo "PREVIEW_URL=https://$SLUG.docs.outworx.io/preview/$PR/" >> $GITHUB_ENV - name: Sticky PR comment uses: marocchino/sticky-pull-request-comment@v2 with: header: outworx-preview message: | 📘 **Docs preview**: ${{ env.PREVIEW_URL }} Updated on every push. Auto-cleaned 14 days after the PR closes. cleanup-preview: if: github.event.action == 'closed' runs-on: ubuntu-latest steps: - name: Delete preview version env: OUTWORX_TOKEN: ${{ secrets.OUTWORX_TOKEN }} SLUG: my-project PR: ${{ github.event.pull_request.number }} run: | curl -sS -X DELETE \ -H "Authorization: Bearer $OUTWORX_TOKEN" \ "https://docs.outworx.io/api/v1/projects/$SLUG/versions/pr-$PR" || true ``` Two safety nets, in order of preference: 1. **Explicit DELETE on PR close** — the `cleanup-preview` job above. Runs as soon as the PR closes (merged or not). 2. **14-day TTL via cron** — if the close webhook never fires (CI crashed, repo was deleted, etc), the prune cron sweeps stale previews. The `expires_at` field is rolled forward on every preview re-push, so an active PR keeps its preview for as long as it sees activity. The dashboard hides preview rows from the version picker on the public docs page. To list them programmatically (for your own preview-management tooling), pass `?include_previews=true` to the GET endpoint: ```bash curl -H "Authorization: Bearer $OUTWORX_TOKEN" \ "https://docs.outworx.io/api/v1/projects/$SLUG/versions?include_previews=true" ``` ### Make a project public + assign a custom domain ```bash curl -X PATCH -H "Authorization: Bearer $OUTWORX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"is_public":true,"custom_domain":"docs.example.com"}' \ https://docs.outworx.io/api/v1/projects/payments-api ``` Custom domain setting is gated on the **Business** plan — Free and Pro tiers get `402 PLAN_LIMIT` when they try to set one. Clearing a domain (passing `null`) is allowed on every plan, mirroring what the subscription-sync would do automatically on a Business → Pro downgrade. Setting `custom_domain` over the API does **not** auto-verify — verification still goes through the dashboard's DNS check + Vercel registration step. The API exposes the field for setup parity (so you can prepare a migration without clicking) but the verification handshake stays a dashboard step. ## Plan limits Every endpoint enforces the owning user's plan limits. Hit one and you get `402 PLAN_LIMIT` with a message that points at exactly which limit you tripped: - Max projects per account - Max active versions per project (Free: 1, Pro: 3, Business: unlimited) The body looks like: ```json { "error": { "code": "PLAN_LIMIT", "message": "Your Free plan allows 1 active version per project." } } ``` ## The spec The full OpenAPI 3.1 spec is at: ``` https://docs.outworx.io/api/v1/openapi.json ``` That's the source of truth — every example here, every schema, every error code. Drop the URL into Postman / Insomnia / Stoplight to get an importable collection. Drop it into [openapi-typescript](https://github.com/openapi-ts/openapi-typescript) to generate a typed client. Or if you want a full rendered reference page, point an Outworx Docs project at it (yes, you can host the docs for the Outworx API through Outworx; we do). ## Status & rate limits Every authenticated request bumps the token's `last_used_at` so dormant tokens stand out in the dashboard list. We emit standard rate-limit headers when applicable; expect `429 RATE_LIMITED` if you blast through a burst — the body's `message` includes the retry-after hint. For automation that needs to run continuously, the comfortable budget is a few requests per second per token. If you need more, contact us before you start hammering — we'd rather raise a limit than have you discover it the hard way. ## Roles + permissions (Business) The API enforces RBAC for accounts on the **Business** plan. On Pro / Free every collaborator gets equal access (the behaviour you had before this feature shipped); a token still acts as the user it belongs to, but every endpoint treats every active collaborator as an Editor regardless of stored role. | Permission | Owner | Editor | Viewer | |---|---|---|---| | Read project + versions | ✅ | ✅ | ✅ | | Push / promote / soft-deactivate versions | ✅ | ✅ | — | | Update project settings (name, public, base URL, custom domain) | ✅ | ✅ | — | | Mock-server overrides + chaos config | ✅ | ✅ | — | | Delete a version | ✅ | ✅ | — | | Delete the project | ✅ | — | — | | Manage collaborators + roles | ✅ | — | — | | Stripe billing + plan changes | ✅ | — | — | **Project hide via per-project override.** On Business, an owner can set a per-project role override of `none` for a specific collaborator. That collaborator's PAT — and dashboard — gets `404 NOT_FOUND` for that project, even though they otherwise have account-wide access. Useful for acquisition-related projects, NDA-scoped specs, or zero-trust setups. **Insufficient role response:** ```json { "error": { "code": "FORBIDDEN", "message": "Your role (viewer) doesn't have the \"version:write\" permission. Ask the project owner to grant a higher role." } } ``` Pattern-match on `code: "FORBIDDEN"`; the `message` names the role and the missing permission. Returns `403`. **Hidden project response:** `404 NOT_FOUND`. We deliberately don't differentiate "the project doesn't exist" from "you can't see this project" — a 403 there would let any token probe slug existence by guessing. ## Audit log (Business) Every project mutation made on a Business account — whether from the dashboard or from a PAT — is recorded in the audit log with the actor's user_id, the action code, the timestamp, and a stable JSON metadata blob. API actions additionally stamp: - `metadata.source = "api"` (vs `"dashboard"`) - `metadata.token_id = ""` — the PAT row that performed the action If a token leaks, a forensic search by `metadata.token_id = ''` surfaces every action that token took, regardless of which user or project. The list of action codes is in the OpenAPI spec; common ones: `PROJECT_CREATED`, `VERSION_CREATED`, `VERSION_PROMOTED_DEFAULT`, `PREVIEW_CREATED`, `COLLABORATOR_INVITED`, `COLLABORATOR_ROLE_CHANGED`, `TOKEN_CREATED`. Free / Pro accounts skip the audit-log write entirely — the column exists, but recording it would be paying for rows nobody can read. ## What's not in v1 - **Mock server config** — overrides, chaos, auth enforcement: dashboard-only for now. - **Theme tokens** — color palette + theme-mode lock: dashboard-only. - **Collaborators** — invite / revoke: dashboard-only. - **Webhooks (inbound + outbound)** — spec drift notifications, inbox config: dashboard-only. These are queued for v2. Priority loosely tracks customer pull — if you need one of them programmatically, [tell us](mailto:info@outworx.io) and we'll likely move it up. --- ## Outworx CLI — push specs from anywhere URL: https://docs.outworx.io/guides/cli Category: Integration Published: 2026-05-09 Reading time: 6 min > Publish OpenAPI / Swagger / GraphQL specs to Outworx Docs from your laptop or any CI provider. One command, zero install graph, scriptable exit codes. If your hosted docs lag behind your spec, the difference between "annoying" and "blocked at 2am" usually comes down to whether the publish step is one command or fifteen lines of `curl`. The Outworx CLI is the one-command path. ```bash npx @outworx/cli push openapi.yaml --project payments-api ``` That's the whole thing. The CLI works on your laptop, in CI on any provider (GitHub Actions, GitLab, CircleCI, Buildkite, Jenkins…), and inside a pre-commit hook. This guide covers install, auth, configuration, and the flows worth wiring up. ## Install The CLI runs on Node.js 18.17+ and ships as an npm package. ```bash # One-shot via npx — recommended for CI, no global state npx @outworx/cli push ... # Or globally npm install -g @outworx/cli outworx push ... ``` There's no Docker image, no Python wrapper, no system service to install. It's a 30KB npm package that wraps the public REST API — so anything you'd expect from the `curl` examples is one CLI flag away, with better error messages. ## Authentication Two token shapes — pick whichever matches your scope: | Token | Prefix | Scope | Best for | | --- | --- | --- | --- | | Personal access token | `otwx_pat_*` | Account-wide; acts as the user who minted it | Multi-project CI, shared org secrets, preview deploys | | Project upload token | `otwx_*` | Single project, spec upload only | Per-repo CI on one project, contractor pushes | Mint either in [Settings](https://docs.outworx.io/settings). The dashboard shows the plaintext **once** — copy it, store it as a secret, and never check it into a repo. Pass the token via `--token`, but `OUTWORX_TOKEN` env var is preferred — flags end up in shell history and process listings, env vars don't: ```bash export OUTWORX_TOKEN=otwx_pat_... outworx push openapi.yaml --project payments-api ``` In GitHub Actions: ```yaml env: OUTWORX_TOKEN: ${{ secrets.OUTWORX_TOKEN }} ``` ## The `push` command The CLI has one command today: `push`. (More are tracked but pushing specs is the 95% workflow.) ### Push to the default version ```bash outworx push openapi.yaml --project payments-api ``` This updates your project's current default version in place. After the push, your hosted docs reflect the new spec within seconds. ### Push to a specific version ```bash outworx push openapi.yaml --project payments-api --version v2-beta ``` If the version doesn't exist, it's created. Useful for cutting a `v2` alongside an existing `v1` without retiring the old one. ### Preview push (PR-only deploys) Preview pushes publish to `/preview/`, get filtered out of the public picker, never become the default, and auto-expire 14 days after the PR closes: ```bash outworx push openapi.yaml \ --project payments-api \ --preview \ --pr "$GITHUB_PR_NUMBER" \ --commit "$GITHUB_SHA" \ --branch "$GITHUB_HEAD_REF" ``` Preview pushes require a PAT (`otwx_pat_*`) — project upload tokens don't carry the `version:write` scope. The CLI rejects with a clear error if you try. ## Configuration file For repos where every push goes to the same project, drop a config file at the repo root so you don't repeat `--project` and `--base-url` on every invocation: ```json // outworx.json { "project": "payments-api", "version": "v1", "baseUrl": "https://docs.outworx.io" } ``` `.outworxrc.json` works too if you prefer dot-files. The CLI walks up from `cwd` to find the first match, so a config in your repo root applies anywhere underneath it. Precedence (highest wins): CLI flag → env var → config file → built-in default. ## Environment variables | Var | Equivalent flag | | --- | --- | | `OUTWORX_TOKEN` | `--token` | | `OUTWORX_PROJECT` | `--project` | | `OUTWORX_BASE_URL` | `--base-url` | `OUTWORX_TOKEN` is the only one most users set. The others exist for the rare case where you don't want a config file but also don't want long flag-soup commands. ## Exit codes The CLI returns standard, scriptable exit codes: | Code | Meaning | What to do | | --- | --- | --- | | `0` | Success | Continue. | | `1` | User error — bad flag, missing file, 4xx response | Fix the input. Don't retry. | | `2` | Network or transient API error (5xx, ECONNREFUSED) | Re-run the workflow. | | `3` | Internal error | [Open an issue](https://github.com/abdallaemadeldin/upload-spec-action/issues) with `DEBUG=1` output. | Pattern-match on these in your CI's retry policy. GitHub Actions, for instance: ```yaml - uses: abdallaemadeldin/upload-spec-action@v1 with: { token: ${{ secrets.OUTWORX_TOKEN }}, project: foo, spec: ./openapi.yaml } - name: Notify slack on user error if: failure() && steps.previous.outcome == 'failure' run: ... ``` ## Full flag reference ```text outworx push [options] --project Project slug. Required (or OUTWORX_PROJECT env, or in outworx.json). --version