ekart
A production-grade Go client for the Ekart Logistics API.
- Zero dependencies — standard library only.
- Automatic auth — bearer tokens are fetched, cached, and refreshed for you.
- Resilient — transient failures (network errors, HTTP 429/5xx) are retried with exponential backoff and full jitter.
- Typed — every request and response in the OpenAPI spec is a Go type, and API errors surface as a structured
*APIError.
- Context-aware — every call takes a
context.Context for cancellation and deadlines.
- Tested — the transport, auth caching, retry logic, and every endpoint are covered by
httptest-backed tests.
The reference OpenAPI document is vendored at docs/openapi.yaml.
Install
go get github.com/entanglesoftware/ekart/pkg/ekart
import "github.com/entanglesoftware/ekart/pkg/ekart"
Requires Go 1.23 or newer.
Project layout
pkg/ekart/ # the public SDK (import this)
cmd/create-shipment/ # runnable example command
internal/retry/ # private backoff/jitter helpers (not importable)
docs/openapi.yaml # vendored OpenAPI reference
Quick start
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/entanglesoftware/ekart"
)
func main() {
client, err := ekart.New(ekart.Config{
ClientID: "your-client-id",
Username: "your-username",
Password: "your-password",
})
if err != nil {
log.Fatal(err)
}
ack, err := client.Shipments.Create(context.Background(), &ekart.Shipment{
SellerName: "Acme",
ConsigneeName: "Asha Rao",
ConsigneeAlternatePhone: "9876543210",
OrderNumber: "ORD-1",
InvoiceNumber: "INV-1",
InvoiceDate: "2026-08-01",
ProductsDesc: "Widgets",
CategoryOfGoods: "General",
PaymentMode: ekart.PaymentCOD,
TotalAmount: 999, TaxValue: 152, TaxableAmount: 847,
CommodityValue: "847", CODAmount: 999, Quantity: 1,
Weight: 500, Length: 10, Height: 10, Width: 10,
DropLocation: &ekart.LocationV1{
Name: "Asha Rao", Phone: 9876543210, Pin: 560025,
Address: "42 Residency Road", City: "Bengaluru",
State: "Karnataka", Country: "India",
},
})
if err != nil {
var apiErr *ekart.APIError
if errors.As(err, &apiErr) {
log.Fatalf("ekart rejected the request: %d %s", apiErr.HTTPStatus, apiErr.Message)
}
log.Fatal(err)
}
fmt.Println("tracking id:", ack.TrackingID)
fmt.Println("track at:", ack.TrackingURL())
}
A runnable version lives in cmd/create-shipment.
Authentication
ekart.New requires either credentials (ClientID + Username + Password) or a pre-issued Token.
With credentials, the client calls the authorization endpoint on the first protected request, caches the returned token, reuses it until shortly before it expires, and refreshes automatically. Concurrent requests share a single refresh rather than stampeding the auth endpoint. Tokens are never sent to the public tracking endpoints, which are unauthenticated.
// Supply a token you already hold (skips the credential exchange).
client, _ := ekart.New(ekart.Config{Token: "eyJ..."})
// Force a fresh token on the next protected call.
if err := client.ResetToken(ctx); err != nil { /* store error */ }
Token caching
By default the token is cached in memory (NewMemoryTokenStore) and held until the process restarts — no configuration needed. To share the token across processes or restarts, implement the TokenStore interface and pass it in Config:
type TokenStore interface {
Get(ctx context.Context) (CachedToken, bool, error) // bool=false on a miss
Set(ctx context.Context, tok CachedToken) error // empty Value clears
}
client, _ := ekart.New(ekart.Config{
ClientID: "...", Username: "...", Password: "...",
TokenStore: myRedisStore{rdb}, // Redis, a file, a bucket — your choice
})
The store is treated as a best-effort cache: a Get error falls through to a fresh fetch and a Set error never fails the request, so a cache outage (e.g. Redis down) degrades to fetching a token per call rather than blocking API traffic. See the TokenStore doc comment for a Redis sketch.
Configuration
| Field |
Default |
Description |
ClientID |
— |
Client id from Ekart onboarding. |
Username |
— |
Auth username. |
Password |
— |
Auth password. |
Token |
— |
Pre-issued bearer token (skips credential exchange). |
BaseURL |
https://app.elite.ekartlogistics.in |
API base URL. |
HTTPClient |
&http.Client{Timeout: 30s} |
Custom HTTP client (proxies, transports, timeouts). |
MaxRetries |
3 |
Retries for transient failures. -1 disables retries. |
UserAgent |
ekart-go/<version> |
Overrides the User-Agent header. |
TokenStore |
in-memory (till restart) |
Pluggable token cache (Redis / file / bucket). |
Services
| Service |
Methods |
client.Auth |
Token |
client.Shipments |
Create, Cancel, SetDispatchDate, UpdateEWBN |
client.Label |
DownloadPDF, DownloadJSON |
client.Manifest |
Download |
client.Track |
ByID (public), Ekart (raw non-large / large) |
client.Serviceability |
V2, V3, Bulk |
client.NDR |
Act |
client.Address |
Add, List |
client.Webhook |
List, Add, Update |
client.Estimate |
Get |
Error handling
Non-2xx responses are returned as *ekart.APIError, carrying the HTTP status plus the API's structured code, message, description, and severity:
if apiErr, ok := ekart.AsAPIError(err); ok {
if apiErr.Temporary() {
// 429 / 5xx — safe to retry later.
}
}
Optional fields
Fields the API treats as optional booleans/numbers are pointers so you can distinguish "unset" from a zero value. Use ekart.Ptr to set them:
shipment.DelayedDispatch = ekart.Ptr(true)
webhook.Active = ekart.Ptr(false)
Development
make check # gofmt + go vet + go test -race
make cover # coverage summary
make lint # golangci-lint (if installed)
License
MIT