Documentation

Best Practices

Guidelines for building reliable integrations with the OrbitKit API.

Follow these practices to build robust, reliable integrations with the OrbitKit API.

Error handling

  • Check error.code, not just the HTTP status — the code field provides a machine-readable identifier (e.g., app-not-found, subscription-required)
  • Display error.message to users — messages are human-readable and provide actionable guidance
  • Log X-Request-Id — every response includes this header; include it when contacting support
const res = await fetch(url, options);
if (!res.ok) {
  const { error } = await res.json();
  console.error(`[${res.headers.get('X-Request-Id')}] ${error.code}: ${error.message}`);
}

Idempotency

  • GET, PUT, DELETE are safe to retry — calling them multiple times produces the same result
  • POST creates are NOT idempotentPOST /api/apps creates a new app each time; guard against duplicate calls
  • POST actions (deploy, subscribe) have built-in guards — deploying twice in a row is safe (the second deploy is a no-op if nothing changed)

Polling patterns

Some operations are asynchronous. Use exponential backoff when polling:

Operation Initial interval Max interval Timeout
Domain DNS verification 30 seconds 5 minutes 1 hour
Post-checkout status 5 seconds 30 seconds 2 minutes
async function pollUntil(checkFn, { interval = 5000, maxInterval = 30000, timeout = 120000 }) {
  const start = Date.now();
  let delay = interval;

  while (Date.now() - start < timeout) {
    const result = await checkFn();
    if (result) return result;

    await new Promise(r => setTimeout(r, delay));
    delay = Math.min(delay * 2, maxInterval);
  }

  throw new Error('Polling timeout');
}

Deploy after configuration changes

Configuration changes (site settings, privacy policy, AASA, support/deletion pages) are saved to the database but not reflected on the live site until you deploy:

curl -X POST https://api.orbitkit.io/api/apps/$APP_ID/deploy \
  -H "Authorization: Bearer $TOKEN"
var request = URLRequest(url: URL(string: "https://api.orbitkit.io/api/apps/\(appId)/deploy")!)
request.httpMethod = "POST"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

let (_, response) = try await URLSession.shared.data(for: request)
// 200 OK on success
await fetch(`https://api.orbitkit.io/api/apps/${appId}/deploy`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
});

Batch your changes, then deploy once to minimize deploy operations.

Data model

Understanding the data hierarchy helps you use the API effectively:

Account (user)
  └── App (appId)
       ├── Site config (slug, domain, banner)
       ├── Privacy Policy (wizard data, 14 Apple categories)
       ├── Support Page (markdown content)
       ├── Deletion Page (markdown content)
       ├── AASA (Universal Links, App Clips, Passkeys, Handoff)
       └── Subscription (plan, status, Stripe link)
  • Each user can have multiple apps — each app has its own subscription
  • An app’s site has a slug (e.g., my-app.orbitkit.io) and an optional custom domain
  • Pages (policy, support, deletion) are configured independently and included in each deploy

API key security

  • Never commit API keys to source control — use environment variables or secret management tools (e.g., GitHub Actions secrets, AWS Secrets Manager)
  • Use descriptive names — name keys after their purpose (e.g., “GitHub Actions Deploy”, “Staging CI”) so you know which to revoke
  • Rotate keys regularly — create a new key, update your integration, then revoke the old key. See the rotation guide
  • Revoke compromised keys immediately — revocation is instant and the key stops working on the next request
  • Use one key per integration — if one system is compromised, you only need to revoke and replace that key
  • Monitor lastUsedAt — check the API keys list to identify unused keys that should be revoked

Idempotency

  • GET, PUT, DELETE are safe to retry — calling them multiple times produces the same result
  • POST creates are NOT idempotent by defaultPOST /api/apps creates a new app each time
  • Use Idempotency-Key for safe retries — pass an Idempotency-Key header on POST requests to ensure the operation only happens once, even if you retry due to a network error:
curl -X POST https://api.orbitkit.io/api/apps \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"appName": "My App"}'
var request = URLRequest(url: URL(string: "https://api.orbitkit.io/api/apps")!)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("unique-request-id-123", forHTTPHeaderField: "Idempotency-Key")
request.httpBody = try JSONEncoder().encode(["appName": "My App"])

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

Idempotency keys are retained for 24 hours. Reusing the same key with the same parameters returns the original response. Reusing with different parameters returns a 422 IDEMPOTENCY_KEY_REUSE error.

Security

  • Server-to-server calls only — never embed API keys in client-side code shipped to end users
  • Rotate credentials if you suspect a compromise — revoke API keys immediately
  • Use HTTPS exclusively — the API only accepts HTTPS connections
  • Validate responses — check HTTP status codes and error.code fields before processing response data