Skip to content

Commit 4e21707

Browse files
committed
first commit
0 parents  commit 4e21707

12 files changed

Lines changed: 906 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- uses: actions/setup-go@v5
15+
with:
16+
go-version: stable
17+
18+
- name: Check formatting
19+
run: test -z "$(gofmt -l .)" || (gofmt -l . && exit 1)
20+
21+
- name: Vet
22+
run: go vet ./...
23+
24+
- name: Build
25+
run: go build ./...
26+
27+
- name: Test
28+
run: go test -race ./...

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Binaries and build output
2+
*.exe
3+
*.dll
4+
*.so
5+
*.dylib
6+
*.test
7+
*.out
8+
9+
# Coverage
10+
coverage.*
11+
12+
# IDE
13+
.idea/
14+
.vscode/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Deivid Fortuna
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# FIPE Go SDK
2+
3+
[![Go Reference](https://pkg.go.dev/badge/github.com/fipe-api/go-sdk.svg)](https://pkg.go.dev/github.com/fipe-api/go-sdk)
4+
[![Go Report Card](https://goreportcard.com/badge/github.com/fipe-api/go-sdk)](https://goreportcard.com/report/github.com/fipe-api/go-sdk)
5+
[![CI](https://github.com/fipe-api/go-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/fipe-api/go-sdk/actions/workflows/ci.yml)
6+
7+
A zero-dependency Go client for the [FIPE API](https://fipe.api.br) (`/api/v2`), which provides average vehicle prices in the Brazilian market from Fundação Instituto de Pesquisas Econômicas (FIPE). Prices are updated monthly.
8+
9+
## Install
10+
11+
```sh
12+
go get github.com/fipe-api/go-sdk
13+
```
14+
15+
## Quick start
16+
17+
```go
18+
package main
19+
20+
import (
21+
"context"
22+
"fmt"
23+
"log"
24+
25+
fipe "github.com/fipe-api/go-sdk"
26+
)
27+
28+
func main() {
29+
client := fipe.New()
30+
ctx := context.Background()
31+
32+
brands, err := client.Brands(ctx, fipe.Cars)
33+
if err != nil {
34+
log.Fatal(err)
35+
}
36+
for _, b := range brands {
37+
fmt.Println(b.Code, b.Name)
38+
}
39+
}
40+
```
41+
42+
Vehicle types: `fipe.Cars`, `fipe.Motorcycles`, `fipe.Trucks`.
43+
44+
## Authentication
45+
46+
The free tier works without a token but is rate limited. With a [subscription token](https://fipe.api.parallelum.com.br), you can make more requests per minute:
47+
48+
```go
49+
client := fipe.New(fipe.WithSubscriptionToken("your-token"))
50+
```
51+
52+
Other client options: `fipe.WithHTTPClient(*http.Client)`, `fipe.WithBaseURL(string)`.
53+
54+
## Endpoints
55+
56+
Drill down brand → model → year → price:
57+
58+
```go
59+
brands, _ := client.Brands(ctx, fipe.Cars) // GET /cars/brands
60+
models, _ := client.Models(ctx, fipe.Cars, "59") // GET /cars/brands/59/models
61+
years, _ := client.Years(ctx, fipe.Cars, "59", "5940") // GET /cars/brands/59/models/5940/years
62+
vehicle, _ := client.Vehicle(ctx, fipe.Cars, "59", "5940", "2014-3") // GET /cars/brands/59/models/5940/years/2014-3
63+
64+
fmt.Println(vehicle.Model, vehicle.Price) // "AMAROK High.CD 2.0 16V TDI 4x4 Dies. Aut" "R$ 10.000,00"
65+
```
66+
67+
Browse by year:
68+
69+
```go
70+
years, _ := client.YearsByBrand(ctx, fipe.Cars, "59") // GET /cars/brands/59/years
71+
models, _ := client.ModelsByBrandYear(ctx, fipe.Cars, "59", "2014-3") // GET /cars/brands/59/years/2014-3/models
72+
```
73+
74+
Look up by FIPE code:
75+
76+
```go
77+
years, _ := client.YearsByFipeCode(ctx, fipe.Cars, "005340-6") // GET /cars/005340-6/years
78+
vehicle, _ := client.VehicleByFipeCode(ctx, fipe.Cars, "005340-6", "2014-3") // GET /cars/005340-6/years/2014-3
79+
history, _ := client.HistoryByFipeCode(ctx, fipe.Cars, "005340-6", "2014-3") // GET /cars/005340-6/years/2014-3/history
80+
81+
for _, h := range history.PriceHistory {
82+
fmt.Println(h.Month, h.Price)
83+
}
84+
```
85+
86+
### Reference months
87+
88+
Prices are published per monthly reference table. Every endpoint accepts `fipe.WithReference` to query a past table:
89+
90+
```go
91+
refs, _ := client.References(ctx) // GET /references — e.g. {Code: "308", Month: "abril de 2024"}
92+
93+
brands, _ := client.Brands(ctx, fipe.Cars, fipe.WithReference(308))
94+
```
95+
96+
## Error handling
97+
98+
Non-2xx responses return an `*fipe.APIError` carrying the status code and body. 404 and 429 also match sentinel errors:
99+
100+
```go
101+
vehicle, err := client.Vehicle(ctx, fipe.Cars, "59", "5940", "1900-1")
102+
switch {
103+
case errors.Is(err, fipe.ErrNotFound):
104+
// unknown brand/model/year
105+
case errors.Is(err, fipe.ErrTooManyRequests):
106+
// rate limited — back off or use a subscription token
107+
case err != nil:
108+
var apiErr *fipe.APIError
109+
if errors.As(err, &apiErr) {
110+
log.Printf("API returned %d: %s", apiErr.StatusCode, apiErr.Body)
111+
}
112+
}
113+
```
114+
115+
## Example program
116+
117+
A runnable example that lists brands and prints an Amarok price from the live API:
118+
119+
```sh
120+
go run ./examples
121+
```
122+
123+
## License
124+
125+
MIT

endpoints.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package fipe
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
)
8+
9+
// References lists the FIPE monthly reference tables, newest first.
10+
func (c *Client) References(ctx context.Context) ([]Reference, error) {
11+
var out []Reference
12+
if err := c.get(ctx, "/references", &out); err != nil {
13+
return nil, err
14+
}
15+
return out, nil
16+
}
17+
18+
// Brands lists the brands available for a vehicle type.
19+
func (c *Client) Brands(ctx context.Context, vt VehicleType, opts ...RequestOption) ([]Brand, error) {
20+
var out []Brand
21+
path := fmt.Sprintf("/%s/brands", vt)
22+
if err := c.get(ctx, path, &out, opts...); err != nil {
23+
return nil, err
24+
}
25+
return out, nil
26+
}
27+
28+
// Models lists the models of a brand.
29+
func (c *Client) Models(ctx context.Context, vt VehicleType, brandID string, opts ...RequestOption) ([]Model, error) {
30+
var out []Model
31+
path := fmt.Sprintf("/%s/brands/%s/models", vt, url.PathEscape(brandID))
32+
if err := c.get(ctx, path, &out, opts...); err != nil {
33+
return nil, err
34+
}
35+
return out, nil
36+
}
37+
38+
// Years lists the model-year variants of a model.
39+
func (c *Client) Years(ctx context.Context, vt VehicleType, brandID, modelID string, opts ...RequestOption) ([]Year, error) {
40+
var out []Year
41+
path := fmt.Sprintf("/%s/brands/%s/models/%s/years", vt, url.PathEscape(brandID), url.PathEscape(modelID))
42+
if err := c.get(ctx, path, &out, opts...); err != nil {
43+
return nil, err
44+
}
45+
return out, nil
46+
}
47+
48+
// Vehicle returns the FIPE price details for a brand, model and year.
49+
func (c *Client) Vehicle(ctx context.Context, vt VehicleType, brandID, modelID, yearID string, opts ...RequestOption) (*Vehicle, error) {
50+
var out Vehicle
51+
path := fmt.Sprintf("/%s/brands/%s/models/%s/years/%s", vt, url.PathEscape(brandID), url.PathEscape(modelID), url.PathEscape(yearID))
52+
if err := c.get(ctx, path, &out, opts...); err != nil {
53+
return nil, err
54+
}
55+
return &out, nil
56+
}
57+
58+
// YearsByBrand lists all model years available for a brand.
59+
func (c *Client) YearsByBrand(ctx context.Context, vt VehicleType, brandID string, opts ...RequestOption) ([]Year, error) {
60+
var out []Year
61+
path := fmt.Sprintf("/%s/brands/%s/years", vt, url.PathEscape(brandID))
62+
if err := c.get(ctx, path, &out, opts...); err != nil {
63+
return nil, err
64+
}
65+
return out, nil
66+
}
67+
68+
// ModelsByBrandYear lists the models of a brand available for a given year.
69+
func (c *Client) ModelsByBrandYear(ctx context.Context, vt VehicleType, brandID, yearID string, opts ...RequestOption) ([]Model, error) {
70+
var out []Model
71+
path := fmt.Sprintf("/%s/brands/%s/years/%s/models", vt, url.PathEscape(brandID), url.PathEscape(yearID))
72+
if err := c.get(ctx, path, &out, opts...); err != nil {
73+
return nil, err
74+
}
75+
return out, nil
76+
}
77+
78+
// YearsByFipeCode lists the model-year variants of a vehicle by its FIPE code
79+
// (e.g. "005340-6").
80+
func (c *Client) YearsByFipeCode(ctx context.Context, vt VehicleType, fipeCode string, opts ...RequestOption) ([]Year, error) {
81+
var out []Year
82+
path := fmt.Sprintf("/%s/%s/years", vt, url.PathEscape(fipeCode))
83+
if err := c.get(ctx, path, &out, opts...); err != nil {
84+
return nil, err
85+
}
86+
return out, nil
87+
}
88+
89+
// VehicleByFipeCode returns the FIPE price details for a vehicle by its FIPE
90+
// code and year.
91+
func (c *Client) VehicleByFipeCode(ctx context.Context, vt VehicleType, fipeCode, yearID string, opts ...RequestOption) (*Vehicle, error) {
92+
var out Vehicle
93+
path := fmt.Sprintf("/%s/%s/years/%s", vt, url.PathEscape(fipeCode), url.PathEscape(yearID))
94+
if err := c.get(ctx, path, &out, opts...); err != nil {
95+
return nil, err
96+
}
97+
return &out, nil
98+
}
99+
100+
// HistoryByFipeCode returns the vehicle details including its price history
101+
// across reference months.
102+
func (c *Client) HistoryByFipeCode(ctx context.Context, vt VehicleType, fipeCode, yearID string, opts ...RequestOption) (*Vehicle, error) {
103+
var out Vehicle
104+
path := fmt.Sprintf("/%s/%s/years/%s/history", vt, url.PathEscape(fipeCode), url.PathEscape(yearID))
105+
if err := c.get(ctx, path, &out, opts...); err != nil {
106+
return nil, err
107+
}
108+
return &out, nil
109+
}

errors.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package fipe
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
)
8+
9+
// Sentinel errors for common API failures. APIError unwraps to these, so
10+
// callers can match with errors.Is regardless of which form they prefer.
11+
var (
12+
ErrNotFound = errors.New("fipe: not found")
13+
ErrTooManyRequests = errors.New("fipe: too many requests")
14+
)
15+
16+
// APIError is returned when the API responds with a non-2xx status.
17+
type APIError struct {
18+
StatusCode int
19+
Body string
20+
}
21+
22+
func (e *APIError) Error() string {
23+
if e.Body != "" {
24+
return fmt.Sprintf("fipe: unexpected status %d: %s", e.StatusCode, e.Body)
25+
}
26+
return fmt.Sprintf("fipe: unexpected status %d", e.StatusCode)
27+
}
28+
29+
func (e *APIError) Unwrap() error {
30+
switch e.StatusCode {
31+
case http.StatusNotFound:
32+
return ErrNotFound
33+
case http.StatusTooManyRequests:
34+
return ErrTooManyRequests
35+
}
36+
return nil
37+
}

0 commit comments

Comments
 (0)