A Go client library for the Tangerino Employer API.
Covers the core employer modules: Employees, Companies, Holiday Calendars, Work Schedules, Workplaces, and Punches.
go get github.com/raykavin/tangerino-goRequires Go 1.22+. No external dependencies — uses only the Go standard library.
import tangerino "github.com/raykavin/tangerino-go"
client, err := tangerino.NewClient("your-username", "your-password")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// List employees (first page)
page, err := client.Employees.List(ctx, tangerino.ListEmployeesParams{Size: 20})
if err != nil {
log.Fatal(err)
}
for _, e := range page.Content {
fmt.Println(e.ID, e.Name, e.AdmissionDate.Format("02/01/2006"))
}// Production environment (default)
client, err := tangerino.NewClient("username", "password")
// Custom base URL (e.g. for testing)
opt, err := tangerino.WithBaseURL("https://custom.tangerino.example.com")
if err != nil {
log.Fatal(err)
}
client, err = tangerino.NewClient("username", "password", opt)
// Staging environment
client, err = tangerino.NewClient("username", "password", tangerino.WithStagingEnv())
// Custom HTTP client (for TLS, proxies, custom timeouts, etc.)
httpClient := &http.Client{Timeout: 60 * time.Second}
client, err = tangerino.NewClient("username", "password", tangerino.WithHTTPClient(httpClient))The Tangerino API uses HTTP Basic Authentication. Credentials are encoded and sent automatically on every request — no additional setup is required after creating the client.
client, err := tangerino.NewClient("your-username", "your-password")// List employees (paginated)
page, err := client.Employees.List(ctx, tangerino.ListEmployeesParams{
Size: 20,
})
fmt.Printf("Page 1 of %d (%d total)\n", page.TotalPages, page.TotalElements)
// Apply filters
page, err = client.Employees.List(ctx, tangerino.ListEmployeesParams{
Size: 20,
BranchExternalID: "branch-001",
ManagerExternalID: "manager-042",
ShowFired: 1, // include terminated employees
})
// Filter by last update (Unix timestamp in milliseconds)
page, err = client.Employees.List(ctx, tangerino.ListEmployeesParams{
LastUpdate: time.Now().Add(-24 * time.Hour).UnixMilli(),
Size: 50,
})
// Iterate all pages
params := tangerino.ListEmployeesParams{Size: 50}
for {
page, err := client.Employees.List(ctx, params)
if err != nil {
log.Fatal(err)
}
for _, e := range page.Content {
fmt.Println(e.ID, e.Name)
}
if !page.HasNext() {
break
}
params.Page = page.NextPageNumber()
}
// Access typed date fields
for _, e := range page.Content {
fmt.Println(e.AdmissionDate.Format("02/01/2006")) // "01/04/2025"
fmt.Println(e.AdmissionDate.Raw()) // 1743462000000
fmt.Println(e.AdmissionDate.Time()) // time.Time
if e.BirthDate != nil {
fmt.Println(e.BirthDate.Format("02/01/2006"))
}
}// List companies (paginated)
page, err := client.Companies.List(ctx, tangerino.ListCompaniesParams{
Size: 20,
})
// Explicit page navigation
page, err = client.Companies.List(ctx, tangerino.ListCompaniesParams{
Page: 1,
Size: 10,
})
for _, c := range page.Content {
fmt.Println(c.ID, c.CNPJ, c.SocialReason, c.FantasyName)
}// List all holiday calendars for the employer
calendars, err := client.HolidayCalendars.List(ctx)
if err != nil {
log.Fatal(err)
}
for _, cal := range calendars {
fmt.Printf("[%d] %s (%d)\n", cal.ID, cal.Name, cal.Year)
for _, h := range cal.Holidays {
fmt.Printf(" %s - %s\n", h.Date, h.Description)
}
}// List all work schedules
page, err := client.WorkSchedules.List(ctx, tangerino.ListWorkSchedulesParams{
Size: 50,
})
if err != nil {
log.Fatal(err)
}
for _, ws := range page.Content {
fmt.Printf("[%d] %s (standard: %v, inactive: %v)\n",
ws.ID, ws.Name, ws.Standard, ws.Inactive)
for _, tt := range ws.Timetable {
fmt.Printf(" Day %d: %s - %s",
tt.Day,
tt.StartShift1.String(), // "08:00"
tt.EndShift1, // *DayOffset, check nil before use
)
if tt.StartShift2 != nil {
fmt.Printf(" | %s - %s",
tt.StartShift2.String(),
tt.EndShift2,
)
}
fmt.Println()
}
fmt.Println("Last modified:", ws.AlterationDate.Format("02/01/2006"))
}// List workplaces (paginated)
page, err := client.Workplaces.List(ctx, tangerino.ListWorkplacesParams{
Size: 20,
})
if err != nil {
log.Fatal(err)
}
for _, w := range page.Content {
fmt.Printf("[%d] %s — %s, %s\n", w.ID, w.Name, w.City, w.State)
}
// Iterate all pages
params := tangerino.ListWorkplacesParams{Size: 50}
for {
page, err := client.Workplaces.List(ctx, params)
if err != nil {
log.Fatal(err)
}
for _, w := range page.Content {
fmt.Println(w.ID, w.Name)
}
if !page.HasNext() {
break
}
params.Page = page.NextPageNumber()
}The punch endpoints live on a separate host (apis.tangerino.com.br) — the client handles routing automatically.
adj := true
pending := false
// List punch records for an employee
punches, err := client.Punches.List(ctx, employeeID, tangerino.PunchesParams{
Status: 3,
Adjustment: &adj,
StartDate: time.Now().AddDate(0, -1, 0), // converted to Unix seconds
EndDate: time.Now(),
Pending: &pending,
})
if err != nil {
log.Fatal(err)
}
for _, p := range punches {
end := "open"
if p.EndDate != nil {
end = p.EndDate.String() // "2026-06-01T18:02:00"
}
fmt.Printf("[%d] %s %s → %s (%s manual: start=%v end=%v)\n",
p.ID, p.Date,
p.StartDate.String(), end,
formatMillis(p.TotalHours),
p.StartManual, p.EndManual,
)
}
// Get aggregate summary for an employee
summary, err := client.Punches.Summary(ctx, employeeID, tangerino.PunchesParams{
StartDate: time.Now().AddDate(0, -1, 0),
EndDate: time.Now(),
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Worked: %dms Expected: %dms Balance: %dms\n",
summary.TotalWorked, summary.TotalExpected, summary.Balance)StartDate and EndDate are time.Time values and are automatically converted to Unix second timestamps in the request query string.
Adjustment and Pending are *bool — use a pointer so that false is distinguishable from "not set":
t, f := true, false
tangerino.PunchesParams{Adjustment: &t, Pending: &f}Used for absolute timestamps (AdmissionDate, BirthDate, EffectiveDate,
AlterationDate, StartDate). Preserves the original Unix millisecond value and
provides conversion helpers.
ts := employee.AdmissionDate
ts.Raw() // int64 → 1776049200000 (original API value)
ts.Time() // time.Time (UTC)
ts.Format("02/01/2006") // string → "01/04/2026"
ts.Format("15:04") // string → "03:00" (UTC hour)
ts.Format(time.RFC3339) // string → "2026-04-01T03:00:00Z"
ts.String() // string → "2026-04-01 03:00:00 UTC"Used for time-of-day offsets in work schedule timetables (StartShift1,
EndShift1, StartShift2, EndShift2, StartMainInterval, EndMainInterval).
Values are milliseconds from midnight and may exceed 86400000 (24 h) for shifts
that extend into the next day.
d := timetable.StartShift1
d.Raw() // int64 → 39600000 (original API value)
d.Duration() // time.Duration → 11h0m0s
d.String() // string → "11:00"
// Shifts past midnight (e.g. 12x36 schedule):
// EndShift2 = 100800000 → "28:00"Used for punch timestamps (StartDate, EndDate in Punch). Represents a
wall-clock datetime without timezone in the format "2006-01-02T15:04:05".
p := punches[0]
p.StartDate.String() // "2026-06-01T14:06:00"
p.StartDate.Time() // time.Time (no timezone — server local time)
// EndDate is *LocalDateTime, nil when the employee hasn't clocked out yet
if p.EndDate != nil {
fmt.Println(p.EndDate.String())
}Paginated endpoints return *Page[T], a generic type wrapping the Spring-style
content envelope.
type Page[T any] struct {
Content []T
First bool
Last bool
TotalElements int
TotalPages int
NumberOfElements int
Size int
Number int
}Helper methods:
page.HasNext() // bool: whether a next page exists
page.NextPageNumber() // int: page number to use in the next request (-1 on last page)Pagination parameters follow a consistent naming convention across all services:
| Field | Query param | Description |
|---|---|---|
Page |
page |
Zero-based page index |
Size |
size |
Number of items per page |
Full iteration pattern:
params := tangerino.ListEmployeesParams{Size: 50}
for {
page, err := client.Employees.List(ctx, params)
if err != nil {
log.Fatal(err)
}
// process page.Content ...
if !page.HasNext() {
break
}
params.Page = page.NextPageNumber()
}All methods return *APIError on HTTP-level failures.
page, err := client.Employees.List(ctx, tangerino.ListEmployeesParams{})
if err != nil {
switch {
case tangerino.IsUnauthorized(err):
// HTTP 401 — invalid credentials
case tangerino.IsForbidden(err):
// HTTP 403 — insufficient permissions
case tangerino.IsNotFound(err):
// HTTP 404 — resource not found
case tangerino.IsRateLimited(err):
// HTTP 429 — too many requests, back off and retry
case tangerino.IsServerError(err):
// HTTP 5xx — transient server error
default:
if apiErr, ok := err.(*tangerino.APIError); ok {
fmt.Printf("Status: %d\n", apiErr.StatusCode)
fmt.Printf("Body: %s\n", apiErr.Body)
}
}
}go test ./... -vAll tests use net/http/httptest — no external services or environment variables
are required.
-
GET /employee/find-all— List employees (paginated, with filters)
-
GET /companies— List companies (paginated)
-
GET /holiday-calendar/— List holiday calendars
-
GET /work-schedule— List work schedules (paginated)
-
GET /workplace/find-all— List workplaces (paginated)
-
GET /punch/v2/punches/employees/{id}— List punch records for an employee -
GET /punch/v2/punches/employees/{id}/summary— Get aggregate time summary for an employee
Total: 7 endpoints covered
Contributions to tangerino-go are welcome! Here are some ways you can help:
- Report bugs and suggest features by opening issues on GitHub
- Submit pull requests with bug fixes or new features
- Improve documentation to help other users and developers
tangerino-go is distributed under the MIT License.
For complete license terms and conditions, see the LICENSE file in the repository.
For support, collaboration, or questions about tangerino-go:
Email: raykavin.meireles@gmail.com
GitHub: @raykavin