iOS
HTTP POST
networking
Swift
mobile development

Sending an HTTP POST request on iOS

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

On iOS, the standard way to send an HTTP POST request is with URLSession. The job has three parts: build a URLRequest, attach the body and headers, then send it asynchronously and validate the response before decoding or using the returned data.

Build the Request Explicitly

A POST request needs a URL, HTTP method, headers, and body. JSON is the most common payload format for mobile APIs.

swift
1import Foundation
2
3struct LoginRequest: Encodable {
4    let email: String
5    let password: String
6}
7
8func makeLoginRequest() throws -> URLRequest {
9    let url = URL(string: "https://api.example.com/login")!
10    let payload = LoginRequest(email: "[email protected]", password: "secret")
11
12    var request = URLRequest(url: url)
13    request.httpMethod = "POST"
14    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
15    request.setValue("application/json", forHTTPHeaderField: "Accept")
16    request.httpBody = try JSONEncoder().encode(payload)
17
18    return request
19}

Being explicit keeps the request easy to inspect when debugging server-side failures.

Send the Request With URLSession

Modern Swift code usually uses async and await with URLSession.shared.data(for:).

swift
1import Foundation
2
3struct LoginResponse: Decodable {
4    let token: String
5}
6
7enum NetworkError: Error {
8    case invalidResponse
9    case badStatusCode(Int)
10}
11
12func login() async throws -> LoginResponse {
13    let request = try makeLoginRequest()
14    let (data, response) = try await URLSession.shared.data(for: request)
15
16    guard let httpResponse = response as? HTTPURLResponse else {
17        throw NetworkError.invalidResponse
18    }
19
20    guard 200..<300 ~= httpResponse.statusCode else {
21        throw NetworkError.badStatusCode(httpResponse.statusCode)
22    }
23
24    return try JSONDecoder().decode(LoginResponse.self, from: data)
25}

This pattern is clear and correct for most application code:

  • build request
  • await response
  • validate status code
  • decode payload

Call It From the UI Safely

Networking is asynchronous, so call it from a task instead of blocking the main thread.

swift
1import UIKit
2
3final class LoginViewController: UIViewController {
4    @MainActor
5    func performLogin() {
6        Task {
7            do {
8                let result = try await login()
9                print("Token:", result.token)
10            } catch {
11                print("Login failed:", error)
12            }
13        }
14    }
15}

The UI stays responsive because URLSession does not block the main thread while the request is in flight.

Posting Form Data Instead of JSON

Not every API expects JSON. Some older endpoints or OAuth flows expect application/x-www-form-urlencoded.

swift
1var request = URLRequest(url: URL(string: "https://api.example.com/token")!)
2request.httpMethod = "POST"
3request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
4request.httpBody = "grant_type=password&username=demo&password=secret".data(using: .utf8)

The transport is still HTTP POST. What changes is the body encoding and content type.

Reuse a Configured Session When Needed

For apps with authentication, retry behavior, custom timeouts, or background transfer requirements, create a dedicated URLSession with a URLSessionConfiguration instead of always using shared.

swift
1let configuration = URLSessionConfiguration.default
2configuration.timeoutIntervalForRequest = 30
3configuration.timeoutIntervalForResource = 60
4let session = URLSession(configuration: configuration)

This is especially useful when the app talks to one API consistently and needs stable networking policy.

Inspect the Response, Not Just the Error

A failed API call often returns a perfectly valid HTTP response with a 400, 401, or 500 status. That is not the same as a transport failure.

A robust POST implementation should distinguish between:

  • network transport errors such as no connectivity
  • HTTP status failures returned by the server
  • decoding failures caused by unexpected response formats

Lumping them together makes troubleshooting much harder.

App Transport Security Still Applies

On iOS, plain HTTP endpoints may be blocked by App Transport Security unless you configure exceptions. In most modern apps, the correct solution is simply to use HTTPS.

If a request mysteriously fails before reaching the server, confirm that the URL scheme and ATS policy are compatible.

Common Pitfalls

A common mistake is forgetting to set httpMethod = "POST". The request then defaults to GET even if the body exists.

Another mistake is sending JSON bytes without setting the Content-Type header. Some servers accept it anyway, but many will not.

Developers also skip status-code validation and try to decode every response as if it were a successful payload.

Finally, do not run synchronous network patterns on the main thread. URLSession is designed for asynchronous use, and iOS networking code should stay that way.

Summary

  • Use URLRequest plus URLSession to send HTTP POST requests on iOS.
  • Set the method, headers, and body explicitly.
  • Use async and await for clear non-blocking networking code.
  • Validate the HTTP status before decoding the response body.
  • Match the body encoding and Content-Type to what the server expects.

Course illustration
Course illustration

All Rights Reserved.