Introducing the FeedbackKit Vapor SDK
Today we are introducing Vapor SDK 1.0, a Swift package that lets a server built with Vapor 4 talk to FeedbackKit. The app SDKs put a request board in front of your users. This one is for the code behind them: your backend can file a request, list the board for a web page of your own, record a vote or a comment, tell FeedbackKit what each user pays, and, with an account token, manage projects, members and statuses.
The short version
- One configure call in
configure.swift, thenreq.feedbackKitin any route. - Project-key operations: list, get and file requests, vote and unvote, list and add comments, and register a user with their MRR.
- Account-token operations through
req.feedbackKitAdmin(token:): projects, members and invites, status changes and merges, and the ticket integrations. - Errors are a
FeedbackKitErrorthat conforms to Vapor'sAbortError, so a failure thrown from a route becomes the matching HTTP status. - It needs Swift 6.2 and Vapor 4.89 or later. 1.0.0 has no git tag, so you pin it by commit, as shown below.
What is in Vapor SDK 1.0
One configure call
app.feedbackKit.configure(apiKey:environment:) stores your project's API key and the server to talk to. After that, req.feedbackKit gives you the project-key client inside a route, built on the request's own HTTP client and logger. Outside a request, in a lifecycle handler or a scheduled job, app.feedbackKit.client() returns the same client. Either one throws FeedbackKitError.notConfigured if the configure call has not run.
Project-key operations
These send your project's key in the X-API-Key header, the same key the app SDKs use:
feedback.list(status:category:includeMerged:userId:),feedback.get(id:userId:)andfeedback.create(_:). Pass auserIdto a read and each request'shasVotedanswers for that user.votes.vote(feedbackId:request:)andvotes.unvote(feedbackId:request:). ACreateVoteRequestcarries the user id, and can carry an email and anotifyStatusChangeopt-in.comments.list(feedbackId:)andcomments.create(feedbackId:request:).users.register(_:), which records a user id with an optional MRR, so your team can rank requests by the revenue behind them.
Responses are typed: a FeedbackResponse has the status, category, vote and comment counts, the total MRR behind the request, and the link to any ticket it was sent to.
Account-token operations
req.feedbackKitAdmin(token:) returns a client that sends a Bearer token for a FeedbackKit account instead of a project key. auth.login(_:) returns such a token. With it, your server can do what the account could do in the admin app:
- Projects: list, create, update, archive and unarchive, regenerate the API key, transfer ownership, and set the Slack settings and the statuses a project uses.
- Members: list, add, change a role, remove, and manage invites.
- Requests:
feedback.update(id:_:)changes a status, title, category or rejection reason, andfeedback.merge(_:)folds duplicates into one request. - Integrations: settings, single and bulk ticket creation for GitHub, ClickUp, Notion, Monday.com, Linear, Trello, Airtable, Asana and Basecamp, plus the Email Campaign settings.
The server applies the same rules as everywhere else: an account's role and plan decide what each call may do, and a call past a plan limit answers with FeedbackKitError.paymentRequired.
Errors
FeedbackKitError has a case per answer: badRequest, unauthorized, invalidApiKey, paymentRequired, forbidden, notFound, conflict (for example a repeat vote), serverError and decodingError. Because it conforms to AbortError, a route can let it propagate and Vapor answers with the same status code and a readable reason.
Installing 1.0
1.0.0 has no git tag. The package lives in the SwiftlyFeedbackKit-Vapor-SDK repository, and the 1.0.0 code is its commit e2be1ee, from 14 February. The earliest tag in that repository is 1.1.0, so Swift Package Manager cannot resolve 1.0.0 as a version. To install 1.0 itself, pin the commit. Add it next to your Vapor dependency in Package.swift:
.package(
url: "https://github.com/Swiftly-Developed/SwiftlyFeedbackKit-Vapor-SDK.git",
revision: "e2be1ee98fef47e3d0f88745084d74e9d11fad0f"
),
and add the product to your app target's dependencies:
.product(name: "SwiftlyFeedbackKitVapor", package: "SwiftlyFeedbackKit-Vapor-SDK"),
The package's manifest uses Swift tools 6.2 and sets macOS 13 as its minimum Apple platform.
Getting started
Keep the key in an environment variable and configure once:
import Vapor
import SwiftlyFeedbackKitVapor
public func configure(_ app: Application) async throws {
app.feedbackKit.configure(
apiKey: Environment.get("FEEDBACKKIT_API_KEY") ?? "",
environment: .custom(URL(string: "https://api.feedbackkit.prod.swiftly-developed.com")!)
)
try routes(app)
}
Which address. In 1.0.0, .production points at getfeedbackkit.com, the website's address. The FeedbackKit server your projects live on answers at api.feedbackkit.prod.swiftly-developed.com, the address the Swift SDK uses, so pass that with .custom. The package adds /api/v1 itself. .local is for running a FeedbackKit server on your own machine.
Then use it from a route. Trailmate, a fictional hiking app, lets people suggest a feature from its website, and its server lists the approved requests and files new ones under the visitor's account id:
struct NewIdea: Content {
let title: String
let description: String
let accountID: String
}
func routes(_ app: Application) throws {
app.get("ideas") { req async throws -> [FeedbackResponse] in
try await req.feedbackKit.feedback.list(status: .approved)
}
app.post("ideas") { req async throws -> FeedbackResponse in
let idea = try req.content.decode(NewIdea.self)
return try await req.feedbackKit.feedback.create(CreateFeedbackRequest(
title: idea.title,
description: idea.description,
category: .featureRequest,
userId: idea.accountID
))
}
}
To weigh votes by revenue, register what each account pays when their subscription changes:
_ = try await req.feedbackKit.users.register(
RegisterSDKUserRequest(userId: account.id, mrr: 4.99)
)
Use the same user id your app passes to its FeedbackKit SDK, so a person's votes and requests from the app and from your server count as one user. The reference for every call is in the Vapor SDK docs.
How FeedbackKit fits in
Requests your server files land on the same board as the ones filed in your apps. Your team reads them in the FeedbackKit admin app for iPhone, iPad and Mac, or in the web admin, merges duplicates, replies, sorts by votes or by the MRR behind them, and moves a request through Approved, In Progress and TestFlight to Completed. A submitter who left an email is told by email when the status changes, and the team gets a push notification for new requests and comments. See pricing for what each plan includes.
Here is how triage looks in FeedbackKit, with sample data from Trailmate:
Requests your team sorts
The other SDKs
For the app side, the Swift SDK puts the board in iPhone, iPad, Mac and Vision Pro apps, and the Kotlin SDK does the same on Android.
Every plan includes the Vapor SDK. Start free, create a project, and connect your server to it.