RunCloud API Documentation Audit
The docs are mid-migration and it shows everywhere: a V2 tree (Basic Auth, key+secret) is still the default and holds the entire endpoint reference, while an "in progress" V3 tree (Bearer tokens) has an introduction but no reference — and the guides, error tables, rate limits, and credential instructions disagree with each other across all of them.
1. Two incompatible auth models presented as one, with a JWT labeled "your API key" (critical)
Location: /docs/using-the-runcloud-api (cross-referenced with /docs/api and /docs/api/v3/)
Problem: The "Using the RunCloud API" page mixes both auth schemes in consecutive sentences: "Once you have your API key and secret, you can start using API" (the V2 Basic-Auth model) followed immediately by "RunCloud uses Bearer Token... you need to use your API Key while making the request," then a curl example with -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" and the instruction to "replace the eyJ0eXAi... with your API key." That value is a JWT header ({"typ":"JWT","alg":"HS256"}), not an API key — and V2 (default reference) authenticates with -u YOUR_API_KEY:YOUR_API_SECRET (Basic Auth), while V3 authenticates with a single Bearer token. The page never tells the reader which model they are on.
Consequence: A developer (or an AI agent) cannot determine whether to send a key+secret via Basic Auth or a single token via Bearer. Following the "key and secret" sentence with the Bearer example produces 401s. An agent parsing "your API key" will paste an API key into a header that expects a JWT/token. This is the single most confusing thing in the docs.
The fix: Split the guide by version. On V3 pages, say "API Token" consistently and show only Authorization: Bearer <token>. On V2 pages, say "API Key and API Secret" and show only Basic Auth. Never call a Bearer token an "API key," and never present both models on the same page without labeling which version each belongs to.
2. Rate limit is documented three different ways across three pages (critical)
Location: /docs/api (V2 intro), /docs/api/v3/ (V3 intro), /pricing
Problem: The V2 API intro states a flat "The rate limit is 60 requests/minute." The pricing page sells Business at "120 requests per minute" and Enterprise at "300 requests per minute," plus monthly caps ("10,000 requests per month," up to "400,000 requests per month") that appear nowhere in the API reference. The V3 intro removes the number entirely and only says "You can get the information about your current rate limit in Workspace > Settings > API Keys."
Consequence: A developer building backoff/throttling logic against the documented "60/minute" will under-utilize a Business plan (real limit 120) or get throttled reasoning about a limit that doesn't match their plan. The undocumented monthly cap means an integration can pass every per-minute check and still get cut off at 10,000/month with no warning in the API docs. Agents that read the API reference will never learn the monthly ceiling exists.
The fix: Publish one canonical rate-limit table in the API reference, keyed by plan, covering both per-minute and per-month limits, and reference the X-RateLimit-Limit / X-RateLimit-Remaining headers for runtime values. Delete the stale flat "60 requests/minute" or mark it as the free/default tier explicitly. Keep pricing and the API docs pointed at the same source.
3. The "current" V3 API has no endpoint reference — the entire reference is V2 (critical)
Location: /docs/api/v3/ vs /docs/api/servers, /docs/api/web-application, /docs/using-the-runcloud-api
Problem: V3 is presented as the version to use ("please refer to our updated documentation for V3"), but every endpoint reference page is V2: base URL https://manage.runcloud.io/api/v2/... and Basic Auth -u YOUR_API_KEY:YOUR_API_SECRET. The V3 tree contains only an introduction and a Cloudflare integration page; there is no V3 servers, webapps, or any other endpoint documentation. The gap even shows up inside a single guide: /docs/using-the-runcloud-api walks through /servers/{id}/health/latest, but that endpoint appears nowhere in the V2 endpoint reference (/docs/api/servers) — a guide references an endpoint the reference doesn't document.
Consequence: A developer told to use V3 has an auth scheme (Bearer) but no endpoints to call it against — every concrete request example they can find is V2/Basic-Auth. They must either reverse-engineer V3 endpoints or fall back to V2 while V3 is "current," with no migration guide bridging the two. Agents indexing the reference will emit V2 Basic-Auth calls even for users on V3.
The fix: Either finish the V3 endpoint reference before steering users to V3, or explicitly label V2 as the supported/complete reference and V3 as preview-only with a dated availability note. Add a V2→V3 migration page mapping base URL, auth header, and any changed request/response shapes, and reconcile guide-only endpoints (like /servers/{id}/health/latest) with the reference.
4. V3 error table says 401 means "wrong API Key and/or API Secret" — but V3 has no key or secret (significant)
Location: /docs/api/v3/ (HTTP Response table)
Problem: The V3 page authenticates with a Bearer token ("supply API Token as Bearer Authentication"), yet its HTTP Response table — copied verbatim from V2 — defines 401 | Authentication failed. This is due to wrong API Key and/or API Secret. V3 has no API Secret at all.
Consequence: A developer debugging a V3 401 goes hunting for a "key and secret" pair that doesn't exist in the V3 model, wasting time before realizing the real cause is a bad/expired Bearer token. Agents building error handlers will surface a message referencing credentials the API doesn't use.
The fix: Rewrite the V3 401 row to reference the token model: "Authentication failed — missing, invalid, or expired API Token." Audit the rest of the V3 table (it also collapses 500,502,503,504 into 50x, confirming the table was hand-edited from the V2 copy).
5. IP-whitelist rejection returns 401, contradicting the definition of 401 (significant)
Location: /docs/generating-api-keys-enabling-api-access ("Restricting API Access") vs /docs/api HTTP Response table
Problem: The IP-restriction docs state: "if you use an IP address that is not present in this whitelist, RunCloud will return an HTTP 401 error." But 401 is defined in the API reference strictly as "Authentication failed. This is due to wrong API Key and/or API Secret." A request with perfectly valid credentials from a non-whitelisted IP is an authorization/forbidden condition — conceptually 403, which the table already reserves for "Forbidden request."
Consequence: A developer whose credentials are correct but whose server IP isn't whitelisted receives a 401 that tells them their key is wrong. They rotate/regenerate keys chasing a phantom credential problem while the real fix is adding their IP. Agents cannot distinguish "bad token" from "blocked IP" because both surface as the same code and message.
The fix: Return 403 for whitelist rejections (with a distinct error body like "IP not permitted"), or if 401 must stay, document the IP-block case explicitly in the 401 row so it's distinguishable from a credential failure.
6. Getting Started says you only need "a RunCloud account"; the API is actually Business/Enterprise-only (significant)
Location: /docs/api ("Get Started") vs /docs/generating-api-keys-enabling-api-access and /pricing
Problem: The V2 API intro says: "To get started with API, you need a RunCloud account. If you don't have one, you can create a new account here." But the API-access page states plainly: "access to the RunCloud API is an exclusive feature available on our Business and Enterprise subscriptions... you will need to upgrade your account." Pricing confirms the API ships only with Business ($49/mo) and Enterprise ($399/mo).
Consequence: A developer on the free tier or Essentials/Professional plan follows the getting-started flow, creates an account, and then can't find or generate API credentials — the docs sent them down a path their plan doesn't support, with the paywall disclosed only on a different page.
The fix: Put the plan requirement in the first paragraph of the API intro: "The API is available on Business and Enterprise plans." Link directly to pricing from the Get Started section.
7. Credential location and terminology are inconsistent within and across pages (significant)
Location: /docs/api, /docs/api/v3/, /docs/generating-api-keys-enabling-api-access
Problem: The V2 intro tells you to get credentials from "the profile menu" (Get Started) and then from "Settings > API Key" (Authentication) — two different places on one page. The V3 intro says "Workspace > Settings > API Management" (Authentication) and "Workspace > Settings > API Keys" (Rate Limit) — again two different places on one page. Terminology also drifts: "API Key + API Secret" (V2), "API Token" (V3 auth), "API Key" (using-the-API guide), and "Generate New Key" (key-generation page).
Consequence: Users click through menus that don't match the instructions, and can't tell whether "API Key," "API Token," "Key," and "Secret" are the same thing or different artifacts. This compounds the auth-model confusion in Finding 1.
The fix: Pick one navigation path per version and use it verbatim everywhere ("Workspace > Settings > API Management"). Standardize on one noun per version — "API Token" for V3, "API Key + Secret" for V2 — and stop using them interchangeably.
8. No llms-full.txt, no machine-readable spec, and no dedicated authentication page (significant)
Location: site root and /docs (probes: /llms-full.txt → 404, /docs/llms-full.txt → 404, /docs/api/authentication → 404)
Problem: There is no llms-full.txt at either the site root or under /docs (both 404), no OpenAPI/Swagger spec referenced anywhere in the reference, and /docs/api/authentication 404s even though guides point readers to "enabling API Access." Every endpoint is prose + curl, with no machine-discoverable schema.
Consequence: AI coding agents (a primary consumer of these docs) can't efficiently index the docs or programmatically enumerate endpoints, parameters, or auth. Given the auth and rate-limit contradictions above, agents have no authoritative structured source to fall back on and will silently emit wrong requests. Humans hit a 404 following the docs' own "authentication" references.
The fix: Publish an llms.txt/llms-full.txt and an OpenAPI 3 spec for the current API version, and add a single canonical /docs/api/v3/authentication page (and V2 equivalent) that the guides link to. A spec would also let you generate the reference and eliminate the copy-paste drift behind Findings 2, 4, and 9.
9. Reference examples use EOL PHP and Ubuntu versions that the platform no longer supports (significant)
Location: /docs/api/servers and /docs/api/web-application vs /docs/beginner-guide-to-runcloud
Problem: The server/webapp reference examples show phpCLIVersion: "php73rc", an available-PHP list of ["php55rc","php56rc","php70rc","php71rc","php72rc","php73rc"], phpVersion: "php73rc" in the create-webapp body, Ubuntu osVersion: "xenial" (16.04), agentVersion: "2.1.7-1+ubuntu16.04+2", and created_at dates from 2019. The beginner guide states the supported OS is "a 64-bit Ubuntu LTS release (20.04, 22.04, or 24.04)" and the UI only offers "Ubuntu 22 or Ubuntu 24."
Consequence: An agent or developer copying the create-webapp example sends "phpVersion": "php73rc" — an EOL version that likely no longer exists on supported (Ubuntu 20/22/24) servers — and the call fails or produces an unsupported app. The reference actively teaches values the platform has moved off of.
The fix: Refresh all reference examples to currently supported PHP and OS versions (Ubuntu 22.04/24.04, current PHP releases), regenerate response fixtures, and add a note that the PHP-version list is server-dependent and must be fetched from /servers/{id}/php/version rather than copied from the example.
10. The disableFunctions example contains a copy-paste-breaking typo in a security-hardening setting (significant)
Location: /docs/api/web-application (Create web application, disableFunctions default)
Problem: The default disableFunctions string includes ...posix_getpid,**posix,_getppid**,posix_getpwuid,... — a stray comma splits posix_getppid into two invalid tokens, posix and _getppid.
Consequence: Integrators (and agents) copy this default verbatim into their create-webapp calls. Because posix and _getppid aren't real PHP functions, posix_getppid is silently left enabled in what is meant to be a security-hardening deny-list, while two bogus entries are added. This is a security-relevant misconfiguration — a dangerous function stays callable — that is easy to miss and ships in every copied example.
The fix: Correct the string to posix_getppid and validate the full disable-functions list against real PHP function names before publishing.
11. Pagination examples contradict each other and the documented maximum, in both versions (minor)
Location: /docs/api/servers ("List servers" and "List shared servers") vs /docs/api and /docs/api/v3/ (both: "maximum perPage query is 40")
Problem: The "List servers" response shows "per_page": 15; "List shared servers" shows "per_page": 16; both the V2 intro and the V3 intro document "the maximum perPage query is 40." Three different numbers, and the query parameter is called perPage in prose but appears as per_page in responses — and the max-40 rule is repeated across both API versions.
Consequence: A developer can't tell the default page size (15? 16?), whether it's configurable, or whether the parameter is perPage or per_page. Agents may send the wrong casing and get ignored/defaulted silently.
The fix: Document the default page size and the max (40) explicitly, use one consistent parameter name (state the request-param casing vs the response-field casing), and make the example responses use a consistent value across V2 and V3.
12. Example response embeds a live-looking installer token and models curl | bash (minor)
Location: /docs/api/servers (Installation Script response)
Problem: The documented installation-script response contains a full installer URL with an embedded token, piped straight into a shell: curl --silent --location https://manage.runcloud.io/scripts/installer/cix510RbawOVExZANlzgUqcU9E1561024841dzRy0jIa3F0d7ibg4J94xSl1yB0dDBy0IJrIYMkjX2lNfu8JOY8gWbGB4tMWUwsw/... | bash -.
Consequence: If this is a real (or once-real) provisioning token, publishing it in docs is a hygiene/security problem; if it's fabricated, it's indistinguishable from a real one, so a reader can't tell whether to treat installer tokens as secrets. Either way it normalizes pasting a curl … | bash with an embedded credential into the terminal — a pattern that runs an unreviewed remote script as root.
The fix: Replace the token with an obvious placeholder (.../installer/YOUR_INSTALL_TOKEN/...) and add a note that installation tokens are sensitive and should not be shared or committed. Consider showing a download-then-inspect flow rather than a bare pipe-to-shell.
13. Undefined stack values, and provider naming that no example resolves (minor)
Location: /docs/api/web-application (stack parameter), /docs/api/servers (provider examples), /docs/beginner-guide-to-runcloud
Problem: The stack values "hybrid", "nativenginx", and "customnginx" are enumerated only inside this one parameter description and defined nowhere else — there's no page explaining what each stack is or when to choose it. Separately, the beginner guide lists supported providers as "Akamai (formerly known as Linode)," Vultr, UpCloud, etc., but the API reference's only concrete provider values in the "List servers" response are "Localhost" and "VMWare Fusion 10" — dev/test strings, not any real supported cloud provider. So a developer trying to match a real provider (Linode/Akamai) has no example of the literal string the API expects, and the Linode→Akamai rename adds ambiguity about which name is current.
Consequence: A developer picking a stack value is guessing at the difference between hybrid/native/custom nginx with no reference. And an integrator setting or matching the provider field can't tell from the docs which literal string a real provider maps to — nor whether the rename means "Linode" or "Akamai."
The fix: Add a short "Web application stacks" concept page defining each stack value and link the parameter to it. Show the actual provider literals the API accepts (including how Linode/Akamai is represented) in the reference examples, and standardize provider naming across the guides and the API.
14. Page dates are incoherent with the version they describe (minor)
Location: /docs/using-the-runcloud-api, /docs/generating-api-keys-enabling-api-access, /docs/api/v3/
Problem: /docs/using-the-runcloud-api is author-dated 2023-05-23, yet it already points at the V3 base URL https://manage.runcloud.io/api/v3/ and the Bearer-token model — a versioning model that the V3 intro itself only marks "still on progress" and last modified 2024-10-24. Meanwhile /docs/generating-api-keys-enabling-api-access carries an author date of 2025-12-01. The dates don't line up with the content: an older page describes a newer, still-incomplete auth model.
Consequence: Readers use the visible dates to judge which page to trust, but here the dates are misleading — the "2023" guide describes the not-yet-finished V3 flow, so a developer can't use freshness to resolve the auth-model contradictions in Findings 1 and 4. It reinforces that pages were edited piecemeal without re-dating.
The fix: Re-stamp pages when their content is changed, and add a visible "applies to API version" badge per page so freshness and version are both unambiguous rather than inferred from a stale author date.
What they do well
- The V2 HTTP response table is genuinely useful — it enumerates 200/401/403/422/404/429/5xx with plain-language meanings and even flags which codes come from the client vs the server.
- Real, runnable curl examples accompany endpoints (base URL, auth flag, headers, and JSON bodies), which is more than many platform docs provide.
- The
/ping→pongsmoke test is a good onboarding touch for verifying auth before building anything real.
Top 3 recommendations
- Fix the auth story end-to-end (Findings 1, 4, 7). Pick one model per version, call it by one name, correct the V3 401 message, and stop presenting Basic Auth and Bearer on the same page. This is the highest-impact fix.
- Make one canonical rate-limit + plan-requirement source (Findings 2, 6) shared by the API docs and pricing, covering per-minute and per-month limits, and put the Business/Enterprise gate in the first line of the API intro.
- Ship machine-readable docs and finish V3 (Findings 3, 8, 9). Publish an OpenAPI spec +
llms.txt/llms-full.txt, build the V3 endpoint reference (or clearly mark V2 as the complete one), and regenerate examples off current PHP/OS versions so agents stop emitting EOL values.