HelipodDocs

Connect with Helipod (OAuth)

Let people connect your app to their Helipod account. Register an app, ask for access with OAuth 2.1 and PKCE, and call the Helipod API on their behalf.

"Connect with Helipod" lets your app work in someone else's Helipod account: deploy their repositories, restart services, manage their VPS, register domains or send email, with the access they approve. Nobody copies an API key. The account owner chooses the products, read-only or full access, whether your app may spend from their wallet, and a monthly limit. They can change or remove it any time in Settings → API keys → Connected apps.

It is standard OAuth 2.1 with PKCE, the same sign-in the AI tools use. A token works on the whole public API: the Container, Database, VPS, Domain, Email, Account and AI APIs, the AI Gateway and the hosted MCP server.

Register your app

Open Developer apps

In the dashboard, open any project and go to Settings → Developer apps, then Register app. Apps belong to your account, not to the project.

Fill in the details

  • App name and Homepage (https). Names cannot contain "Helipod".
  • What it does: one sentence, shown to users once the app is verified.
  • Logo URL (https, optional, square), shown once verified.
  • Redirect URIs, one per line: https://…, http://localhost… for apps on the user's computer, or your app's own scheme (myapp://callback).
  • It has a server that can keep a secret: tick it for a web app with a backend. Leave it off for a single-page app, a mobile app or a CLI: those use PKCE only, without a secret.

Copy the credentials

You get a Client ID (hoa_…) and, for server apps, a Client secret (hel_ocs_…) shown once. Keep the secret on your server. New secret replaces it; the old one stops working at once.

An app that is not reviewed works, but users see "Not reviewed by Helipod" on the consent screen. Click Request verification: we check the homepage, the name, the redirect URIs and what the app does. Verified apps show their name, logo and description, get a verified badge, and are listed on helipod.io/apps. Changing the name, logo, homepage or redirect URIs of a verified app sends it back for review.

The flow

Authorization endpointhttps://helipod.io/oauth/authorize
Token endpointhttps://api.helipod.io/oauth/token
Revocation endpointhttps://api.helipod.io/oauth/revoke
Metadatahttps://api.helipod.io/.well-known/oauth-authorization-server
PKCErequired, S256
Access tokenhel_oat_…, 1 hour
Refresh tokenhel_ort_…, 60 days, replaced on every use

Send the user to Helipod

Create a random code_verifier, its code_challenge (BASE64URL of its SHA-256) and a state, then redirect:

https://helipod.io/oauth/authorize
  ?response_type=code
  &client_id=hoa_your_client_id
  &redirect_uri=https://example.com/auth/helipod/callback
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &scope=containers vps:read spend
  &state=af0ifjsldkj

The user signs in (Google), sees what you ask for, can narrow it, and clicks Allow.

Exchange the code

Helipod redirects to your redirect_uri with code and your state. Check the state, then:

curl https://api.helipod.io/oauth/token \
  -u "hoa_your_client_id:hel_ocs_your_secret" \
  -d grant_type=authorization_code \
  -d code=THE_CODE \
  -d redirect_uri=https://example.com/auth/helipod/callback \
  -d code_verifier=THE_VERIFIER

A public app (no secret) sends client_id in the body instead of -u. The answer:

{
  "access_token": "hel_oat_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "hel_ort_…",
  "scope": "containers vps:read spend"
}

scope is what the user actually allowed, which can be less than you asked for.

Call the API

Send the access token like an API key:

curl https://api.helipod.io/api/containers/projects \
  -H "Authorization: Bearer hel_oat_…"

When it expires, get a new pair with grant_type=refresh_token&refresh_token=hel_ort_…. Every refresh returns a new refresh token: store it and drop the old one. Using an old refresh token again ends the whole connection, because it means it leaked.

Scopes

ScopeGives
containers, databases, vps, domains, email, ai, accountFull access to that product
containers:read, vps:read, …Read only: GET requests, price checks (?dryRun=true) and read-only SQL
spendMay charge the wallet: create or upgrade services, VPS, domains, plans, renewals, AI top-ups

The user decides in the end: they can switch a product to read only, turn purchases off, set a monthly limit, or reach only one project. Without a whole-account connection, the Domain API and the account's money routes are not available, as with API keys.

Purchases and the monthly limit

Without spend, every request that would cost money answers 403 PURCHASE_NOT_ALLOWED. The same request with ?dryRun=true still works, so your app can show the price and ask the user to allow purchases.

With a monthly limit, each purchase is priced before it runs. If it would go over the limit it is refused with 402 SPEND_LIMIT_REACHED, and nothing is charged. A purchase whose price cannot be checked first is refused with 402 SPEND_LIMIT rather than run unchecked. The limit counts what your app bought this calendar month; the daily cost of services that keep running is not counted. AI Gateway usage through your app has its own monthly counter with the same limit.

Errors

Besides the usual API errors (see Requests and errors):

StatuscodeMeaning
401INVALID_TOKENThe access token expired or was revoked: refresh it, or sign in again
403SCOPE_NOT_ALLOWEDThe user did not give your app this product
403READ_ONLY_ACCESSYour app may only read this product
403PURCHASE_NOT_ALLOWEDYour app may not spend from the wallet
402SPEND_LIMIT_REACHED / SPEND_LIMITOver the monthly limit, or its price could not be checked first
429APP_RATE_LIMITEDYour app sent too many requests in the last minute, across all its users

If Helipod turns an app off (abuse, a security problem), its tokens stop working at once and the token endpoint answers invalid_client. The developer sees why in Developer apps.

AI Gateway with the user's balance

With ai (full access) and spend, your app can call the AI Gateway with the user's access token instead of an AI key, and the usage is paid from their AI balance:

import OpenAI from "openai"

const client = new OpenAI({ baseURL: "https://ai.helipod.io/v1", apiKey: userAccessToken }) // hel_oat_…
const r = await client.chat.completions.create({ model: "glm-5.3", messages: [{ role: "user", content: "Halo!" }] })

The user sees your app as an AI key named "App: your app name" in AI Gateway → API keys, with its usage, and can revoke it there.

Deploy on Helipod button

For an open-source project you do not need OAuth at all: add a button that deploys your repository into the visitor's own account.

[![Deploy di Helipod](https://helipod.io/deploy-on-helipod.svg)](https://helipod.io/deploy?repo=owner/name&port=3000)
Parameter
repoowner/name on GitHub, or an https URL of any public Git host (required)
branchBranch to build (default: the repository's default branch)
rootFolder of the app in a monorepo
portPort the app listens on: it gets a public *.helipod.app domain
nameService name (letters, digits, - and _)

The visitor signs in, picks an environment and clicks Deploy. Helipod builds the repository with the usual build detection. For several services, variables or a database, publish a template instead: its page has the same button.

On this page