Swift SDK

The official Swift client for the FoPost API, for iOS, macOS, tvOS, and watchOS.

FoPost is the official Swift package. It needs Swift 5.9 or newer and iOS 15, macOS 12, tvOS 15, or watchOS 8, and it has no third-party dependencies: URLSession and Codable only, so nothing lands in your dependency graph but this.

Add it to Package.swift:

dependencies: [
    .package(url: "https://github.com/fopost/fopost-swift.git", from: "0.1.0")
]

and the product to your target:

.target(name: "YourApp", dependencies: [.product(name: "FoPost", package: "fopost-swift")])

In Xcode: File → Add Package Dependencies… and paste the repository URL.

This is a 0.x release. The surface is still settling and a minor version may break something. Pin an exact version if that matters to you.

Source and issues: github.com/fopost/fopost-swift. MIT licensed.

Quick start

import FoPost

let client = try FoPostClient(apiKey: "fp_...")  // or omit it and set FOPOST_API_KEY

let workspace = try await client.workspaces.list()[0]
let accounts = try await client.accounts.list(workspaceID: workspace.id)

let post = try await client.posts.create(
    CreatePostRequest(
        workspaceID: workspace.id,
        accounts: accounts.map(\.id),
        content: .text("Hello from Swift")))

try await client.posts.publish(post.id)

Create a key at Settings → API Keys in the dashboard. It travels as the X-API-Key header, never as a bearer token.

Content

.text builds a single-block post and .thread builds one block per entry. Media is attached per block:

try await client.posts.create(
    CreatePostRequest(
        workspaceID: workspace.id,
        accounts: accounts.map(\.id),
        content: [
            ContentBlock(text: "First post in the thread"),
            ContentBlock(
                text: "Second one, with an image",
                media: [MediaItem(type: "image", name: "chart.png", url: "https://.../chart.png")]),
        ]))

Uploading through the media library gives you an item that drops straight in:

let uploaded = try await client.media.upload(
    workspaceID: workspace.id,
    file: UploadFile(contentsOf: URL(fileURLWithPath: "chart.png")))

let content = [ContentBlock(text: "Numbers are in", media: [uploaded.asMediaItem()])]

Scheduling and publishing

status is .draft or .scheduled, and a scheduled post needs scheduleAt. To send something out now, create it and call publish. Nothing reaches a platform without one of those two.

let post = try await client.posts.create(
    CreatePostRequest(
        workspaceID: workspace.id,
        accounts: accounts.map(\.id),
        content: .text("Ship day"),
        status: .scheduled,
        scheduleAt: Date().addingTimeInterval(3600)))

publish returns when delivery is queued, not when the post is live. Poll client.posts.deliveries(post.id) or subscribe to webhooks for the outcome. publish(_:dryRun: true) validates the plan without sending anything, and preflight reports per-account blockers and advisory content signals.

Resources

NamespaceCovers
client.postsList, create, update, publish, cancel, retry, preflight, deliveries, publish runs, per-post analytics, bulk actions, CSV import
client.workspacesWorkspaces and their follower and post roll-up
client.accountsConnected accounts, health, validation, token refresh, history
client.communitiesThe X communities an account can post into
client.labelsCampaign labels
client.webhooksOutbound event subscriptions
client.analyticsOverview, time series, top posts, posts table, labels, demographics, posting streak, on-demand collection
client.automationsAutomations, runs, stats, manual triggers
client.mediaThe media library and uploads

Pagination

Lists that paginate return a Page<T> carrying data and meta (currentPage, perPage, total, lastPage, from, to). client.posts.all(_:) walks every page as an AsyncThrowingStream:

for try await post in client.posts.all(PostListParams(status: .published)) {
    print(post.id)
}

Concurrency

FoPostClient is Sendable, and so is every model and request type. The whole public API is async/await, the package builds in Swift 6 language mode, and one client is safe to share across tasks and actors.

Configuration

let client = try FoPostClient(
    apiKey: key,
    configuration: FoPostConfiguration(timeout: 60, maxAttempts: 5))

Set FOPOST_BASE_URL to point the SDK at another deployment; it defaults to https://api.fopost.com/v1.

Three attempts by default, so two retries, on 429, 5xx, and network errors. Nothing else is retried, so a 400 or a 404 comes back immediately. Backoff is exponential from 500 ms, doubling, capped at 60 seconds; a Retry-After header on a 429 wins, capped the same way. A cancelled task is never retried.

Errors

Every failure is a FoPostError, mapped from the response status:

do {
    try await client.posts.publish(post.id)
} catch let error as FoPostError {
    switch error {
    case .paymentRequired(let details):
        print(details.message, error.upgradeURL as Any)
    case .rateLimited:
        print("retry after \(error.retryAfter ?? 0)s")
    case .validation(let details):
        print(details.field("fields", as: [String].self) ?? [])
    default:
        print(error.localizedDescription)
    }
}
CaseStatus
.validation400, 422
.authentication401
.paymentRequired402, carries upgradeURL
.permissionDenied403
.notFound404
.conflict409
.rateLimited429, carries retryAfter
.server5xx
.apianything else
.transportthe request never reached the API
.decoding, .encoding, .configurationclient side

Each API case carries status, code, message, the raw body, the rate-limit budget, and field(_:as:) for anything the SDK does not model.

Escape hatch

Anything the client does not wrap yet is still reachable, with the same auth, retries, and error handling:

let payload = try await client.request(
    method: "GET", path: "/some/new/endpoint", query: ["days": "30"], as: JSONValue.self)

Pass any Encodable as body, and unwrapData: false when the endpoint does not use the {"data": ...} envelope.

Next

Related documentation
  • SDKs & Integrations

    Official FoPost clients for TypeScript, Python, PHP, Ruby, Go, Rust, Java, .NET, Swift, Kotlin, Dart, and Elixir, framework integrations from Laravel to Next.js, and tooling for the CLI, CI, Terraform, and the automation platforms.

  • SDKs Overview

    Every official FoPost client, what it covers, and how to pick one.

  • TypeScript SDK

    The official TypeScript and Node.js client for the FoPost API.

  • Python SDK

    The official Python client for the FoPost API.

  • PHP SDK

    The official PHP client for the FoPost API, with no framework and no HTTP library.

  • Ruby SDK

    The official Ruby client for the FoPost API, with no runtime dependencies.

  • Go SDK

    The official Go client for the FoPost API.

  • Rust SDK

    The official Rust client for the FoPost API, async and built on reqwest.

Was this helpful?

On this page