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.
go get github.com/AshkanYarmoradi/go-invoice-ninja
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
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)
}
}
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")
// 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, err := client.Payments.List(ctx, &invoiceninja.PaymentListOptions{
PerPage: 20,
Page: 1,
ClientID: "client-hash-id",
Status: "active",
Sort: "amount|desc",
})
payment, err := client.Payments.Get(ctx, "payment-hash-id")
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},
},
})
payment, err := client.Payments.Update(ctx, "payment-hash-id", &invoiceninja.PaymentRequest{
PrivateNotes: "Updated notes",
})
err := client.Payments.Delete(ctx, "payment-hash-id")
payment, err := client.Payments.Refund(ctx, &invoiceninja.RefundRequest{
ID: "payment-hash-id",
Amount: 50.00,
GatewayRefund: true,
})
// 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, err := client.Invoices.List(ctx, &invoiceninja.InvoiceListOptions{
PerPage: 20,
ClientID: "client-hash-id",
})
invoice, err := client.Invoices.Get(ctx, "invoice-hash-id")
invoice, err := client.Invoices.Create(ctx, &invoiceninja.Invoice{
ClientID: "client-hash-id",
LineItems: []invoiceninja.LineItem{
{ProductKey: "Product A", Quantity: 2, Cost: 50.00},
},
})
// 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, err := client.Clients.List(ctx, &invoiceninja.ClientListOptions{
PerPage: 20,
Balance: "gt:1000", // Balance greater than 1000
Include: "contacts,documents",
})
newClient, err := client.Clients.Create(ctx, &invoiceninja.INClient{
Name: "Acme Corporation",
Contacts: []invoiceninja.ClientContact{
{
FirstName: "John",
LastName: "Doe",
Email: "john@acme.com",
IsPrimary: true,
},
},
})
mergedClient, err := client.Clients.Merge(ctx, "primary-id", "mergeable-id")
// 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")
// 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")
// 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)
// 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)
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, OnInvoiceDeletedOnPaymentCreated, OnPaymentUpdated, OnPaymentDeletedOnClientCreated, OnClientUpdatedOnCreditCreated, OnQuoteCreatedFor 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,
})
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)
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)
}
# Run all tests
make test
# Run with race detector
make test-race
# Run with coverage
make coverage
# Run linter
make lint
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 ./...
Check out the examples directory for complete working examples:
| Status Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request |
| 401 | Unauthorized - Invalid API token |
| 403 | Forbidden - No permission |
| 404 | Not Found |
| 422 | Validation Error |
| 429 | Rate Limited |
| 5xx | Server Error |
This SDK is released under the MIT License.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
git checkout -b feature/amazing-feature)make test)make lint)git commit -m 'feat: add amazing feature')git push origin feature/amazing-feature)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β βββββββ βββββββ β
β ββββββββ βββββββββ β
β βββ βββββββ βββ β
β βββ ββββββ βββ β
β ββββββββββββββββββ β
β βββββββ βββββββ β
β β
β βββββββ ββββββ βββ βββββββ βββ βββββββββββββββ β
β ββββββββ ββββββ βββββββββββββββββββββββββββββββ β
β βββββββββ ββββββ ββββββ βββββββββ ββββββ β
β βββββββββββββββββ βββββββ βββββββββ ββββββ β
β ββββββ ββββββ βββββββ ββββββββββββββββββββββββββββ β
β ββββββ βββββ βββββ βββββββ βββ βββββββββββββββ β
β β
β ββββ ββββββββββ βββ βββ ββββββ β
β βββββ βββββββββββ βββ βββββββββββ β
β ββββββ ββββββββββββ βββ βββββββββββ β
β βββββββββββββββββββββββββ βββββββββββ β
β βββ ββββββββββββ βββββββββββββββββ βββ β
β βββ βββββββββββ βββββ ββββββ βββ βββ β
β β
β β Star us on GitHub! β β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Made with β€οΈ by Ashkan Yarmoradi
15 commits
Go
98.4%
Makefile
1.6%
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.
go get github.com/AshkanYarmoradi/go-invoice-ninja
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
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)
}
}
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")
// 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, err := client.Payments.List(ctx, &invoiceninja.PaymentListOptions{
PerPage: 20,
Page: 1,
ClientID: "client-hash-id",
Status: "active",
Sort: "amount|desc",
})
payment, err := client.Payments.Get(ctx, "payment-hash-id")
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},
},
})
payment, err := client.Payments.Update(ctx, "payment-hash-id", &invoiceninja.PaymentRequest{
PrivateNotes: "Updated notes",
})
err := client.Payments.Delete(ctx, "payment-hash-id")
payment, err := client.Payments.Refund(ctx, &invoiceninja.RefundRequest{
ID: "payment-hash-id",
Amount: 50.00,
GatewayRefund: true,
})
// 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, err := client.Invoices.List(ctx, &invoiceninja.InvoiceListOptions{
PerPage: 20,
ClientID: "client-hash-id",
})
invoice, err := client.Invoices.Get(ctx, "invoice-hash-id")
invoice, err := client.Invoices.Create(ctx, &invoiceninja.Invoice{
ClientID: "client-hash-id",
LineItems: []invoiceninja.LineItem{
{ProductKey: "Product A", Quantity: 2, Cost: 50.00},
},
})
// 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, err := client.Clients.List(ctx, &invoiceninja.ClientListOptions{
PerPage: 20,
Balance: "gt:1000", // Balance greater than 1000
Include: "contacts,documents",
})
newClient, err := client.Clients.Create(ctx, &invoiceninja.INClient{
Name: "Acme Corporation",
Contacts: []invoiceninja.ClientContact{
{
FirstName: "John",
LastName: "Doe",
Email: "john@acme.com",
IsPrimary: true,
},
},
})
mergedClient, err := client.Clients.Merge(ctx, "primary-id", "mergeable-id")
// 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")
// 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")
// 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)
// 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)
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, OnInvoiceDeletedOnPaymentCreated, OnPaymentUpdated, OnPaymentDeletedOnClientCreated, OnClientUpdatedOnCreditCreated, OnQuoteCreatedFor 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,
})
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)
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)
}
# Run all tests
make test
# Run with race detector
make test-race
# Run with coverage
make coverage
# Run linter
make lint
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 ./...
Check out the examples directory for complete working examples:
| Status Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request |
| 401 | Unauthorized - Invalid API token |
| 403 | Forbidden - No permission |
| 404 | Not Found |
| 422 | Validation Error |
| 429 | Rate Limited |
| 5xx | Server Error |
This SDK is released under the MIT License.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
git checkout -b feature/amazing-feature)make test)make lint)git commit -m 'feat: add amazing feature')git push origin feature/amazing-feature)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β βββββββ βββββββ β
β ββββββββ βββββββββ β
β βββ βββββββ βββ β
β βββ ββββββ βββ β
β ββββββββββββββββββ β
β βββββββ βββββββ β
β β
β βββββββ ββββββ βββ βββββββ βββ βββββββββββββββ β
β ββββββββ ββββββ βββββββββββββββββββββββββββββββ β
β βββββββββ ββββββ ββββββ βββββββββ ββββββ β
β βββββββββββββββββ βββββββ βββββββββ ββββββ β
β ββββββ ββββββ βββββββ ββββββββββββββββββββββββββββ β
β ββββββ βββββ βββββ βββββββ βββ βββββββββββββββ β
β β
β ββββ ββββββββββ βββ βββ ββββββ β
β βββββ βββββββββββ βββ βββββββββββ β
β ββββββ ββββββββββββ βββ βββββββββββ β
β βββββββββββββββββββββββββ βββββββββββ β
β βββ ββββββββββββ βββββββββββββββββ βββ β
β βββ βββββββββββ βββββ ββββββ βββ βββ β
β β
β β Star us on GitHub! β β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Made with β€οΈ by Ashkan Yarmoradi
15 commits
Go
98.4%
Makefile
1.6%