This document describes how the Interspace iOS app integrates with the backend API.
// Environment-based URLs
Development: http://localhost:3000/api/v1
Staging: https://staging-api.interspace.com/api/v1
Production: https://api.interspace.com/api/v1All requests use JSON format with the following headers:
Content-Type: application/json
Accept: application/json
Authorization: Bearer <access_token>
X-App-Version: 1.0.0
X-Platform: iOS-
Email/Password Login
POST /auth/login { "email": "account@example.com", "password": "secure_password" } Response: { "accessToken": "eyJ...", "refreshToken": "eyJ...", "account": { // Changed from 'user' to 'account' for flat identity model "id": "account_id", "email": "account@example.com", "profiles": [...] } }
-
Google Sign-In
POST /auth/google { "idToken": "google_id_token" }
-
Apple Sign-In
POST /auth/apple { "identityToken": "apple_identity_token", "authorizationCode": "apple_auth_code", "account": { // Changed from 'user' to 'account' for flat identity model "email": "account@privaterelay.apple.com", "firstName": "John", "lastName": "Doe" } }
-
Refresh Token
POST /auth/refresh { "refreshToken": "current_refresh_token" } Response: { "accessToken": "new_access_token", "refreshToken": "new_refresh_token" }
-
Logout
POST /auth/logout { "refreshToken": "current_refresh_token" }
-
Register Passkey
POST /auth/passkey/register { "challenge": "server_challenge", "credentialId": "credential_id", "publicKey": "public_key_data" }
-
Authenticate with Passkey
POST /auth/passkey/authenticate { "credentialId": "credential_id", "signature": "auth_signature", "clientData": "client_data_json" }
-
Get Current User
GET /users/me Response: { "id": "user_id", "email": "user@example.com", "profiles": [...], "settings": {...} }
-
Update User
PATCH /users/me { "email": "newemail@example.com", "settings": { "notifications": true, "theme": "dark" } }
-
List Profiles
GET /profiles Response: { "profiles": [ { "id": "profile_id", "username": "johndoe", "displayName": "John Doe", "bio": "Digital identity enthusiast", "avatar": "avatar_url", "isDefault": true } ] }
-
Create Profile
POST /profiles { "username": "newprofile", "displayName": "New Profile", "bio": "Profile description", "avatar": "avatar_data_or_url" }
-
Update Profile
PATCH /profiles/{profileId} { "displayName": "Updated Name", "bio": "Updated bio", "settings": { "privacy": "public" } }
-
Delete Profile
DELETE /profiles/{profileId}
-
Link Social Account
POST /profiles/{profileId}/social-accounts { "platform": "twitter", "accountId": "twitter_user_id", "username": "@johndoe", "accessToken": "platform_access_token" }
-
List Social Accounts
GET /profiles/{profileId}/social-accounts Response: { "accounts": [ { "id": "account_id", "platform": "twitter", "username": "@johndoe", "verified": true, "connectedAt": "2024-01-15T10:00:00Z" } ] }
-
Unlink Social Account
DELETE /profiles/{profileId}/social-accounts/{accountId}
-
Connect Wallet
POST /wallets/connect { "address": "0x...", "signature": "signed_message", "message": "original_message", "walletType": "metamask" }
-
List Wallets
GET /wallets Response: { "wallets": [ { "id": "wallet_id", "address": "0x...", "type": "metamask", "isPrimary": true, "balance": { "ETH": "1.5", "USDC": "1000" } } ] }
-
Disconnect Wallet
DELETE /wallets/{walletId}
-
List Connected Apps
GET /profiles/{profileId}/apps Response: { "apps": [ { "id": "app_id", "name": "DeFi App", "icon": "app_icon_url", "permissions": ["read_profile", "read_wallet"], "connectedAt": "2024-01-15T10:00:00Z" } ] }
-
Revoke App Access
DELETE /profiles/{profileId}/apps/{appId}
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
"field": "Additional context"
}
}
}| Code | HTTP Status | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Invalid or expired token |
| FORBIDDEN | 403 | Insufficient permissions |
| NOT_FOUND | 404 | Resource not found |
| VALIDATION_ERROR | 400 | Invalid request data |
| RATE_LIMITED | 429 | Too many requests |
| SERVER_ERROR | 500 | Internal server error |
enum APIError: Error {
case unauthorized
case forbidden
case notFound
case validationError(String)
case rateLimited(retryAfter: Int)
case serverError
case networkError
case decodingError
}
// Usage
do {
let profile = try await APIService.shared.fetchProfile()
} catch APIError.unauthorized {
// Refresh token or re-authenticate
} catch APIError.rateLimited(let retryAfter) {
// Wait and retry
} catch {
// Handle other errors
}struct User: Codable {
let id: String
let email: String
let profiles: [Profile]
let settings: UserSettings
let createdAt: Date
let updatedAt: Date
}struct Profile: Codable {
let id: String
let username: String
let displayName: String
let bio: String?
let avatar: String?
let isDefault: Bool
let socialAccounts: [SocialAccount]
let settings: ProfileSettings
}struct Wallet: Codable {
let id: String
let address: String
let type: WalletType
let isPrimary: Bool
let balance: [String: String]?
let chain: BlockchainNetwork
}struct SocialAccount: Codable {
let id: String
let platform: SocialPlatform
let username: String
let profileUrl: String?
let verified: Bool
let connectedAt: Date
}// Batch requests when possible
struct BatchRequest: Codable {
let requests: [APIRequest]
}
// Use pagination for large datasets
GET /profiles?page=1&limit=20// Cache responses with appropriate TTL
let cachePolicy = URLRequest.CachePolicy.returnCacheDataElseLoad
request.cachePolicy = cachePolicy
// Custom cache headers
Cache-Control: max-age=300
ETag: "resource_version"// Monitor network status
NetworkMonitor.shared.isConnected
// Implement offline mode
if !NetworkMonitor.shared.isConnected {
return cachedData
}- Always use HTTPS in production
- Implement certificate pinning
- Store tokens securely in Keychain
- Clear sensitive data from memory
- Validate all server responses
The API implements rate limiting:
- 100 requests per minute for authenticated users
- 20 requests per minute for unauthenticated users
Handle rate limits gracefully:
if response.statusCode == 429 {
let retryAfter = response.headers["Retry-After"] ?? "60"
await Task.sleep(nanoseconds: UInt64(retryAfter) * 1_000_000_000)
return try await retry()
}Always include API version in requests:
- Current version: v1
- Version included in base URL
- Check for deprecation headers
For real-time updates:
// Connect to WebSocket
let socket = WebSocket(url: "wss://api.interspace.com/ws")
// Subscribe to events
socket.send(json: [
"type": "subscribe",
"channels": ["profile_updates", "wallet_updates"]
])
// Handle messages
socket.onMessage { message in
switch message.type {
case "profile_updated":
updateProfile(message.data)
case "wallet_balance_changed":
updateWalletBalance(message.data)
default:
break
}
}For development, use mock responses:
#if DEBUG
if ProcessInfo.processInfo.environment["USE_MOCK_API"] == "1" {
return MockAPIService()
}
#endifInteractive API documentation available at:
- Development: http://localhost:3000/api-docs
- Production: https://api.interspace.com/api-docs
This API integration guide provides the foundation for communicating with the Interspace backend. Always refer to the latest API documentation for updates and changes.