Documentation

App Sites

Create, list, get, and delete app sites via the OrbitKit API.

App sites are the top-level resource in OrbitKit. Each app site has its own privacy policy, support page, custom domain, and subscription. A single account can have up to 10 app sites.

Endpoints

Method Path Description
GET /api/apps List all apps
POST /api/apps Create an app
GET /api/apps/:appId Get a single app
DELETE /api/apps/:appId Delete an app
GET /api/apps/:appId/site Get site configuration
PATCH /api/apps/:appId/site Update site configuration

List all apps

GET /api/apps

Returns all apps for the authenticated user.

Response

[
  {
    "appId": "-NtestApp123",
    "appName": "My Weather App",
    "slug": "my-weather-app",
    "createdAt": 1712345678000,
    "updatedAt": 1712345678000
  }
]

Returns an empty array [] if the user has no apps.


Create an app

POST /api/apps

Creates a new app with an auto-generated URL slug derived from the app name.

Rate limit: 5/hour per user

Request body

Field Type Required Description
appName string Yes App display name (1–200 chars)

Full example

curl -X POST https://api.orbitkit.io/api/apps \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"appName": "My Weather App"}'
var request = URLRequest(url: URL(string: "https://api.orbitkit.io/api/apps")!)
request.httpMethod = "POST"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(["appName": "My Weather App"])

let (data, response) = try await URLSession.shared.data(for: request)
let result = try JSONDecoder().decode(CreateAppResponse.self, from: data)
print(result.appId) // "-NtestApp123"
const res = await fetch("https://api.orbitkit.io/api/apps", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ appName: "My Weather App" }),
});
const { appId, appName, slug } = await res.json();

Response 201 Created

{
  "appId": "-NtestApp123",
  "appName": "My Weather App",
  "slug": "my-weather-app"
}

Errors

Code Status When
VALIDATION_FAILED 400 appName is missing or invalid
APP_LIMIT_REACHED 400 Account already has 10 apps
SLUG_TAKEN 409 Generated slug is already in use

Get a single app

GET /api/apps/:appId

Returns app details including the site configuration.

Response

{
  "appId": "-NtestApp123",
  "appName": "My Weather App",
  "site": {
    "appName": "My Weather App",
    "description": "Accurate weather forecasts",
    "slug": "my-weather-app",
    "iconUrl": "https://storage.googleapis.com/.../icon.png",
    "deployed": true,
    "deployedAt": 1712345678000,
    "createdAt": 1712345678000,
    "updatedAt": 1712345678000
  }
}

The site field is null if the app has not been configured yet.


Delete an app

DELETE /api/apps/:appId

Permanently deletes the app and all associated data:

  • Releases the URL slug
  • Removes the custom domain and SSL certificate
  • Cancels the app’s Stripe subscription
  • Deletes all files from storage
  • Removes the app record from the database

Response 204 No Content

No response body.


Get site configuration

GET /api/apps/:appId/site

Returns the site configuration for an app.

Response

{
  "appName": "My Weather App",
  "description": "Accurate weather forecasts",
  "slug": "my-weather-app",
  "iconUrl": "https://storage.googleapis.com/.../icon.png",
  "customDomain": "privacy.myapp.com",
  "customHomepageHtml": "",
  "searchIndexing": true,
  "deployed": true,
  "deployedAt": 1712345678000,
  "createdAt": 1712345678000,
  "updatedAt": 1712345678000
}

customHomepageHtml is "" (empty) when the default hero section is used. When set, it contains the sanitized HTML rendered in place of the hero.

searchIndexing (default true) controls whether search engines may index your hosted pages — keep it true so users can find your app’s privacy/support pages, or set false to keep them reachable only via the direct link. TestFlight beta pages are always excluded from search regardless of this value.


Update site configuration

PATCH /api/apps/:appId/site

Partially updates site fields. Only provided fields are changed.

Request body

All fields are optional:

Field Type Description
appName string Display name (1–200 chars)
description string Site description (max 4000 chars)
slug string URL slug (1–63 chars, lowercase alphanumeric + hyphens)
customHomepageHtml string Custom HTML to render in place of the default hero on the homepage (max 50 KB / 51200 bytes). Pass an empty string "" to clear and revert to the default. The server sanitizes the HTML on every save: scripts, iframes, forms, external stylesheets, and SVG are stripped. See Custom Homepage HTML for the full allowlist.
searchIndexing boolean Whether search engines may index the hosted pages (default true). false emits noindex on the privacy/support/deletion/home pages so they’re reachable only via the direct link. TestFlight beta pages are always noindex regardless. Requires a redeploy to take effect. See Search engine indexing.

Full example

curl -X PATCH https://api.orbitkit.io/api/apps/-NtestApp123/site \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"description": "Accurate weather forecasts for iOS"}'
var request = URLRequest(url: URL(string: "https://api.orbitkit.io/api/apps/-NtestApp123/site")!)
request.httpMethod = "PATCH"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(
  ["description": "Accurate weather forecasts for iOS"]
)

let (data, _) = try await URLSession.shared.data(for: request)
let site = try JSONDecoder().decode(SiteConfig.self, from: data)
const res = await fetch(
  "https://api.orbitkit.io/api/apps/-NtestApp123/site",
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      description: "Accurate weather forecasts for iOS",
    }),
  }
);
const site = await res.json();

Response

Returns the full merged site object (same shape as GET /api/apps/:appId/site).

Errors

Code Status When
VALIDATION_FAILED 400 Invalid field value
SLUG_TAKEN 409 New slug is already in use