Swift
Alamofire
HTTP
status code
programming

Swift Alamofire How to get the HTTP response status code

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Alamofire provides HTTP response status codes through the response.response?.statusCode property on every request. The status code is an optional Int because the response object is nil when the request fails before reaching the server (no network, DNS failure, timeout). Understanding how to access and handle these codes is essential for building robust networking layers in Swift applications.

Accessing the Status Code

swift
1import Alamofire
2
3AF.request("https://api.example.com/users").responseDecodable(of: [User].self) { response in
4    // response.response is the HTTPURLResponse (optional)
5    if let statusCode = response.response?.statusCode {
6        print("Status code: \(statusCode)")  // e.g., 200
7    }
8}

response.response is an HTTPURLResponse?. It is nil when the request never reached the server. The statusCode property is a plain Int — 200, 404, 500, etc.

Handling Different Status Codes

swift
1AF.request("https://api.example.com/users/42").responseData { response in
2    guard let statusCode = response.response?.statusCode else {
3        print("No response from server")
4        return
5    }
6
7    switch statusCode {
8    case 200...299:
9        print("Success")
10    case 401:
11        print("Unauthorized — refresh token or log in again")
12    case 403:
13        print("Forbidden — user lacks permission")
14    case 404:
15        print("Resource not found")
16    case 500...599:
17        print("Server error — try again later")
18    default:
19        print("Unexpected status: \(statusCode)")
20    }
21}

Using Swift's range matching in switch makes it clean to group status codes by category.

Using validate() for Automatic Status Code Checking

Alamofire's validate() method automatically treats non-2xx status codes as errors:

swift
1AF.request("https://api.example.com/users")
2    .validate(statusCode: 200..<300)  // Fail if not 2xx
3    .responseDecodable(of: [User].self) { response in
4        switch response.result {
5        case .success(let users):
6            print("Got \(users.count) users")
7        case .failure(let error):
8            // error includes the status code context
9            print("Request failed: \(error)")
10
11            // Still access the raw status code if needed
12            if let statusCode = response.response?.statusCode {
13                print("HTTP \(statusCode)")
14            }
15        }
16    }

Calling .validate() with no arguments validates that the status code is 200-299 and that the Content-Type matches the Accept header:

swift
1AF.request("https://api.example.com/users")
2    .validate()  // Same as .validate(statusCode: 200..<300) + content type check
3    .responseJSON { response in
4        // response.result is .failure if status code is outside 200-299
5    }

Combining Status Code with Response Data

APIs often include error details in the response body even on failure:

swift
1struct APIError: Decodable {
2    let message: String
3    let code: String
4}
5
6AF.request("https://api.example.com/users", method: .post, parameters: newUser)
7    .responseData { response in
8        let statusCode = response.response?.statusCode ?? 0
9
10        switch response.result {
11        case .success(let data):
12            if (200...299).contains(statusCode) {
13                let user = try? JSONDecoder().decode(User.self, from: data)
14                print("Created user: \(user?.name ?? "unknown")")
15            } else {
16                // Server returned an error with a body
17                let apiError = try? JSONDecoder().decode(APIError.self, from: data)
18                print("Error \(statusCode): \(apiError?.message ?? "Unknown error")")
19            }
20        case .failure(let error):
21            print("Network error: \(error)")
22        }
23    }

Status Code in Async/Await (Alamofire 5.5+)

swift
1func fetchUser(id: Int) async throws -> User {
2    let response = await AF.request("https://api.example.com/users/\(id)")
3        .validate()
4        .serializingDecodable(User.self)
5        .response
6
7    // Access status code on the response
8    print("Status: \(response.response?.statusCode ?? 0)")
9
10    // .value throws if validation or decoding failed
11    return try response.result.get()
12}

Or use the simpler .value accessor:

swift
1let user = try await AF.request("https://api.example.com/users/1")
2    .validate()
3    .serializingDecodable(User.self)
4    .value  // Throws on non-2xx or decode failure

HTTP Status Code Categories

RangeCategoryMeaning
1xxInformationalRequest received, continuing process
2xxSuccessRequest successfully received and accepted
3xxRedirectionFurther action needed to complete request
4xxClient ErrorBad request syntax or cannot be fulfilled
5xxServer ErrorServer failed to fulfill a valid request

Common Pitfalls

  • Force-unwrapping response.response: response.response is nil when the request fails before reaching the server (no network, DNS failure). Always use optional binding (if let) or provide a default value.
  • Ignoring validate() and checking status codes manually everywhere: Alamofire's validate() method converts non-2xx responses into errors automatically. Without it, a 404 response with valid JSON still appears as .success in the result.
  • Assuming status code means the request succeeded: A status code of 200 means the HTTP request succeeded, but the response body may still contain an application-level error. Always check both the status code and the response payload.
  • Not handling the nil response case: When response.response is nil, there is no status code at all. This happens on network timeouts, airplane mode, or invalid URLs. Treating nil as 0 or ignoring it causes silent failures.
  • Using deprecated response serializers: Alamofire 5 replaced responseJSON completion-based APIs with responseDecodable and async/await. Using the old response.result.value pattern from Alamofire 4 causes compiler errors.

Summary

  • Access status codes via response.response?.statusCode (returns optional Int)
  • Use validate(statusCode: 200..<300) to automatically treat non-2xx as errors
  • response.response is nil when the request never reached the server
  • Combine status code checking with response body parsing for complete error handling
  • Alamofire 5.5+ supports async/await with .serializingDecodable() and .value
  • Always handle both network failures (no response) and HTTP errors (non-2xx status codes)

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.