╔══════════════════╗ ║ APIClientProto ║ ╠══════════════════╣ ║ func request( ║ ║ method, ║ ║ path, ║ ║ body? ║ ║ ) → Decodable ║ ╚══════════════════╝

API Reference

APIClientProtocol, authentication, error handling, and retry logic.

┌────────────┐ │ GET /api │ │ POST /api │ │ PUT /api │ │ DEL /api │ └────────────┘
Developer Docs Archon iOS · Swift · SwiftUI API v1

API Reference

APIClientProtocol, authentication, error handling, and retry logic.

APIClientProtocol

All network communication in Archon goes through APIClientProtocol. This protocol abstracts the HTTP layer, making it easy to swap in mock implementations for testing and SwiftUI previews.

protocol APIClientProtocol {
    func request<T: Decodable>(
        method: HTTPMethod,
        path: String,
        body: (any Encodable)?
    ) async throws -> T

    func requestStream(
        method: HTTPMethod,
        path: String,
        body: (any Encodable)?
    ) -> AsyncThrowingStream<Data, Error>
}

Base URL & Configuration

Base URL
All API requests target the Supabase project URL: https://<project-ref>.supabase.co. The project reference is stored in the xcconfig file and injected at build time.
API Key
The Supabase anon key is included in the apikey header for every request. This is the public anon key, not the service role key.
Edge Functions
Server-side logic runs via Supabase Edge Functions at /functions/v1/. These handle AI model routing, credit management, and task orchestration.

Authentication Flow

┌──────────┐     ┌──────────┐     ┌──────────┐
│  Login   │────▶│ Supabase │────▶│  Store   │
│  Request │     │  Auth    │     │  Token   │
└──────────┘     └──────────┘     └─────┬────┘
                                        │
                                   ┌────▼────┐
                                   │ Keychain│
                                   │ Storage │
                                   └─────────┘

Subsequent requests:
  Header: Authorization: Bearer <access_token>
  Header: apikey: <anon_key>
Sign In
POST /auth/v1/token?grant_type=password with email/password. Returns access_token and refresh_token.
Apple Sign In
POST /auth/v1/token?grant_type=id_token with the Apple identity token. Supabase creates or links the user account.
Token Refresh
POST /auth/v1/token?grant_type=refresh_token when the access token expires. Tokens are stored in the iOS Keychain via KeychainHelper.
Sign Out
POST /auth/v1/logout and clear Keychain. The app resets to the onboarding flow.

Error Handling

All API errors are caught and mapped to the APIError enum:

enum APIError: Error, LocalizedError {
    case notAuthenticated
    case notFound
    case conflict
    case validationError(String)
    case serverError(Int)
    case networkError(Error)
    case decodingError(Error)
    case rateLimited(retryAfter: TimeInterval)
    case quotaExceeded
}
🔐
401 — Not Authenticated
Token expired or missing. App attempts a silent refresh. If refresh fails, redirects to login.
📋
400/422 — Validation
Invalid request body or parameters. Error message is displayed inline on the relevant form field.
⚠️
429 — Rate Limited
Too many requests. Uses the Retry-After header to schedule automatic retry.
💥
500 — Server Error
Unexpected server failure. Shows a generic error with a "Try Again" button. Logged for debugging.

Retry Logic

Automatic Retry
Network errors and 5xx responses are retried up to 3 times with exponential backoff (1s, 2s, 4s). 4xx errors are never retried.
Rate Limit Handling
On 429 responses, the client reads the Retry-After header and queues the retry accordingly. The UI shows a "Please wait" indicator.
Token Refresh Race
If a 401 is received, the client attempts one token refresh. Concurrent requests are queued during refresh to prevent race conditions.

Request Interceptors

The APIClient injects common headers into every request:

// Automatically added to every request:
Content-Type: application/json
Authorization: Bearer <access_token>
apikey: <anon_key>
X-Client-Info: archon-ios/1.0

Mock API Client

MockAPIClient conforms to APIClientProtocol and is used for unit tests, SwiftUI previews, and development. It returns configurable responses without hitting the network.

class MockAPIClient: APIClientProtocol {
    var projectsToReturn: [ArchonProject] = []
    var errorToThrow: APIError? = nil

    func request<T: Decodable>(...) async throws -> T {
        if let error = errorToThrow { throw error }
        return try JSONDecoder().decode(T.self, from: mockData)
    }
}