The Feishu Go SDK bundles token handling, signing and per product service packages
larksuite oapi sdk by golang
At a glance
- What is it?
- larksuite/oapi-sdk-go is the MIT licensed Go server SDK for the Feishu and Lark Open Platform, with a service package per product area, a WebSocket based Channel module and a device flow helper that creates apps for you. It suits a Go team building bots and internal integrations, and its app registration helper and addon scopes are pre-filled on a confirmation page rather than validated by the SDK.
- Who is it for?
- larksuite/oapi-sdk-go fits a Go team that needs the Feishu or Lark Open Platform and would rather not maintain access tokens, encryption and signature verification by hand, especially if the work is a bot or an internal workflow service where the Channel module and typed service packages remove most of the boilerplate.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A Go SDK for the Feishu Open Platform, and the friction it removes
The repository is the Go server SDK for the Feishu Open Platform, and the name of the module tells you the generation you are looking at: github.com/larksuite/oapi-sdk-go/v3, with a go.mod that declares go 1.17. The project documentation describes the platform as offering a series of server-side atomic APIs, and then names the real friction in using them: obtaining and maintaining access tokens, encrypting and decrypting data, and verifying request signatures. It adds a second complaint about the lack of semantic descriptions for function calls and the lack of type system support, both of which push work onto the developer.
The SDK is the answer to that list. It folds the token handling, the cryptography and the signature verification into the library, adds a type system for the request and response payloads, and gives every operation a name that says what it does. So the target user is a Go developer building a server-side integration with Feishu or Lark, typically an internal bot or a workflow service, who does not want to re-implement the authentication handshake for every endpoint.
There is a second product inside the same repository, which is easy to miss: the one-click app registration flow. That is a tool for creating an app through the platform without going through the developer console by hand, and it is covered in detail further down.
One service package per product area, from im to docx
The repository tree is a map of the platform's API surface, and the directory names are the clearest description of what the SDK covers.
There is a service/ directory with one package per product area: im for messaging, drive, docx, sheets, calendar, meeting_room, contact, approval, attendance, bitable, admin, application, acs, baike, ehr, event, human_authentication, mail, optical_char_recognition, passport, search, and more, with the go.mod listing the same set as separate modules in its commented-out replace block. Each of those packages holds the request and response types for that area, which is what makes the calls semantic rather than a single generic HTTP helper.
Around those sit the shared pieces. core/ holds the client plumbing, the token handling and the configuration, client.go is the entry point for building a client, event/ handles incoming event callbacks, card/ handles interactive card messages, channel/ is the WebSocket based module, ws/ is the lower level WebSocket support, cache/ is the credential cache, and scene/ holds higher level flows such as the registration scene.
Then there is sample/, which is a small workspace of its own with its own go.mod, containing directories such as accesstoken, api, apiall, callrawapi, card, channel, channel_test_cases, client, event, mocksendcardaction, mocksendevent and ws. The presence of apiall and callrawapi next to api is a useful detail: it means you can call a single endpoint, enumerate everything, or drop to a raw API call when the typed wrapper does not cover what you need.
The Channel module wraps the WebSocket event stream
The Channel module is the piece most Go developers will actually build on, and it is worth reading the module's own documentation before the raw event handling.
The project documentation describes it as built on top of WebSocket and the API Client, encapsulating event listening, message normalization, streaming replies and media uploads, with the stated aim that developers focus purely on business logic. There is a doc/channel.md and a doc/channel.zh.md in the repository, so the English documentation for that module ships with the code.
The distinction between the Channel module and the lower level event/ and ws/ packages is the useful part. The ws/ package gives you a connection to the platform's long connection service, event/ gives you the parsing and dispatch of the callbacks that arrive on it, and the Channel module sits on top with normalization, reply helpers and uploads already wired together.
That layering is also the difference between the SDK and a hand written integration. Writing a bot that receives events over the long connection service means handling reconnection, message types, and reply formats yourself. With the Channel module, the event handler receives a normalized message and the answer to a question is a reply call. The cost is that you inherit the module's opinion about what a normalized message looks like, which is fine until your use case needs the raw event, and then the sample/mocksendevent and sample/channel_test_cases directories show the way back down.
RegisterApp runs the device grant and hands back credentials
registration.RegisterApp creates an app for you through the OAuth 2.0 Device Authorization Grant, RFC 8628, and the flow is a polling one rather than a redirect.
You call the function with a context and a set of options, it prints or renders a verification URL, the user opens it in Feishu or Lark or scans a QR code, and after the user authorizes, the app is created and the SDK returns the App ID and App Secret.
The options carry a Source identifier, which is appended to the QR URL as go-sdk/{source} and defaults to go-sdk, and Domain and LarkDomain for pointing at a different accounts host, where the Lark one is used when a tenant_brand=lark value is detected. The Domain field accepts a full base URL, with https://accounts.feishu.cn as the default.
The polling loop reports itself through callbacks. OnQRCode receives a QRCodeInfo with the URL and the expiry in seconds, and OnStatusChange receives a StatusChangeInfo whose Status is one of polling, slow_down or domain_switched, with an Interval telling you when to poll next. That slow_down status is the device grant's own backoff signal, and the sample handles it by printing the status and the next interval rather than pretending the cadence is fixed.
The context controls the whole flow, and cancelling it stops the polling, which is why the sample sets a ten minute timeout.
Addons pre-fill scopes, events and callbacks on the page
The Addons mechanism lets you declare permissions, event subscriptions and callbacks at creation time, and the documentation is unusually explicit about its limits.
Options.Addons carries three lists. Scopes are split into Tenant and User, with entries like im:message:send_as_bot for a tenant scope and calendar:calendar:read for a user scope. Events carry Items with a Tenant list such as im.message.receive_v1. Callbacks carry Items such as card.action.trigger. The values are pre-filled into the confirmation page after the user opens the QR code URL, and they take effect once the user confirms.
The stated limits matter. Addons is additive only, so it cannot remove configuration from the base template. The SDK validates shape and non-empty strings, but it does not validate whether a scope, event or callback name exists, so a typo is not caught until the platform rejects it.
Two more switches complete the picture. CreateOnly=true restricts the call to creating a new app, and setting Options.AppID starts the update flow for an existing app instead, which is how you add scopes to an app that is already in place. The second RegisterApp call in the sample does exactly that, passing AppID: "cli_xxx" with a single tenant scope.
Addons: ®istration.AppAddons{
Scopes: registration.AppAddonsScopes{
Tenant: []string{"im:message:send_as_bot"},
User: []string{"calendar:calendar:read"},
},
Events: registration.AppAddonsEvents{
Items: registration.AppAddonsEventItems{
Tenant: []string{"im.message.receive_v1"},
},
},
Callbacks: registration.AppAddonsCallbacks{
Items: []string{"card.action.trigger"},
},
},Preset picks the base template the app is created from
Choosing the base template is what AppAddons.Preset controls, and it is a separate thing from Options.AppPreset, which pre-fills the app name, description and avatar.
The table in the documentation gives three cases. An unset Preset, meaning nil, uses the platform default template and the encoded payload carries no preset key, so the behaviour is the same as before the field existed. Preset set to true explicitly requests that same platform default. Preset set to false selects a minimal base template, where the confirmation page shows only the scopes, events and callbacks declared in Addons.
The minimal template is the one with a consequence worth knowing. With Preset set to false, an Addons that declares no scope, event or callback at all is still valid, the page still enters the confirmation flow, and the app is created with only the minimal base capabilities. That is the way to create an app you intend to configure later.
The sample for it is short, since the only thing to set is the preset and the QR callback.
preset := false
_, err := registration.RegisterApp(ctx, ®istration.Options{
Addons: ®istration.AppAddons{
Preset: &preset,
},
OnQRCode: func(info *registration.QRCodeInfo) {
fmt.Println(info.URL)
},
})
if err != nil {
panic(err)
}Building a client and reading the samples directory
The ordinary path through the SDK is shorter than the registration flow, and it starts with the module path and a client.
The module is github.com/larksuite/oapi-sdk-go/v3, and the requirement is Go 1.17 or later. Once the module is in your go.mod, you build a client with lark.NewClient, which takes the app ID and the app secret, and then call the typed service packages for the endpoint you want. The registration sample ends with exactly that, creating a client from the credentials it just obtained.
There is no configuration file to write and no global initialisation to call. The token handling and the caching are inside the client and the core package, which is the whole point of the SDK existing.
The samples directory is where the real usage documentation lives. There is a client directory for the basic setup, an accesstoken directory for working with tokens directly, api for a single endpoint, apiall for enumerating, callrawapi for escaping the typed layer, event and ws for the callback side, card for interactive cards, and the channel directory for the WebSocket module. Each of these is a small runnable program, so the fastest way to learn the shape of an API is to open the sample for that area and change the parameters.
The module path also carries a versioning convention worth noting. The go.mod in the repository declares the module as github.com/larksuite/oapi-sdk-go/v3, and the listed releases in the project metadata are v2.0.11 from 2022-05-09, v1.1.44 from 2022-02-11 and v2.0.9 from 2021-12-06, with a default branch named v3_main. So the v3 line lives on a branch rather than in those tags, and the semantic import version in the path is what tells the go tooling which generation you are importing.
client := lark.NewClient(result.ClientID, result.ClientSecret)
_ = clientA v3 branch beside release tags that stop in 2022
The maintenance picture is a mixture, and it is the one place where the dates need care.
The repository is not archived, and the last push was on 2026-10-19, so work is landing on the v3_main branch. The release tags, on the other hand, are old: v2.0.11 from May 2022, whose note mentions a custom bot send card message change, v1.1.44 from February 2022 fixing a core.Context Value method, and v2.0.9 from December 2021 with a typo fix. There is also a changelog.md in the repository root.
So if you pin a version, you are pinning to the v2 era, and if you take the branch you are taking unreleased v3 work. That is a normal arrangement for a vendor SDK whose breaking changes land on a major version path, but it means you have to decide which line you are on rather than simply taking the latest tag.
The licence is MIT, with the LICENSE file at the repository root, which means no obligations beyond keeping the notice in a distribution. The README also exists in Simplified Chinese as README.zh.md, and the development guides are hosted on the platform's documentation site rather than in the repository, with pages for preparations before development, calling server-side APIs, handling events, handling card callbacks and an SDK FAQ section.
Editorial conclusion
larksuite/oapi-sdk-go fits a Go team that needs the Feishu or Lark Open Platform and would rather not maintain access tokens, encryption and signature verification by hand, especially if the work is a bot or an internal workflow service where the Channel module and typed service packages remove most of the boilerplate. It does not fit a team that needs a stable, tagged release, since the listed tags stop at v2.0.11 from 2022 while the v3 line lives on the v3_main branch, and it does not fit a team that expects the SDK to catch bad permission names, since the addon scopes are checked for shape but not for existence. Before adopting it, decide whether you import the v3 module path from the branch or a tagged v2 release, read the platform guides on preparations, calling server side APIs and handling events that the README points to, and confirm the service package for your product area exists under service/ before you design around it.
Frequently asked questions
What Go version does the larksuite oapi-sdk-go require?
The documentation lists Go 1.17 or later, and the go.mod in the repository declares go 1.17.
What does the Channel module in oapi-sdk-go provide?
It is built on WebSocket and the API client and encapsulates event listening, message normalization, streaming replies and media uploads, so handlers can focus on business logic.
How does registration.RegisterApp create a Feishu app?
It uses the OAuth 2.0 Device Authorization Grant, RFC 8628. It returns a verification URL that the user opens in Feishu or Lark or scans as a QR code, and after authorization the app is created and the App ID and App Secret are returned.
Can oapi-sdk-go add scopes to an app that already exists?
Yes, by passing Options.AppID, which starts the update flow for an existing app. The Addons configuration is additive only, and the SDK does not validate whether scope, event or callback names exist.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/larksuite-oapi-sdk-go)Was this useful?