README.md

December 18, 2025 ยท View on GitHub

Go Invoice Ninja SDK

Go Invoice Ninja SDK

Go Reference Go Report Card CI codecov License: MIT

A professional, idiomatic Go SDK for the Invoice Ninja API. This SDK provides a clean interface for interacting with Invoice Ninja's comprehensive invoicing and payment platform.

โœจ Features

  • ๐Ÿ” Secure Authentication - Token-based API authentication
  • ๐Ÿ’ณ Payment Management - Full CRUD operations with refund support
  • ๐Ÿ“„ Invoice Operations - Create, send, and manage invoices
  • ๐Ÿ‘ฅ Client Management - Client CRUD with merge capabilities
  • ๐Ÿ’ฐ Credits & Payment Terms - Complete credit and terms management
  • ๐Ÿ“ฅ File Operations - Download PDFs and upload documents
  • ๐Ÿ”” Webhook Handling - Built-in handler with signature verification
  • โšก Rate Limiting - Client-side limiting with automatic retry
  • ๐Ÿ”„ Retry Logic - Exponential backoff for transient failures
  • ๐ŸŒ Self-hosted Support - Works with cloud and self-hosted instances
  • โœ… Fully Tested - 90+ tests with comprehensive coverage

๐Ÿ“ฆ Installation

go get github.com/AshkanYarmoradi/go-invoice-ninja

๐Ÿ“– Documentation

๐Ÿ—๏ธ Project Structure

go-invoice-ninja/
โ”œโ”€โ”€ .github/workflows/     # CI/CD pipelines
โ”œโ”€โ”€ docs/                  # Detailed documentation
โ”œโ”€โ”€ examples/              # Runnable examples
โ”‚   โ”œโ”€โ”€ basic/            # Basic usage
โ”‚   โ”œโ”€โ”€ invoices/         # Invoice operations
โ”‚   โ””โ”€โ”€ webhooks/         # Webhook handling
โ”œโ”€โ”€ testdata/              # Test fixtures
โ”‚
โ”œโ”€โ”€ client.go             # Main client
โ”œโ”€โ”€ clients.go            # Clients service
โ”œโ”€โ”€ credits.go            # Credits service
โ”œโ”€โ”€ errors.go             # Error types
โ”œโ”€โ”€ files.go              # File operations
โ”œโ”€โ”€ invoices.go           # Invoices service
โ”œโ”€โ”€ models.go             # Data models
โ”œโ”€โ”€ payments.go           # Payments service
โ”œโ”€โ”€ payment_terms.go      # Payment terms
โ”œโ”€โ”€ retry.go              # Retry & rate limiting
โ”œโ”€โ”€ webhooks.go           # Webhook handling
โ”‚
โ”œโ”€โ”€ CHANGELOG.md          # Version history
โ”œโ”€โ”€ CONTRIBUTING.md       # Contribution guide
โ”œโ”€โ”€ LICENSE               # MIT License
โ”œโ”€โ”€ Makefile              # Build automation
โ””โ”€โ”€ README.md             # This file

๐Ÿš€ Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    invoiceninja "github.com/AshkanYarmoradi/go-invoice-ninja"
)

func main() {
    // Create a new client
    client := invoiceninja.NewClient("your-api-token")
    
    // For self-hosted instances:
    // client := invoiceninja.NewClient("your-api-token", 
    //     invoiceninja.WithBaseURL("https://your-instance.com"))

    ctx := context.Background()

    // List payments
    payments, err := client.Payments.List(ctx, &invoiceninja.PaymentListOptions{
        PerPage: 10,
        Page:    1,
    })
    if err != nil {
        log.Fatal(err)
    }

    for _, payment := range payments.Data {
        fmt.Printf("Payment %s: $%.2f\n", payment.Number, payment.Amount)
    }
}

๐Ÿ”‘ Authentication

All API requests require an API token. You can obtain your token from: Settings > Account Management > Integrations > API tokens

client := invoiceninja.NewClient("your-api-token")

โš™๏ธ Configuration Options

// Custom HTTP client
client := invoiceninja.NewClient("token",
    invoiceninja.WithHTTPClient(customHTTPClient))

// Custom base URL (for self-hosted)
client := invoiceninja.NewClient("token",
    invoiceninja.WithBaseURL("https://your-instance.com"))

// Custom timeout
client := invoiceninja.NewClient("token",
    invoiceninja.WithTimeout(60 * time.Second))

๐Ÿ’ณ Payments

List Payments

payments, err := client.Payments.List(ctx, &invoiceninja.PaymentListOptions{
    PerPage:  20,
    Page:     1,
    ClientID: "client-hash-id",
    Status:   "active",
    Sort:     "amount|desc",
})

Get Payment

payment, err := client.Payments.Get(ctx, "payment-hash-id")

Create Payment

payment, err := client.Payments.Create(ctx, &invoiceninja.PaymentRequest{
    ClientID: "client-hash-id",
    Amount:   100.00,
    Date:     "2024-01-15",
    Invoices: []invoiceninja.PaymentInvoice{
        {InvoiceID: "invoice-hash-id", Amount: 100.00},
    },
})

Update Payment

payment, err := client.Payments.Update(ctx, "payment-hash-id", &invoiceninja.PaymentRequest{
    PrivateNotes: "Updated notes",
})

Delete Payment

err := client.Payments.Delete(ctx, "payment-hash-id")

Refund Payment

payment, err := client.Payments.Refund(ctx, &invoiceninja.RefundRequest{
    ID:            "payment-hash-id",
    Amount:        50.00,
    GatewayRefund: true,
})

Bulk Actions

// Archive multiple payments
payments, err := client.Payments.Bulk(ctx, "archive", []string{"id1", "id2"})

// Single item convenience methods
payment, err := client.Payments.Archive(ctx, "payment-hash-id")
payment, err := client.Payments.Restore(ctx, "payment-hash-id")

Invoices

List Invoices

invoices, err := client.Invoices.List(ctx, &invoiceninja.InvoiceListOptions{
    PerPage:  20,
    ClientID: "client-hash-id",
})

Get Invoice

invoice, err := client.Invoices.Get(ctx, "invoice-hash-id")

Create Invoice

invoice, err := client.Invoices.Create(ctx, &invoiceninja.Invoice{
    ClientID: "client-hash-id",
    LineItems: []invoiceninja.LineItem{
        {ProductKey: "Product A", Quantity: 2, Cost: 50.00},
    },
})

Invoice Actions

// Mark as paid
invoice, err := client.Invoices.MarkPaid(ctx, "invoice-hash-id")

// Mark as sent
invoice, err := client.Invoices.MarkSent(ctx, "invoice-hash-id")

// Send via email
invoice, err := client.Invoices.Email(ctx, "invoice-hash-id")

Clients

List Clients

clients, err := client.Clients.List(ctx, &invoiceninja.ClientListOptions{
    PerPage: 20,
    Balance: "gt:1000",  // Balance greater than 1000
    Include: "contacts,documents",
})

Create Client

newClient, err := client.Clients.Create(ctx, &invoiceninja.INClient{
    Name: "Acme Corporation",
    Contacts: []invoiceninja.ClientContact{
        {
            FirstName: "John",
            LastName:  "Doe",
            Email:     "john@acme.com",
            IsPrimary: true,
        },
    },
})

Merge Clients

mergedClient, err := client.Clients.Merge(ctx, "primary-id", "mergeable-id")

Payment Terms

// List payment terms
terms, err := client.PaymentTerms.List(ctx, nil)

// Create a payment term
term, err := client.PaymentTerms.Create(ctx, &invoiceninja.PaymentTerm{
    Name:    "Net 45",
    NumDays: 45,
})

// Get, Update, Delete
term, err := client.PaymentTerms.Get(ctx, "term-id")
term, err := client.PaymentTerms.Update(ctx, "term-id", &invoiceninja.PaymentTerm{Name: "Net 60"})
err := client.PaymentTerms.Delete(ctx, "term-id")

Credits

// List credits
credits, err := client.Credits.List(ctx, &invoiceninja.CreditListOptions{
    ClientID: "client-hash-id",
    PerPage:  20,
})

// Create a credit
credit, err := client.Credits.Create(ctx, &invoiceninja.Credit{
    ClientID: "client-hash-id",
    LineItems: []invoiceninja.LineItem{
        {ProductKey: "Credit", Quantity: 1, Cost: 100.00},
    },
})

// Credit actions
credit, err := client.Credits.MarkSent(ctx, "credit-id")
credit, err := client.Credits.Email(ctx, "credit-id")

File Downloads

// Download invoice PDF
pdf, err := client.Downloads.DownloadInvoicePDF(ctx, "invitation-key")

// Download delivery note
pdf, err := client.Downloads.DownloadInvoiceDeliveryNote(ctx, "invoice-id")

// Download credit PDF
pdf, err := client.Downloads.DownloadCreditPDF(ctx, "invitation-key")

// Save to file
os.WriteFile("invoice.pdf", pdf, 0644)

File Uploads

// Upload document to invoice
err := client.Uploads.UploadInvoiceDocument(ctx, "invoice-id", "/path/to/file.pdf")

// Upload to other entities
err := client.Uploads.UploadPaymentDocument(ctx, "payment-id", "/path/to/file.pdf")
err := client.Uploads.UploadClientDocument(ctx, "client-id", "/path/to/file.pdf")
err := client.Uploads.UploadCreditDocument(ctx, "credit-id", "/path/to/file.pdf")

// Upload from io.Reader
reader := bytes.NewReader(pdfContent)
err := client.Uploads.UploadDocumentFromReader(ctx, "invoices", "invoice-id", "document.pdf", reader)

Webhooks

Handle incoming webhooks from Invoice Ninja:

// Create a webhook handler
handler := invoiceninja.NewWebhookHandler("your-webhook-secret")

// Register event handlers
handler.OnPaymentCreated(func(event *invoiceninja.WebhookEvent) error {
    payment, err := event.ParsePayment()
    if err != nil {
        return err
    }
    fmt.Printf("New payment: %s ($%.2f)\n", payment.Number, payment.Amount)
    return nil
})

handler.OnInvoiceCreated(func(event *invoiceninja.WebhookEvent) error {
    invoice, err := event.ParseInvoice()
    if err != nil {
        return err
    }
    fmt.Printf("New invoice: %s\n", invoice.Number)
    return nil
})

// Use as HTTP handler
http.Handle("/webhook", handler)
http.ListenAndServe(":8080", nil)

Supported webhook events:

  • OnInvoiceCreated, OnInvoiceUpdated, OnInvoiceDeleted
  • OnPaymentCreated, OnPaymentUpdated, OnPaymentDeleted
  • OnClientCreated, OnClientUpdated
  • OnCreditCreated, OnQuoteCreated

Rate Limiting & Retry

For production use, use the rate-limited client with automatic retries:

// Create a rate-limited client
client := invoiceninja.NewRateLimitedClient("your-api-token",
    invoiceninja.WithBaseURL("https://your-instance.com"))

// Configure rate limit (requests per second)
client.SetRateLimit(10)

// Configure retry behavior
client.SetRetryConfig(&invoiceninja.RetryConfig{
    MaxRetries:         3,
    InitialBackoff:     1 * time.Second,
    MaxBackoff:         30 * time.Second,
    BackoffMultiplier:  2.0,
    RetryOnStatusCodes: []int{429, 500, 502, 503, 504},
    Jitter:             true,
})

Generic Requests

For API endpoints not covered by specialized methods, use the generic request:

// GET request
var activities json.RawMessage
err := client.Request(ctx, "GET", "/api/v1/activities", nil, &activities)

// POST request with body
body := map[string]interface{}{
    "name": "New Product",
    "cost": 99.99,
}
var result json.RawMessage
err := client.Request(ctx, "POST", "/api/v1/products", body, &result)

// With query parameters
query := url.Values{}
query.Set("per_page", "50")
err := client.RequestWithQuery(ctx, "GET", "/api/v1/products", query, nil, &result)

Error Handling

The SDK provides typed errors with helper methods:

payment, err := client.Payments.Get(ctx, "invalid-id")
if err != nil {
    if apiErr, ok := invoiceninja.IsAPIError(err); ok {
        if apiErr.IsNotFound() {
            fmt.Println("Payment not found")
        } else if apiErr.IsUnauthorized() {
            fmt.Println("Invalid API token")
        } else if apiErr.IsValidationError() {
            fmt.Printf("Validation errors: %v\n", apiErr.Errors)
        } else if apiErr.IsRateLimited() {
            fmt.Println("Rate limit exceeded, please wait")
        }
    }
    log.Fatal(err)
}

๐Ÿงช Testing

# Run all tests
make test

# Run with race detector
make test-race

# Run with coverage
make coverage

# Run linter
make lint

๐Ÿ”— Integration Tests

Run integration tests against a live Invoice Ninja server:

# Run against demo server
go test -tags=integration -v ./...

# Run against custom server
INVOICE_NINJA_BASE_URL=https://your-server.com \
INVOICE_NINJA_API_TOKEN=your-token \
go test -tags=integration -v ./...

๐Ÿ“š Examples

Check out the examples directory for complete working examples:

๐Ÿ“‹ API Reference

Status CodeDescription
200Success
400Bad Request
401Unauthorized - Invalid API token
403Forbidden - No permission
404Not Found
422Validation Error
429Rate Limited
5xxServer Error

๐Ÿ“„ License

This SDK is released under the MIT License.

๐Ÿค Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (make test)
  5. Run the linter (make lint)
  6. Commit your changes (git commit -m 'feat: add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

๐Ÿ“ž Support


Go Invoice Ninja Logo

                    โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
                    โ•‘                                                           โ•‘
                    โ•‘             โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—                              โ•‘
                    โ•‘            โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—                             โ•‘
                    โ•‘            โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘                             โ•‘
                    โ•‘            โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘                             โ•‘
                    โ•‘            โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•                             โ•‘
                    โ•‘             โ•šโ•โ•โ•โ•โ•โ•  โ•šโ•โ•โ•โ•โ•โ•                              โ•‘
                    โ•‘                                                           โ•‘
                    โ•‘    โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—     โ•‘
                    โ•‘    โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•     โ•‘
                    โ•‘    โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—       โ•‘
                    โ•‘    โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘   โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•”โ•โ•โ•       โ•‘
                    โ•‘    โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—     โ•‘
                    โ•‘    โ•šโ•โ•โ•šโ•โ•  โ•šโ•โ•โ•โ•  โ•šโ•โ•โ•โ•   โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•     โ•‘
                    โ•‘                                                           โ•‘
                    โ•‘    โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•—   โ–ˆโ–ˆโ•—     โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—                 โ•‘
                    โ•‘    โ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ•—  โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—                โ•‘
                    โ•‘    โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘     โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘                โ•‘
                    โ•‘    โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆ   โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘                โ•‘
                    โ•‘    โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘  โ–ˆโ–ˆโ•‘                โ•‘
                    โ•‘    โ•šโ•โ•  โ•šโ•โ•โ•โ•โ•šโ•โ•โ•šโ•โ•  โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ• โ•šโ•โ•  โ•šโ•โ•                โ•‘
                    โ•‘                                                           โ•‘
                    โ•‘           โญ Star us on GitHub! โญ                       โ•‘
                    โ•‘                                                           โ•‘
                    โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Made with โค๏ธ by Ashkan Yarmoradi