api-integration-patterns

API Integration Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "api-integration-patterns" with this command: npx skills add linehaul-ai/linehaulai-claude-marketplace/linehaul-ai-linehaulai-claude-marketplace-api-integration-patterns

API Integration Patterns

Unified patterns for building robust, secure external API integrations in laneweaverTMS. These patterns apply across all integrations (QuickBooks, MyCarrierPackets, Slack, and future APIs).

When to Use This Skill

  • Implementing OAuth2 authentication with any external service

  • Building token refresh and storage mechanisms

  • Choosing between webhooks and polling for data sync

  • Implementing rate limiting and backoff strategies

  • Handling API errors consistently across integrations

  • Designing multi-tenant token isolation

  • Securing webhook endpoints

  • Writing integration tests for external APIs

OAuth2 Token Lifecycle

Token Types and Responsibilities

Token Type Purpose Typical Lifetime Storage Location

Access Token API authorization 1-3 hours Memory or encrypted DB

Refresh Token Obtain new access token 30-100 days Encrypted database only

ID Token User identity (OIDC) 1-24 hours Session or memory

Token Storage Pattern

Store tokens in encrypted database columns, never in JWTs or local storage.

type OAuthTokenStore struct { AccountID string db:"account_id" // Multi-tenant isolation Provider string db:"provider" // "quickbooks", "slack", etc. AccessToken string db:"access_token" // Encrypted at rest RefreshToken string db:"refresh_token" // Encrypted at rest ExpiresAt time.Time db:"expires_at" Scopes []string db:"scopes" // Granted permissions CreatedAt time.Time db:"created_at" UpdatedAt time.Time db:"updated_at" }

Proactive Token Refresh

Refresh tokens before expiry to avoid request failures. Refresh when 80% of lifetime has elapsed.

func (c *OAuthClient) getValidToken(ctx context.Context, accountID string) (string, error) { c.tokenMu.RLock() token, exists := c.tokens[accountID] c.tokenMu.RUnlock()

if !exists {
    return "", fmt.Errorf("no token for account %s", accountID)
}

// Proactive refresh: refresh at 80% of lifetime
// For 1-hour token, refresh after 48 minutes
refreshThreshold := token.ExpiresAt.Add(-time.Duration(float64(time.Hour) * 0.2))

if time.Now().After(refreshThreshold) {
    return c.refreshToken(ctx, accountID, token.RefreshToken)
}

return token.AccessToken, nil

}

func (c *OAuthClient) refreshToken(ctx context.Context, accountID, refreshToken string) (string, error) { c.tokenMu.Lock() defer c.tokenMu.Unlock()

// Double-check after acquiring lock (another goroutine may have refreshed)
if token, ok := c.tokens[accountID]; ok {
    if time.Now().Before(token.ExpiresAt.Add(-5 * time.Minute)) {
        return token.AccessToken, nil
    }
}

// Perform refresh with provider
newToken, err := c.provider.RefreshToken(ctx, refreshToken)
if err != nil {
    return "", fmt.Errorf("token refresh failed: %w", err)
}

// Store new tokens
c.tokens[accountID] = &OAuthTokenStore{
    AccountID:    accountID,
    AccessToken:  newToken.AccessToken,
    RefreshToken: newToken.RefreshToken, // May be new or same
    ExpiresAt:    newToken.ExpiresAt,
}

// Persist to database
if err := c.repo.SaveToken(ctx, c.tokens[accountID]); err != nil {
    return "", fmt.Errorf("failed to persist token: %w", err)
}

return newToken.AccessToken, nil

}

Token Refresh Flow Diagram

Request API → Check Token Expiry → [Not Expired] → Use Token → API Call ↓ [Near Expiry] ↓ Acquire Lock → Refresh Token → Store New Token → Use Token → API Call ↓ (on failure) Clear Token → Return Error → Prompt Re-authentication

Exponential Backoff

Standard Backoff Implementation

Use exponential backoff with jitter for all retryable operations.

import ( "math" "math/rand" "time" )

type BackoffConfig struct { BaseDelay time.Duration // Starting delay (e.g., 1 second) MaxDelay time.Duration // Maximum delay cap (e.g., 60 seconds) MaxRetries int // Maximum attempts (e.g., 5) JitterRatio float64 // Random jitter 0-1 (e.g., 0.2 for 20%) }

func DefaultBackoffConfig() BackoffConfig { return BackoffConfig{ BaseDelay: 1 * time.Second, MaxDelay: 60 * time.Second, MaxRetries: 5, JitterRatio: 0.2, } }

func (c BackoffConfig) Delay(attempt int) time.Duration { if attempt >= c.MaxRetries { return 0 // Signal to stop retrying }

// Calculate exponential delay: base * 2^attempt
delay := float64(c.BaseDelay) * math.Pow(2, float64(attempt))

// Cap at maximum
if delay > float64(c.MaxDelay) {
    delay = float64(c.MaxDelay)
}

// Add jitter: delay * (1 +/- jitterRatio)
jitter := delay * c.JitterRatio * (2*rand.Float64() - 1)
delay += jitter

return time.Duration(delay)

}

Retry Wrapper Pattern

func RetryWithBackoff[T any]( ctx context.Context, config BackoffConfig, operation func(context.Context) (T, error), isRetryable func(error) bool, ) (T, error) { var zero T var lastErr error

for attempt := 0; attempt < config.MaxRetries; attempt++ {
    result, err := operation(ctx)
    if err == nil {
        return result, nil
    }

    lastErr = err

    // Check if error is retryable
    if !isRetryable(err) {
        return zero, fmt.Errorf("non-retryable error: %w", err)
    }

    // Calculate delay
    delay := config.Delay(attempt)
    if delay == 0 {
        break // Max retries exceeded
    }

    // Wait with context cancellation support
    select {
    case <-ctx.Done():
        return zero, ctx.Err()
    case <-time.After(delay):
        // Continue to next attempt
    }
}

return zero, fmt.Errorf("max retries exceeded: %w", lastErr)

}

// Usage example result, err := RetryWithBackoff(ctx, DefaultBackoffConfig(), func(ctx context.Context) (*Response, error) { return client.MakeAPICall(ctx, request) }, func(err error) bool { // Retry on 429, 500, 502, 503, 504 var apiErr *APIError if errors.As(err, &apiErr) { return apiErr.StatusCode == 429 || apiErr.StatusCode >= 500 } return false }, )

Circuit Breaker Integration

Combine backoff with circuit breaker for cascading failure prevention.

type CircuitBreaker struct { failures int32 threshold int32 // Failures before opening resetAfter time.Duration // Time before attempting reset openedAt time.Time mu sync.RWMutex state CircuitState }

type CircuitState int

const ( CircuitClosed CircuitState = iota // Normal operation CircuitOpen // Failing, reject requests CircuitHalfOpen // Testing if service recovered )

func (cb *CircuitBreaker) Execute(operation func() error) error { cb.mu.RLock() state := cb.state cb.mu.RUnlock()

switch state {
case CircuitOpen:
    if time.Since(cb.openedAt) > cb.resetAfter {
        cb.mu.Lock()
        cb.state = CircuitHalfOpen
        cb.mu.Unlock()
    } else {
        return fmt.Errorf("circuit breaker open")
    }
}

err := operation()

cb.mu.Lock()
defer cb.mu.Unlock()

if err != nil {
    cb.failures++
    if cb.failures >= cb.threshold {
        cb.state = CircuitOpen
        cb.openedAt = time.Now()
    }
    return err
}

// Success - reset state
cb.failures = 0
cb.state = CircuitClosed
return nil

}

Webhook vs Polling Decision Matrix

Choose the right data synchronization strategy based on these factors.

Factor Use Webhooks Use Polling

Real-time requirement Immediate updates needed Minutes-to-hours delay acceptable

Webhook reliability Provider has high uptime Provider webhooks unreliable

Data volume Low-medium event frequency High volume (batch more efficient)

API support Provider offers webhooks No webhook support

Network reliability Your endpoint highly available Intermittent connectivity

Ordering guarantees Order not critical Strict ordering required

Initial sync Need polling for backfill Polling for all data

Cost Lower API calls More API calls but predictable

Hybrid Approach (Recommended)

Use webhooks for real-time updates with polling as backup for reliability.

type SyncManager struct { webhookHandler *WebhookHandler pollingInterval time.Duration lastSyncTime time.Time }

// Primary: Webhook receives real-time updates func (m *SyncManager) HandleWebhook(event WebhookEvent) error { if err := m.processEvent(event); err != nil { // Log for reconciliation during next poll m.logFailedEvent(event) return err } m.lastSyncTime = time.Now() return nil }

// Backup: Polling catches missed webhooks and handles initial sync func (m *SyncManager) Poll(ctx context.Context) error { changes, err := m.api.GetChangesSince(m.lastSyncTime) if err != nil { return err }

for _, change := range changes {
    if err := m.processChange(change); err != nil {
        return err
    }
}

m.lastSyncTime = time.Now()
return nil

}

// Run polling on interval as safety net func (m *SyncManager) StartBackgroundPolling(ctx context.Context) { ticker := time.NewTicker(m.pollingInterval) defer ticker.Stop()

for {
    select {
    case <-ctx.Done():
        return
    case <-ticker.C:
        if err := m.Poll(ctx); err != nil {
            log.Printf("polling error: %v", err)
        }
    }
}

}

Rate Limiting Patterns

Reading Rate Limit Headers

Most APIs return rate limit information in response headers.

type RateLimitInfo struct { Limit int // Max requests allowed Remaining int // Requests remaining in window Reset time.Time // When limit resets }

func ParseRateLimitHeaders(resp *http.Response) *RateLimitInfo { info := &RateLimitInfo{}

// Common header patterns
if limit := resp.Header.Get("X-RateLimit-Limit"); limit != "" {
    info.Limit, _ = strconv.Atoi(limit)
}
if remaining := resp.Header.Get("X-RateLimit-Remaining"); remaining != "" {
    info.Remaining, _ = strconv.Atoi(remaining)
}
if reset := resp.Header.Get("X-RateLimit-Reset"); reset != "" {
    // Unix timestamp
    if ts, err := strconv.ParseInt(reset, 10, 64); err == nil {
        info.Reset = time.Unix(ts, 0)
    }
}

// Slack uses different header
if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
    if seconds, err := strconv.Atoi(retryAfter); err == nil {
        info.Reset = time.Now().Add(time.Duration(seconds) * time.Second)
    }
}

return info

}

Token Bucket Rate Limiter

Client-side rate limiting to avoid hitting server limits.

type TokenBucket struct { tokens float64 maxTokens float64 refillRate float64 // Tokens per second lastRefill time.Time mu sync.Mutex }

func NewTokenBucket(maxTokens, refillRate float64) *TokenBucket { return &TokenBucket{ tokens: maxTokens, maxTokens: maxTokens, refillRate: refillRate, lastRefill: time.Now(), } }

func (tb *TokenBucket) Take(ctx context.Context) error { tb.mu.Lock() defer tb.mu.Unlock()

// Refill tokens based on elapsed time
now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
tb.tokens = math.Min(tb.maxTokens, tb.tokens+elapsed*tb.refillRate)
tb.lastRefill = now

if tb.tokens >= 1 {
    tb.tokens--
    return nil
}

// Calculate wait time for next token
waitTime := time.Duration((1 - tb.tokens) / tb.refillRate * float64(time.Second))

select {
case <-ctx.Done():
    return ctx.Err()
case <-time.After(waitTime):
    tb.tokens = 0 // Consumed the partial token + waited
    return nil
}

}

Request Queue with Priority

Queue requests with priority handling for rate-limited APIs.

type PriorityRequest struct { Priority int // Lower = higher priority Request func(context.Context) error Done chan error }

type RequestQueue struct { queue *heap.Heap[*PriorityRequest] rateLimiter *TokenBucket }

func (q *RequestQueue) Submit(ctx context.Context, priority int, req func(context.Context) error) error { done := make(chan error, 1) q.queue.Push(&PriorityRequest{ Priority: priority, Request: req, Done: done, })

select {
case <-ctx.Done():
    return ctx.Err()
case err := <-done:
    return err
}

}

func (q *RequestQueue) Process(ctx context.Context) { for { select { case <-ctx.Done(): return default: item := q.queue.Pop() if item == nil { time.Sleep(10 * time.Millisecond) continue }

        // Wait for rate limit token
        if err := q.rateLimiter.Take(ctx); err != nil {
            item.Done &#x3C;- err
            continue
        }

        // Execute request
        item.Done &#x3C;- item.Request(ctx)
    }
}

}

Error Handling Taxonomy

HTTP Status Code Response Matrix

Status Category Meaning Action

400 Client Error Bad request syntax Fix request, do not retry

401 Auth Error Token expired/invalid Refresh token, retry once

403 Auth Error Forbidden Check permissions, log, do not retry

404 Client Error Resource not found Handle gracefully, do not retry

409 Client Error Conflict (stale data) Re-fetch, merge, retry

422 Client Error Validation failed Fix data, do not retry

429 Rate Limit Too many requests Backoff, retry with delay

500 Server Error Internal error Retry with backoff

502 Server Error Bad gateway Retry with backoff

503 Server Error Service unavailable Retry with backoff

504 Server Error Gateway timeout Retry with backoff

Unified Error Type

type APIError struct { StatusCode int json:"statusCode" Code string json:"code" // Provider-specific code Message string json:"message" Details map[string]any json:"details,omitempty" Provider string json:"provider" // "quickbooks", "slack", etc. RequestID string json:"requestId,omitempty" Retryable bool json:"retryable" RetryAfter *time.Duration json:"retryAfter,omitempty" }

func (e *APIError) Error() string { return fmt.Sprintf("[%s] %d %s: %s", e.Provider, e.StatusCode, e.Code, e.Message) }

func ClassifyError(statusCode int, provider string, body []byte) *APIError { err := &APIError{ StatusCode: statusCode, Provider: provider, }

// Parse provider-specific error format
switch provider {
case "quickbooks":
    err.parseQuickBooksError(body)
case "slack":
    err.parseSlackError(body)
case "mycarrierpackets":
    err.parseMCPError(body)
}

// Determine retryability
switch statusCode {
case 429:
    err.Retryable = true
    // Parse Retry-After if present
case 500, 502, 503, 504:
    err.Retryable = true
case 401:
    err.Retryable = true // After token refresh
default:
    err.Retryable = false
}

return err

}

Error Handler Pattern

func (c *APIClient) handleResponse(resp *http.Response, body []byte) error { if resp.StatusCode >= 200 && resp.StatusCode < 300 { return nil }

apiErr := ClassifyError(resp.StatusCode, c.provider, body)
apiErr.RequestID = resp.Header.Get("X-Request-ID")

switch resp.StatusCode {
case 401:
    // Clear cached token, trigger refresh
    c.clearToken()
    return fmt.Errorf("authentication required: %w", apiErr)

case 429:
    // Parse retry timing
    if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
        if seconds, err := strconv.Atoi(retryAfter); err == nil {
            d := time.Duration(seconds) * time.Second
            apiErr.RetryAfter = &#x26;d
        }
    }
    return apiErr

default:
    return apiErr
}

}

Multi-Tenant Token Isolation

Database Schema

Each tenant (account/workspace) has isolated credentials.

CREATE TABLE oauth_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), account_id UUID NOT NULL REFERENCES accounts(id), provider VARCHAR(50) NOT NULL, -- 'quickbooks', 'slack', 'mycarrierpackets'

-- Encrypted token storage
access_token_encrypted BYTEA NOT NULL,
refresh_token_encrypted BYTEA NOT NULL,

-- Token metadata
expires_at TIMESTAMPTZ NOT NULL,
scopes TEXT[],

-- Provider-specific identifiers
external_id VARCHAR(255),       -- realm_id, team_id, etc.
external_user_id VARCHAR(255),  -- user who authorized

-- Audit
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

-- Ensure one token per account per provider
UNIQUE(account_id, provider)

);

-- Index for token lookup CREATE INDEX idx_oauth_tokens_account_provider ON oauth_tokens(account_id, provider);

Token Isolation in Code

type MultiTenantTokenManager struct { repo TokenRepository encryptor Encryptor cache sync.Map // account_id:provider -> *OAuthTokenStore }

func (m *MultiTenantTokenManager) GetToken(ctx context.Context, accountID, provider string) (*OAuthTokenStore, error) { cacheKey := fmt.Sprintf("%s:%s", accountID, provider)

// Check cache
if cached, ok := m.cache.Load(cacheKey); ok {
    token := cached.(*OAuthTokenStore)
    if time.Now().Before(token.ExpiresAt) {
        return token, nil
    }
}

// Load from database
encrypted, err := m.repo.GetToken(ctx, accountID, provider)
if err != nil {
    return nil, err
}

// Decrypt
token := &#x26;OAuthTokenStore{
    AccountID:    accountID,
    Provider:     provider,
    AccessToken:  m.encryptor.Decrypt(encrypted.AccessTokenEncrypted),
    RefreshToken: m.encryptor.Decrypt(encrypted.RefreshTokenEncrypted),
    ExpiresAt:    encrypted.ExpiresAt,
    Scopes:       encrypted.Scopes,
}

// Cache
m.cache.Store(cacheKey, token)

return token, nil

}

// CRITICAL: Always scope API clients to specific account func (m *MultiTenantTokenManager) GetClientForAccount(ctx context.Context, accountID, provider string) (*APIClient, error) { token, err := m.GetToken(ctx, accountID, provider) if err != nil { return nil, err }

// Client is scoped to this account's token
return NewAPIClient(provider, token.AccessToken, token.ExternalID), nil

}

Webhook Security

Signature Verification

Always verify webhook signatures to prevent spoofing.

import ( "crypto/hmac" "crypto/sha256" "encoding/hex" )

type WebhookVerifier struct { secrets map[string]string // provider -> secret }

func (v *WebhookVerifier) Verify(provider string, payload []byte, signature string, timestamp string) error { secret, ok := v.secrets[provider] if !ok { return fmt.Errorf("unknown provider: %s", provider) }

switch provider {
case "slack":
    return v.verifySlack(payload, signature, timestamp, secret)
case "quickbooks":
    return v.verifyQuickBooks(payload, signature, secret)
default:
    return v.verifyHMACSHA256(payload, signature, secret)
}

}

// Slack uses version:timestamp:body format func (v WebhookVerifier) verifySlack(payload []byte, signature, timestamp, secret string) error { // Check timestamp freshness (prevent replay attacks) ts, err := strconv.ParseInt(timestamp, 10, 64) if err != nil { return fmt.Errorf("invalid timestamp") } if time.Since(time.Unix(ts, 0)) > 5time.Minute { return fmt.Errorf("timestamp too old") }

// Compute expected signature
baseString := fmt.Sprintf("v0:%s:%s", timestamp, string(payload))
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(baseString))
expected := "v0=" + hex.EncodeToString(mac.Sum(nil))

if !hmac.Equal([]byte(expected), []byte(signature)) {
    return fmt.Errorf("signature mismatch")
}

return nil

}

// Generic HMAC-SHA256 verification func (v *WebhookVerifier) verifyHMACSHA256(payload []byte, signature, secret string) error { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(payload) expected := hex.EncodeToString(mac.Sum(nil))

if !hmac.Equal([]byte(expected), []byte(signature)) {
    return fmt.Errorf("signature mismatch")
}

return nil

}

Replay Protection

Prevent replay attacks with timestamp validation and idempotency.

type WebhookHandler struct { verifier *WebhookVerifier idempotencyDB IdempotencyStore maxAge time.Duration // e.g., 5 minutes }

func (h *WebhookHandler) Handle(w http.ResponseWriter, r *http.Request) { provider := mux.Vars(r)["provider"]

// Read body
body, err := io.ReadAll(r.Body)
if err != nil {
    http.Error(w, "Failed to read body", http.StatusBadRequest)
    return
}

// Get signature and timestamp from headers
signature := r.Header.Get("X-Signature")
timestamp := r.Header.Get("X-Timestamp")

// 1. Verify signature
if err := h.verifier.Verify(provider, body, signature, timestamp); err != nil {
    http.Error(w, "Invalid signature", http.StatusUnauthorized)
    return
}

// 2. Check timestamp freshness
ts, _ := strconv.ParseInt(timestamp, 10, 64)
if time.Since(time.Unix(ts, 0)) > h.maxAge {
    http.Error(w, "Request expired", http.StatusBadRequest)
    return
}

// 3. Check idempotency key
eventID := r.Header.Get("X-Event-ID")
if eventID == "" {
    eventID = fmt.Sprintf("%s-%s", signature[:16], timestamp)
}

if h.idempotencyDB.Exists(eventID) {
    // Already processed, return success (idempotent)
    w.WriteHeader(http.StatusOK)
    return
}

// 4. Process webhook
if err := h.processWebhook(r.Context(), provider, body); err != nil {
    http.Error(w, "Processing failed", http.StatusInternalServerError)
    return
}

// 5. Mark as processed
h.idempotencyDB.Set(eventID, 24*time.Hour) // Keep for 24h

w.WriteHeader(http.StatusOK)

}

Integration Testing Patterns

Mock Server for Unit Tests

import ( "net/http" "net/http/httptest" )

type MockAPIServer struct { *httptest.Server Responses map[string]MockResponse }

type MockResponse struct { StatusCode int Body string Headers map[string]string }

func NewMockAPIServer() *MockAPIServer { m := &MockAPIServer{ Responses: make(map[string]MockResponse), }

m.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    key := fmt.Sprintf("%s %s", r.Method, r.URL.Path)

    resp, ok := m.Responses[key]
    if !ok {
        w.WriteHeader(http.StatusNotFound)
        return
    }

    for k, v := range resp.Headers {
        w.Header().Set(k, v)
    }
    w.WriteHeader(resp.StatusCode)
    w.Write([]byte(resp.Body))
}))

return m

}

// Usage in tests func TestGetCarrierData(t *testing.T) { server := NewMockAPIServer() defer server.Close()

server.Responses["POST /api/v1/Carrier/GetCarrierData"] = MockResponse{
    StatusCode: 200,
    Body:       `{"DOTNumber": 12345, "LegalName": "Test Carrier"}`,
}

client := NewMCPClient(server.URL, "user", "pass")
carrier, err := client.GetCarrierData(context.Background(), 12345, "MC123")

assert.NoError(t, err)
assert.Equal(t, "Test Carrier", carrier.LegalName)

}

HTTP Interaction Recording

Record and replay HTTP interactions for integration tests.

import ( "gopkg.in/dnaeon/go-vcr.v3/recorder" )

func TestWithRecording(t *testing.T) { // Record mode: captures real API responses // Replay mode: uses recorded responses r, err := recorder.New("fixtures/carrier_data") if err != nil { t.Fatal(err) } defer r.Stop()

client := &#x26;http.Client{
    Transport: r,
}

// Use recorded responses in tests
apiClient := NewAPIClient(WithHTTPClient(client))
result, err := apiClient.GetData(context.Background())

assert.NoError(t, err)
assert.NotNil(t, result)

}

Sandbox Environment Testing

type IntegrationTestSuite struct { suite.Suite client *APIClient }

func (s *IntegrationTestSuite) SetupSuite() { // Use sandbox/test environment baseURL := os.Getenv("API_SANDBOX_URL") if baseURL == "" { s.T().Skip("Sandbox URL not configured") }

s.client = NewAPIClient(
    WithBaseURL(baseURL),
    WithCredentials(
        os.Getenv("SANDBOX_CLIENT_ID"),
        os.Getenv("SANDBOX_CLIENT_SECRET"),
    ),
)

}

func (s *IntegrationTestSuite) TestTokenRefresh() { // Test with real sandbox token, err := s.client.RefreshToken(context.Background()) s.Require().NoError(err) s.NotEmpty(token.AccessToken) }

func TestIntegration(t *testing.T) { if testing.Short() { t.Skip("Skipping integration tests in short mode") } suite.Run(t, new(IntegrationTestSuite)) }

Provider-Specific Considerations

For detailed provider-specific implementation:

Provider Reference Skill

QuickBooks quickbooks-api-integration:quickbooks-online-api

MyCarrierPackets mycarrierpackets-api:mycarrierpackets-api

Slack slack-go-sdk:slack-auth-security

Goth OAuth goth-oauth:goth-fundamentals

Quick Reference Checklist

New Integration Checklist

[ ] OAuth2 implementation [ ] Authorization URL generation [ ] Token exchange endpoint [ ] Token refresh logic [ ] Proactive refresh (80% lifetime) [ ] Secure token storage (encrypted DB)

[ ] Error handling [ ] Unified error type [ ] Status code classification [ ] Retry logic for transient errors [ ] Clear token on 401

[ ] Rate limiting [ ] Parse rate limit headers [ ] Client-side rate limiter [ ] Backoff on 429

[ ] Reliability [ ] Exponential backoff with jitter [ ] Circuit breaker [ ] Timeout configuration

[ ] Multi-tenancy [ ] Per-account token isolation [ ] Scoped API clients

[ ] Webhook security (if applicable) [ ] Signature verification [ ] Timestamp validation [ ] Idempotency handling

[ ] Testing [ ] Mock server for unit tests [ ] Sandbox environment tests [ ] Recorded HTTP interactions

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

geospatial-postgis-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

rbac-authorization-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

quickbooks-online-api

No summary provided by upstream source.

Repository SourceNeeds Review
General

slack-block-kit

No summary provided by upstream source.

Repository SourceNeeds Review