Docs drift. You ship a new endpoint, your OpenAPI spec updates in the repo, but the hosted docs still show last week's schema. Customers file "your docs are wrong" tickets. You swear you'll set up a cron. You don't.
Outworx Docs solves this two ways. The easiest is spec URL auto-sync — point us at a URL and we re-fetch on a schedule. The more robust way is a GitHub Action that pushes the spec to us on every merge to main, so docs update the instant your API does.
This guide covers both, with the GitHub Action setup at the center.
Option 1 — Spec URL auto-sync (the 30-second option)
If your API serves the OpenAPI JSON at a public URL (e.g. https://api.yourcompany.com/openapi.json), you don't need GitHub Actions at all.
- Open your project's API Spec tab on Outworx.
- Click New version → From URL.
- Paste the URL and pick a sync interval: hourly, daily, or weekly.
- Done.
Every interval tick, Outworx fetches the URL, parses it, and bumps the version. Your hosted docs reflect the latest spec without any CI pipeline.
When this isn't enough: if your OpenAPI spec lives only in a git repo (never served publicly), or you want zero-lag updates the moment a PR merges, use the GitHub Action approach below.
Option 2 — GitHub Actions (zero-lag, push-based)
The flow:
- You merge a PR to
mainthat changes your OpenAPI spec - A GitHub Action fires on push
- It POSTs the spec file to Outworx's upload API
- Your hosted docs update within seconds
Step 1: Get your project upload token
Open your Outworx Docs dashboard, click into your project, and go to the Settings tab. Scroll down past Project Details to the API access — spec upload card and click Generate upload token. You'll get a token that looks like otwx_... and is scoped to spec upload on this one project (nothing else — it can't read chats, analytics, or MCP). The same card shows pre-filled curl and GitHub Actions snippets with your token already dropped in, so you can copy-paste straight into your workflow file.
Copy the token. If you miss it, you can click the eye icon to reveal it again later, or rotate to get a new one.
Add it to your GitHub repo as a secret: Settings → Secrets and variables → Actions → New repository secret.
- Name:
OUTWORX_TOKEN - Value: your
otwx_...token
Step 2: Drop in the workflow
The fastest path is the official abdallaemadeldin/upload-spec-action@v1. Three lines of YAML, no curl to maintain, no exit-code translation, no "did I quote the JSON right?" debugging at 2am.
Create .github/workflows/sync-docs.yml:
name: Sync API docs
on:
push:
branches: [main]
paths:
- "openapi.yaml"
- "openapi.json"
# Uncomment if your spec is generated from source — pair this
# with the "Regenerate spec" step below so a code change actually
# produces a fresh openapi.yaml before the upload runs.
# - "src/**/*.ts"
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Uncomment if your API auto-generates its spec from code.
# - name: Regenerate spec
# run: npm ci && npm run generate:openapi
- uses: abdallaemadeldin/upload-spec-action@v1
with:
token: ${{ secrets.OUTWORX_TOKEN }}
project: <YOUR_SLUG>
spec: ./openapi.yamlReplace <YOUR_SLUG> with your project slug (the subdomain before .docs.outworx.io). Without a version input, the push updates your project's current default version in place — the behavior most teams want for a main-branch sync.
Where does
${{ secrets.OUTWORX_TOKEN }}come from? It's the repo secret you added in Step 1. GitHub substitutes it at run time so your actual token is never in the YAML (or your git history). The action's exit codes mirror the CLI: 1 for user errors (won't retry on its own), 2 for transient API errors (safe to re-run).
Step 3: Optional — version your docs by git tag
If you want each git tag to become a new docs version rather than overwriting the default, pass the version input:
- uses: abdallaemadeldin/upload-spec-action@v1
with:
token: ${{ secrets.OUTWORX_TOKEN }}
project: <YOUR_SLUG>
spec: ./openapi.yaml
version: ${{ github.ref_name }} # e.g. v1.2.3 → version label "v1.2.3"Step 4: Optional — preview deploys for every PR
Pair the action with pull_request events to publish a per-PR preview at /preview/<pr-number>:
on:
pull_request:
paths: ["openapi.yaml"]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: abdallaemadeldin/upload-spec-action@v1
with:
token: ${{ secrets.OUTWORX_TOKEN }}
project: <YOUR_SLUG>
spec: ./openapi.yaml
preview: true
pr: ${{ github.event.pull_request.number }}
commit: ${{ github.event.pull_request.head.sha }}
branch: ${{ github.event.pull_request.head.ref }}Preview pushes are filtered out of your public docs picker, never become the default version, and auto-prune 14 days after the PR closes. They also require a personal access token (otwx_pat_*) — per-project upload tokens don't carry the version:write scope previews need. Mint one in Settings → API tokens.
Prefer raw curl?
You don't need the action. The same upload endpoint is reachable via curl:
- name: Upload to Outworx Docs
env:
OUTWORX_TOKEN: ${{ secrets.OUTWORX_TOKEN }}
run: |
curl -sS --fail-with-body -X POST \
"https://docs.outworx.io/api/projects/<YOUR_SLUG>/upload" \
-H "Authorization: Bearer $OUTWORX_TOKEN" \
-H "Content-Type: application/yaml" \
--data-binary "@openapi.yaml"Trigger on tag pushes instead:
on:
push:
tags:
- "v*.*.*"Now every release tag becomes a permanent docs version visitors can switch between. The latest tag becomes the default.
Verifying it works
After your first merge, check the Actions tab on GitHub — you should see "Sync API docs" green. On the Outworx side, go to API Spec → you should see a new version with Last synced: a few seconds ago.
If the action fails, the response body is JSON with a machine-readable code and a human error:
MISSING_TOKEN/INVALID_TOKEN— theAuthorizationheader is absent or doesn't match. Re-copy from the Settings tab.UPGRADE_REQUIRED— you're on a plan that caps the number of active versions (Free: 1, Pro: 3). The body includes your plan + limit + an upgrade URL. Either drop the?version=query param to update your default version in place, or upgrade.SPEC_TOO_LARGE— bigger than 50MB (or whatever your hosting platform caps request bodies at — Vercel serverless is 4.5MB on Hobby/Pro). For truly huge specs, point a spec URL at the file and let auto-sync fetch it server-side, which has no size cap.UNKNOWN_FORMAT— the body wasn't recognized as OpenAPI/Swagger/GraphQL. Often a curl pathing issue; check--data-binary '@path/to/openapi.yaml'.PARSE_FAILED— the spec was malformed. The message explains which line / field.LABEL_CONFLICT— race between two pushes at the same label. Retry.
Why this pattern works so well
API specs are the source of truth for your contract with developers. When docs lag behind the spec, you pay in confusion, support tickets, and lost trust. Push-based sync flips the dynamic — you can't ship an API change that doesn't update the docs, because they're literally the same commit.
Combine this with Outworx's MCP server and your users' AI assistants will always have the latest spec too. Ship once, everything else catches up automatically.
Related guides
Outworx REST API — Manage Projects from CI
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.
Outworx CLI — push specs from anywhere
Publish OpenAPI / Swagger / GraphQL specs to Outworx Docs from your laptop or any CI provider. One command, zero install graph, scriptable exit codes.
PR preview deploys — every pull request gets its own docs URL
Push specs from CI with `--preview` and Outworx spins up a throwaway version with a banner showing PR / branch / commit. 14-day rolling TTL, hidden from public version listings, three lines of GitHub Action.
Ship your API docs in under a minute.
Upload your OpenAPI, Swagger, or GraphQL spec and get beautiful hosted docs with AI chat and a per-project MCP server — free forever for 2 projects.