Lightweight, zero-dependency Go SDK for Bitrix24 REST API with built-in rate limiting, circuit breaker, and batch support.
- Rate Limiting: Leaky bucket algorithm respecting Bitrix24 limits
- Operating Time Tracking: Monitors server-side execution time to avoid OPERATION_TIME_LIMIT errors
- Circuit Breaker: Protects against cascading failures
- Batch Requests: Execute up to 50 commands in a single request
- Pagination: Iterator-based pagination with generics support
- Token Refresh: Automatic OAuth token refresh with callback
- Custom Logger: Pluggable logging interface
- Zero External Dependencies: Only uses Go standard library
- Go 1.25+
go get github.com/vbcherepanov/bitrix24gosdkpackage main
import (
"context"
"fmt"
"log"
b24 "github.com/vbcherepanov/bitrix24gosdk"
)
func main() {
client, err := b24.New(
b24.WithWebhook("https://your-company.bitrix24.ru/rest/1/your_webhook_token/"),
)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Get current user
resp, err := client.Call(ctx, "user.current", nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Result: %s\n", resp.Result)
}client, err := b24.New(
b24.WithOAuth("your-company", "your_access_token"),
b24.WithOAuthCredentials("client_id", "client_secret", "refresh_token"),
b24.WithTokenRefreshCallback(func(access, refresh string, expiresAt time.Time) {
// Save new tokens to database
log.Printf("Token refreshed, expires at: %v", expiresAt)
}),
)client, err := b24.New(
b24.WithOnPremise("https://your-bitrix.company.com/rest/", "access_token"),
b24.WithEnterprise(), // Uses higher rate limits (250 bucket, 5 req/sec)
)// Using standard logger
logger := b24.NewStdLogger(
b24.WithLevel(b24.LogLevelDebug),
b24.WithPrefix("[bitrix24] "),
)
client, err := b24.New(
b24.WithWebhook("https://..."),
b24.WithLogger(logger),
)
// Using log/slog
import "log/slog"
slogLogger := slog.Default()
client, err := b24.New(
b24.WithWebhook("https://..."),
b24.WithLogger(b24.NewSlogAdapter(slogLogger.Log)),
)
// Implement custom logger
type MyLogger struct{}
func (l *MyLogger) Debug(ctx context.Context, msg string, keysAndValues ...any) { /* ... */ }
func (l *MyLogger) Info(ctx context.Context, msg string, keysAndValues ...any) { /* ... */ }
func (l *MyLogger) Warn(ctx context.Context, msg string, keysAndValues ...any) { /* ... */ }
func (l *MyLogger) Error(ctx context.Context, msg string, keysAndValues ...any) { /* ... */ }The SDK automatically handles Bitrix24 rate limits:
| Plan | Bucket Size | Drain Rate |
|---|---|---|
| Cloud | 50 | 2 req/sec |
| Enterprise | 250 | 5 req/sec |
Bitrix24 limits server-side execution time to 480 seconds per method within 10 minutes. The SDK tracks this automatically.
resp, err := client.Call(ctx, "user.get", map[string]any{
"ID": 1,
})
var user User
if err := json.Unmarshal(resp.Result, &user); err != nil {
log.Fatal(err)
}var user User
err := client.CallJSON(ctx, "user.get", map[string]any{
"ID": 1,
}, &user)resp, err := client.Call(ctx, "tasks.task.list", map[string]any{
"filter": map[string]any{
"STATUS": []int{2, 3}, // In progress, pending
"RESPONSIBLE_ID": 1,
">DEADLINE": "2024-01-01",
},
"select": []string{"ID", "TITLE", "STATUS", "DEADLINE"},
"order": map[string]string{"DEADLINE": "asc"},
"start": 0,
})Execute multiple commands in a single request (max 50):
builder := b24.NewBatchBuilder()
// Add commands with auto-generated IDs
id1 := builder.Add("user.get", map[string]any{"ID": 1})
id2 := builder.Add("user.get", map[string]any{"ID": 2})
builder.Add("tasks.task.list", map[string]any{
"filter": map[string]any{"RESPONSIBLE_ID": 1},
})
// Execute batch
resp, err := client.Batch(ctx, builder)
if err != nil {
log.Fatal(err)
}
// Get specific result
var user User
if err := resp.GetResult(id1, &user); err != nil {
log.Fatal(err)
}
// Check for errors
if resp.HasErrors() {
for id, apiErr := range resp.ResultErrors {
log.Printf("Command %s failed: %s", id, apiErr)
}
}builder := b24.NewBatchBuilder()
builder.AddWithID("tasks", "tasks.task.list", map[string]any{"limit": 1})
builder.AddWithID("user", "user.get", map[string]any{
// Reference first task's responsible ID
"ID": "$result[tasks][tasks][0][responsibleId]",
})builder := b24.NewBatchBuilder()
builder.SetHaltOnError(true) // Stop on first error
builder.Add("user.get", map[string]any{"ID": 1})
builder.Add("user.get", map[string]any{"ID": 999999}) // Will fail
builder.Add("user.get", map[string]any{"ID": 2}) // Won't executetype User struct {
ID int `json:"ID"`
Name string `json:"NAME"`
LastName string `json:"LAST_NAME"`
}
params := &b24.ListParams{
Filter: map[string]any{"ACTIVE": true},
Select: []string{"ID", "NAME", "LAST_NAME"},
}
it := b24.ListIterator(
client,
"user.get",
params,
b24.ParseArrayResult[User],
)
for it.Next(ctx) {
user := it.Item()
fmt.Printf("User: %s %s\n", user.Name, user.LastName)
}
if err := it.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("Total users: %d\n", it.Total())users, err := b24.ListAll(
ctx,
client,
"user.get",
&b24.ListParams{Filter: map[string]any{"ACTIVE": true}},
b24.ParseArrayResult[User],
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Fetched %d users\n", len(users))page, err := b24.FetchPage(
ctx,
client,
"user.get",
&b24.ListParams{Start: 0},
b24.ParseArrayResult[User],
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Items: %d, Total: %d, HasMore: %v\n",
len(page.Items), page.Total, page.HasMore)For methods like tasks.task.list that wrap results in {"tasks": [...]}:
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
}
tasks, err := b24.ListAll(
ctx,
client,
"tasks.task.list",
nil,
b24.ParseTasksResult[Task], // Uses "tasks" wrapper
)resp, err := client.Call(ctx, "user.get", map[string]any{"ID": 1})
if err != nil {
// Check specific errors
switch {
case b24.IsRateLimitError(err):
log.Println("Rate limited, waiting...")
time.Sleep(5 * time.Second)
case b24.IsOperationTimeLimitError(err):
log.Println("Operation time exceeded, try later")
case b24.IsTokenExpiredError(err):
log.Println("Token expired")
case errors.Is(err, b24.ErrCircuitOpen):
log.Println("Circuit breaker open, service unavailable")
case errors.Is(err, b24.ErrNotFound):
log.Println("Entity not found")
}
// Get detailed error info
var apiErr *b24.APIError
if errors.As(err, &apiErr) {
log.Printf("API Error [%s]: %s\n", apiErr.Code, apiErr.Description)
log.Printf("Method: %s, Status: %d\n", apiErr.Method, apiErr.StatusCode)
if apiErr.IsRetryable() {
// Retry logic
}
}
return
}// Authentication errors
b24.ErrCodeExpiredToken // "expired_token"
b24.ErrCodeInvalidToken // "invalid_token"
b24.ErrCodeNoAuthToken // "NO_AUTH_TOKEN"
b24.ErrCodeAccessDenied // "ACCESS_DENIED"
// Rate limiting
b24.ErrCodeQueryLimitExceeded // "QUERY_LIMIT_EXCEEDED"
b24.ErrCodeOperationTimeLimit // "OPERATION_TIME_LIMIT"
// Request errors
b24.ErrCodeNotFound // "NOT_FOUND"
b24.ErrCodeInvalidParameter // "INVALID_PARAMETER"stats := client.GetStats()
fmt.Printf("Throttler tokens available: %d\n", stats.ThrottlerTokens)
fmt.Printf("Circuit breaker state: %s\n", stats.CircuitBreaker.State)
fmt.Printf("Circuit breaker failures: %d\n", stats.CircuitBreaker.Failures)client, err := b24.New(
// Authentication (choose one)
b24.WithWebhook("https://..."), // Webhook URL
b24.WithOAuth("domain", "access_token"), // OAuth
b24.WithOnPremise("https://...", "access_token"), // On-premise
// OAuth credentials for token refresh
b24.WithOAuthCredentials("client_id", "client_secret", "refresh_token"),
// Token management
b24.WithTokenExpiration(time.Now().Add(time.Hour)),
b24.WithTokenRefreshCallback(func(access, refresh string, expiresAt time.Time) {
// Save tokens
}),
// Components
b24.WithLogger(logger),
b24.WithHTTPClient(&http.Client{Timeout: time.Minute}),
// Behavior
b24.WithTimeout(30 * time.Second),
b24.WithEnterprise(), // Higher rate limits
)bitrix24gosdk/
├── go.mod
├── client.go # Main HTTP client with Call, Batch, Pagination
├── options.go # Functional options and New() constructor
├── errors.go # Error types, codes, and helpers
├── logger.go # Logger interface and implementations
├── doc.go # Package documentation
└── internal/
├── batch/ # Batch request builder and executor
├── circuit/ # Circuit breaker implementation
├── constants/ # All constants and limits
├── optime/ # Operating time tracker
├── pagination/ # Pagination iterators and helpers
└── throttle/ # Rate limiter (leaky bucket)
MIT License