MisarMisar Docs
MisarMailMisarBlogMisarReachMisarPostMisarDevMisarCoderMisarSEOMisar PlatformMisar SSO
SDKs

Swift SDK

Publish articles, manage series, and run AI tools with the MisarBlog Swift SDK, plus post-embed helpers.

The Swift SDK ships a full client for the developer API (https://api.misar.io/blog/v1) plus the iframe embed helpers, in the MisarBlog package (v1.0.0).

Installation

Add the MisarBlog package in Package.swift:

dependencies: [
    .package(url: "https://github.com/misar-ai/misarblog-swift.git", from: "1.0.0"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "MisarBlog", package: "misarblog-swift"),
    ]),
]

API Client

MisarBlogClient authenticates with an mbk_ key (or an OAuth 2.1 access token) and defaults to https://api.misar.io/blog/v1 (the gateway strips /api). Every method is async throws and returns the decoded [String: Any] payload. Retryable statuses (429, 500, 502, 503, 504) are retried with exponential back-off.

import MisarBlog

let blog = MisarBlogClient(apiKey: "mbk_YOUR_KEY")

// Override the base URL if needed
let staging = MisarBlogClient(
    apiKey: "mbk_YOUR_KEY",
    baseURL: "https://api.misar.io/blog/v1"
)

Articles

// List your articles
let result = try await blog.articles.list(status: "published", limit: 20)
if let articles = result["articles"] as? [[String: Any]] {
    for a in articles { print(a["title"] ?? "", a["url"] ?? "") }
}

// Get one by slug (or UUID)
let article = try await blog.articles.get(slug: "my-first-article")

// Publish
let created = try await blog.articles.publish(data: [
    "title": "Getting Started with the MisarBlog API",
    "body_markdown": "## Introduction\n\nThis guide covers…",
    "tags": ["swift", "api"],
    "cover_image_url": "https://example.com/cover.jpg",
    "visibility": "public",
])

// Save a draft, then update / publish it
let draft = try await blog.articles.createDraft(data: [
    "title": "Work in progress", "body_markdown": "## Soon…",
])
_ = try await blog.articles.update(slug: draft["slug"] as! String, data: ["publish": true])

Decode any payload into a typed value with Article.from(_:) / Series.from(_:):

let typed = try Article.from(article)
print(typed.title ?? "", typed.slug ?? "")

Other resources

try await blog.series.list()
try await blog.series.create(title: "A Series", description: "Optional")

try await blog.ai.titles(action: "seo", prompt: "best AI writing tools 2026")
try await blog.ai.complete(prompt: "Tighten this intro: …", system: "You are an editor.", maxTokens: 300)

try await blog.images.generate(prompt: "A minimalist cover", size: "1792x1024")

try await blog.search.query(q: "react", type: "articles", sort: "newest")
try await blog.recommendations.get(articleId: articleId, limit: 5)

try await blog.analytics.get(days: 30)
try await blog.profile.get()     // GET /me
try await blog.plan.get()
try await blog.trial.status()

try await blog.reactions.get(articleId: articleId)
try await blog.reactions.add(articleId: articleId, type: "like")     // "like" | "clap" | "bookmark"
try await blog.reactions.remove(articleId: articleId, type: "like")

Resources

ResourceMethods
blog.articleslist · get · publish · update · createDraft
blog.serieslist · create · addArticle
blog.aititles · complete
blog.imagesgenerate · upload
blog.searchquery
blog.recommendationsget
blog.analyticsget
blog.profileget — GET /me
blog.planget
blog.trialstatus · start
blog.reactionsget · add · remove
blog.upsellfunnel — platform-admin only

Error handling

do {
    _ = try await blog.articles.get(slug: "missing-slug")
} catch let error as MisarBlogError {
    print(error)   // .apiError(status:message:requiredScope:grantedScopes:) or .networkError
}

Embed helpers

The no-key iframe helpers live on the MisarBlog type:

import MisarBlog

let client = MisarBlog()

// Profile embed
let url = client.embedURL(username: "johndoe")

// Single-post embed with a theme
let postURL = client.embedURL(username: "johndoe", slug: "my-first-post", theme: "dark")

// For gated (paywalled) embeds, refresh the session token
let result = try await client.refreshToken(token: "current-session-token")
print(result.token, result.expiresAt)

embedURL(username:slug:theme:) -> URL returns a public embed-viewer URL (theme defaults to "auto"). refreshToken(token:baseURL:) async throws -> TokenResult returns a TokenResult(token, expiresAt); baseURL defaults to "https://misar.blog".