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 |