Back to Blog
API design
API integration
API monitoring
API development tools
API architecture

Building APIs for Mobile Applications

5 min read
K
Kevin
API Security Specialist

Building APIs for Mobile Applications

Introduction to Mobile API Design

Mobile applications require specialized API design considerations that differ from traditional web services. The constraints of mobile devices—including intermittent connectivity, limited bandwidth, battery life, and varying screen sizes—demand thoughtful API architecture.

Key principles for mobile APIs:

  • Minimize payload size
  • Optimize for high latency networks
  • Support offline functionality
  • Handle authentication securely
  • Provide efficient data synchronization

RESTful API Best Practices for Mobile

Resource Design

// Good resource naming example
GET /users/{id}/preferences
POST /notifications/{id}/read

Avoid RPC-style endpoints in favor of RESTful resources. Each endpoint should represent a specific resource with clear relationships.

Response Formatting

{
  "data": {
    "id": "usr_123",
    "name": "Jane Doe",
    "avatar": {
      "small": "https://cdn.example.com/avatars/small/123.jpg",
      "medium": "https://cdn.example.com/avatars/medium/123.jpg"
    }
  },
  "meta": {
    "cache_ttl": 3600,
    "api_version": "2025-01"
  }
}

Include multiple image sizes for responsive mobile displays and provide metadata about caching and API versioning.

Pagination and Partial Responses

GET /posts?fields=id,title,preview&limit=20&offset=40
{
  "items": [...],
  "pagination": {
    "total": 120,
    "limit": 20,
    "offset": 40,
    "has_more": true
  }
}

Implement cursor-based pagination for better performance with large datasets:

GET /posts?limit=20&after=cursor123

Performance Optimization

Compression and Minification

Always enable gzip/brotli compression. For JSON responses, consider minification in production:

# Nginx configuration
gzip on;
gzip_types application/json;
brotli on;
brotli_types application/json;

Field Selection

Allow clients to request only needed fields:

GET /users/me?fields=id,name,email

Conditional Requests

Implement ETag and Last-Modified headers:

GET /products/123
If-None-Match: "a1b2c3d4"

Authentication and Security

Modern Auth Flow

sequenceDiagram
    Mobile App->>Auth Service: Initiate auth (PKCE)
    Auth Service-->>Mobile App: Return auth code
    Mobile App->>Backend: Exchange code for tokens
    Backend->>Auth Service: Verify code
    Auth Service-->>Backend: Return tokens
    Backend-->>Mobile App: Return session tokens

Use OAuth 2.0 with PKCE (RFC 7636) for mobile applications:

// iOS PKCE example
let verifier = generateCodeVerifier()
let challenge = generateCodeChallenge(verifier: verifier)

let authURL = URL(string: "https://auth.example.com?client_id=MOBILE_APP&code_challenge=\(challenge)&code_challenge_method=S256")!

Token Management

Implement refresh token rotation (RFC 6819):

# Python refresh token example
def refresh_access_token(refresh_token):
    new_tokens = auth_service.refresh(
        refresh_token,
        client_id=CLIENT_ID,
        client_secret=CLIENT_SECRET
    )
    
    # Invalidate old refresh token
    revoke_refresh_token(refresh_token)
    
    return {
        "access_token": new_tokens.access_token,
        "refresh_token": new_tokens.refresh_token,
        "expires_in": new_tokens.expires_in
    }

Offline Support and Synchronization

Delta Sync Patterns

GET /messages?since=2025-01-01T00:00:00Z
{
  "updates": [...],
  "deletes": ["msg_123", "msg_456"],
  "current_server_time": "2025-01-02T12:00:00Z"
}

Conflict Resolution

Implement Last-Write-Wins or operational transformation:

{
  "error": {
    "code": "conflict",
    "message": "Version mismatch",
    "server_version": 5,
    "client_version": 4,
    "server_data": {
      "title": "Server Title",
      "content": "Merged Content"
    }
  }
}

Push Notifications

Unified Push Service

// Go push notification example
func sendPushNotification(userID string, payload PushPayload) error {
    devices, err := db.GetUserDevices(userID)
    if err != nil {
        return err
    }

    for _, device := range devices {
        switch device.Platform {
        case "ios":
            sendAPNS(device.Token, payload)
        case "android":
            sendFCM(device.Token, payload)
        }
    }
    
    return nil
}

Notification Payload Optimization

{
  "aps": {
    "alert": {
      "title": "New Message",
      "body": "You have a new message from John"
    },
    "content-available": 1
  },
  "data": {
    "type": "message",
    "id": "msg_123",
    "thread_id": "thread_456"
  }
}

GraphQL for Mobile

Query Optimization

query GetUserProfile {
  user(id: "123") {
    id
    name
    avatar(size: [64, 128]) {
      small: url(size: 64)
      medium: url(size: 128)
    }
    friends(first: 10) {
      edges {
        node {
          id
          name
        }
      }
    }
  }
}

Persisted Queries

# Query registration
curl -X POST https://api.example.com/graphql/persisted \
  -H "Authorization: Bearer token123" \
  -d '{
    "id": "userProfileQuery",
    "query": "query GetUserProfile($id: ID!) { user(id: $id) { id name }}"
  }'
GET /graphql?queryId=userProfileQuery&variables={"id":"123"}

Error Handling

Structured Error Responses

{
  "error": {
    "code": "invalid_auth",
    "message": "Authentication token expired",
    "details": {
      "expired_at": "2025-01-01T00:00:00Z"
    },
    "links": {
      "refresh_token": "/auth/refresh"
    }
  }
}

Network Error Recovery

// Android retry with exponential backoff
fun <T> withRetry(block: () -> T): T {
    var currentDelay = 1000L
    val maxDelay = 30000L
    
    while (true) {
        try {
            return block()
        } catch (e: IOException) {
            if (currentDelay >= maxDelay) throw e
            Thread.sleep(currentDelay)
            currentDelay = (currentDelay * 2).coerceAtMost(maxDelay)
        }
    }
}

Monitoring and Analytics

Performance Metrics

# Flask middleware example
@app.after_request
def log_request(response):
    metrics.timing(
        "api.request_time",
        (datetime.now() - request.start_time).total_seconds(),
        tags={
            "endpoint": request.endpoint,
            "method": request.method,
            "status": response.status_code
        }
    )
    return response

Client-Side Tracking

// iOS network monitoring
class APIMonitor: URLProtocol {
    override class func canInit(with request: URLRequest) -> Bool {
        return request.url?.host == "api.example.com"
    }
    
    override func startLoading() {
        let start = Date()
        
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            let duration = Date().timeIntervalSince(start)
            Analytics.trackAPIRequest(
                path: request.url?.path ?? "",
                duration: duration,
                statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0
            )
            // ... handle response
        }
        
        task.resume()
    }
}

The mobile landscape continues to evolve, so regularly revisit your API design to incorporate new best practices and technologies as they emerge.

Back to Blog