├── go.mod ├── README.md └── client.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/timleland/t.ly-go-url-shortener-api 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # T.LY Go Client 2 | 3 | This Go package provides a client for the T.LY URL Shortener API. It includes methods for all API endpoints such as managing pixels, short links, stats, and tags. 4 | 5 | ## Installation 6 | 7 | **Obtain an API Token**: Sign up or log in to [T.LY](https://t.ly/settings#/api) and retrieve your API token from the T.LY dashboard. 8 | 9 | To install the package, run: 10 | 11 | ```bash 12 | go get github.com/timleland/t.ly-go-url-shortener-api 13 | ``` 14 | 15 | Then import it in your Go code: 16 | 17 | ```go 18 | import "github.com/timleland/t.ly-go-url-shortener-api" 19 | ``` 20 | 21 | ## Usage 22 | 23 | Create a new client by providing your API token: 24 | 25 | ```go 26 | client := tly.NewClient("YOUR_API_TOKEN") 27 | ``` 28 | 29 | ### Pixel Management 30 | 31 | #### Create a Pixel 32 | 33 | ```go 34 | pixelReq := tly.PixelCreateRequest{ 35 | Name: "GTMPixel", 36 | PixelID: "GTM-xxxx", 37 | PixelType: "googleTagManager", 38 | } 39 | pixel, err := client.CreatePixel(pixelReq) 40 | if err != nil { 41 | // handle error 42 | } 43 | fmt.Println("Created Pixel:", pixel) 44 | ``` 45 | 46 | #### List Pixels 47 | 48 | ```go 49 | pixels, err := client.ListPixels() 50 | if err != nil { 51 | // handle error 52 | } 53 | fmt.Println("Pixels:", pixels) 54 | ``` 55 | 56 | #### Get a Pixel 57 | 58 | ```go 59 | pixel, err := client.GetPixel(12345) 60 | if err != nil { 61 | // handle error 62 | } 63 | fmt.Println("Pixel:", pixel) 64 | ``` 65 | 66 | #### Update a Pixel 67 | 68 | ```go 69 | updateReq := tly.PixelUpdateRequest{ 70 | ID: 12345, 71 | Name: "UpdatedPixel", 72 | PixelID: "GTM-xxxx", 73 | PixelType: "googleTagManager", 74 | } 75 | updatedPixel, err := client.UpdatePixel(updateReq) 76 | if err != nil { 77 | // handle error 78 | } 79 | fmt.Println("Updated Pixel:", updatedPixel) 80 | ``` 81 | 82 | #### Delete a Pixel 83 | 84 | ```go 85 | err = client.DeletePixel(12345) 86 | if err != nil { 87 | // handle error 88 | } 89 | fmt.Println("Pixel deleted") 90 | ``` 91 | 92 | ### Short Link Management 93 | 94 | #### Create a Short Link 95 | 96 | ```go 97 | shortLinkReq := tly.ShortLinkCreateRequest{ 98 | LongURL: "http://example.com/", 99 | Domain: "https://t.ly/", 100 | } 101 | shortLink, err := client.CreateShortLink(shortLinkReq) 102 | if err != nil { 103 | // handle error 104 | } 105 | fmt.Println("Created Short Link:", shortLink) 106 | ``` 107 | 108 | #### Get a Short Link 109 | 110 | ```go 111 | link, err := client.GetShortLink("https://t.ly/c55j") 112 | if err != nil { 113 | // handle error 114 | } 115 | fmt.Println("Short Link:", link) 116 | ``` 117 | 118 | #### Update a Short Link 119 | 120 | ```go 121 | updateLinkReq := tly.ShortLinkUpdateRequest{ 122 | ShortURL: "https://t.ly/c55j", 123 | LongURL: "http://updated-example.com/", 124 | } 125 | updatedLink, err := client.UpdateShortLink(updateLinkReq) 126 | if err != nil { 127 | // handle error 128 | } 129 | fmt.Println("Updated Short Link:", updatedLink) 130 | ``` 131 | 132 | #### Delete a Short Link 133 | 134 | ```go 135 | err = client.DeleteShortLink("https://t.ly/c55j") 136 | if err != nil { 137 | // handle error 138 | } 139 | fmt.Println("Short Link deleted") 140 | ``` 141 | 142 | #### Expand a Short Link 143 | 144 | ```go 145 | expandReq := tly.ExpandRequest{ 146 | ShortURL: "https://t.ly/OYXL", 147 | } 148 | expanded, err := client.ExpandShortLink(expandReq) 149 | if err != nil { 150 | // handle error 151 | } 152 | fmt.Println("Expanded URL:", expanded.LongURL) 153 | ``` 154 | 155 | #### List Short Links 156 | 157 | ```go 158 | params := map[string]string{ 159 | "search": "amazon", 160 | } 161 | links, err := client.ListShortLinks(params) 162 | if err != nil { 163 | // handle error 164 | } 165 | fmt.Println("Short Links List:", links) 166 | ``` 167 | 168 | #### Bulk Shorten Links 169 | 170 | ```go 171 | bulkReq := tly.BulkShortenRequest{ 172 | Domain: "https://t.ly/", 173 | Links: []string{"http://example1.com", "http://example2.com"}, 174 | } 175 | result, err := client.BulkShortenLinks(bulkReq) 176 | if err != nil { 177 | // handle error 178 | } 179 | fmt.Println("Bulk Shorten Result:", result) 180 | ``` 181 | 182 | ### Stats Management 183 | 184 | #### Get Stats for a Short Link 185 | 186 | ```go 187 | stats, err := client.GetStats("https://t.ly/OYXL") 188 | if err != nil { 189 | // handle error 190 | } 191 | fmt.Println("Stats:", stats) 192 | ``` 193 | 194 | ### Tag Management 195 | 196 | #### List Tags 197 | 198 | ```go 199 | tags, err := client.ListTags() 200 | if err != nil { 201 | // handle error 202 | } 203 | fmt.Println("Tags:", tags) 204 | ``` 205 | 206 | #### Create a Tag 207 | 208 | ```go 209 | tag, err := client.CreateTag("fall2024") 210 | if err != nil { 211 | // handle error 212 | } 213 | fmt.Println("Created Tag:", tag) 214 | ``` 215 | 216 | #### Get a Tag 217 | 218 | ```go 219 | tag, err := client.GetTag(12345) 220 | if err != nil { 221 | // handle error 222 | } 223 | fmt.Println("Tag:", tag) 224 | ``` 225 | 226 | #### Update a Tag 227 | 228 | ```go 229 | updatedTag, err := client.UpdateTag(12345, "fall2025") 230 | if err != nil { 231 | // handle error 232 | } 233 | fmt.Println("Updated Tag:", updatedTag) 234 | ``` 235 | 236 | #### Delete a Tag 237 | 238 | ```go 239 | err = client.DeleteTag(12345) 240 | if err != nil { 241 | // handle error 242 | } 243 | fmt.Println("Tag deleted") 244 | ``` 245 | 246 | ## License 247 | 248 | This project is licensed under the MIT License. 249 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package tly 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | // Client is the main API client for T.LY. 12 | type Client struct { 13 | APIKey string 14 | BaseURL string 15 | Client *http.Client 16 | } 17 | 18 | // NewClient creates a new T.LY API client. 19 | func NewClient(apiKey string) *Client { 20 | return &Client{ 21 | APIKey: apiKey, 22 | BaseURL: "https://api.t.ly", 23 | Client: &http.Client{}, 24 | } 25 | } 26 | 27 | // doRequest is an internal helper for making API calls. 28 | func (c *Client) doRequest(method, path, query string, body interface{}, result interface{}) error { 29 | url := c.BaseURL + path 30 | if query != "" { 31 | url += "?" + query 32 | } 33 | var buf *bytes.Buffer 34 | if body != nil { 35 | data, err := json.Marshal(body) 36 | if err != nil { 37 | return err 38 | } 39 | buf = bytes.NewBuffer(data) 40 | } else { 41 | buf = bytes.NewBuffer(nil) 42 | } 43 | req, err := http.NewRequest(method, url, buf) 44 | if err != nil { 45 | return err 46 | } 47 | req.Header.Set("Authorization", "Bearer "+c.APIKey) 48 | req.Header.Set("Content-Type", "application/json") 49 | req.Header.Set("Accept", "application/json") 50 | resp, err := c.Client.Do(req) 51 | if err != nil { 52 | return err 53 | } 54 | defer resp.Body.Close() 55 | if resp.StatusCode < 200 || resp.StatusCode >= 300 { 56 | data, _ := ioutil.ReadAll(resp.Body) 57 | return fmt.Errorf("API error: %s", string(data)) 58 | } 59 | if result != nil { 60 | return json.NewDecoder(resp.Body).Decode(result) 61 | } 62 | return nil 63 | } 64 | 65 | // ===================== 66 | // Pixel Management 67 | // ===================== 68 | 69 | // Pixel represents a pixel object. 70 | type Pixel struct { 71 | ID int `json:"id"` 72 | Name string `json:"name"` 73 | PixelID string `json:"pixel_id"` 74 | PixelType string `json:"pixel_type"` 75 | CreatedAt string `json:"created_at"` 76 | UpdatedAt string `json:"updated_at"` 77 | } 78 | 79 | // PixelCreateRequest is used to create a new pixel. 80 | type PixelCreateRequest struct { 81 | Name string `json:"name"` 82 | PixelID string `json:"pixel_id"` 83 | PixelType string `json:"pixel_type"` 84 | } 85 | 86 | // PixelUpdateRequest is used to update a pixel. 87 | type PixelUpdateRequest struct { 88 | ID int `json:"id"` 89 | Name string `json:"name"` 90 | PixelID string `json:"pixel_id"` 91 | PixelType string `json:"pixel_type"` 92 | } 93 | 94 | // CreatePixel calls the API to create a new pixel. 95 | func (c *Client) CreatePixel(reqData PixelCreateRequest) (*Pixel, error) { 96 | var pixel Pixel 97 | err := c.doRequest("POST", "/api/v1/link/pixel", "", reqData, &pixel) 98 | if err != nil { 99 | return nil, err 100 | } 101 | return &pixel, nil 102 | } 103 | 104 | // ListPixels retrieves a list of pixels. 105 | func (c *Client) ListPixels() ([]Pixel, error) { 106 | var pixels []Pixel 107 | err := c.doRequest("GET", "/api/v1/link/pixel", "", nil, &pixels) 108 | if err != nil { 109 | return nil, err 110 | } 111 | return pixels, nil 112 | } 113 | 114 | // GetPixel retrieves a pixel by its ID. 115 | func (c *Client) GetPixel(id int) (*Pixel, error) { 116 | path := fmt.Sprintf("/api/v1/link/pixel/%d", id) 117 | var pixel Pixel 118 | err := c.doRequest("GET", path, "", nil, &pixel) 119 | if err != nil { 120 | return nil, err 121 | } 122 | return &pixel, nil 123 | } 124 | 125 | // UpdatePixel updates an existing pixel. 126 | func (c *Client) UpdatePixel(reqData PixelUpdateRequest) (*Pixel, error) { 127 | path := fmt.Sprintf("/api/v1/link/pixel/%d", reqData.ID) 128 | var pixel Pixel 129 | err := c.doRequest("PUT", path, "", reqData, &pixel) 130 | if err != nil { 131 | return nil, err 132 | } 133 | return &pixel, nil 134 | } 135 | 136 | // DeletePixel deletes a pixel by its ID. 137 | func (c *Client) DeletePixel(id int) error { 138 | path := fmt.Sprintf("/api/v1/link/pixel/%d", id) 139 | return c.doRequest("DELETE", path, "", nil, nil) 140 | } 141 | 142 | // ===================== 143 | // Short Link Management 144 | // ===================== 145 | 146 | // ShortLink represents a shortened URL. 147 | type ShortLink struct { 148 | ShortURL string `json:"short_url"` 149 | Description string `json:"description"` 150 | LongURL string `json:"long_url"` 151 | Domain string `json:"domain"` 152 | ShortID string `json:"short_id"` 153 | ExpireAtViews interface{} `json:"expire_at_views"` 154 | ExpireAtDatetime interface{} `json:"expire_at_datetime"` 155 | PublicStats bool `json:"public_stats"` 156 | CreatedAt string `json:"created_at"` 157 | UpdatedAt string `json:"updated_at"` 158 | Meta interface{} `json:"meta"` 159 | } 160 | 161 | // ShortLinkCreateRequest is used to create a short link. 162 | type ShortLinkCreateRequest struct { 163 | LongURL string `json:"long_url"` 164 | ShortID *string `json:"short_id,omitempty"` 165 | Domain string `json:"domain"` 166 | ExpireAtDatetime *string `json:"expire_at_datetime,omitempty"` 167 | ExpireAtViews *int `json:"expire_at_views,omitempty"` 168 | Description *string `json:"description,omitempty"` 169 | PublicStats *bool `json:"public_stats,omitempty"` 170 | Password *string `json:"password,omitempty"` 171 | Tags []int `json:"tags,omitempty"` 172 | Pixels []int `json:"pixels,omitempty"` 173 | Meta interface{} `json:"meta,omitempty"` 174 | } 175 | 176 | // ShortLinkUpdateRequest is used to update a short link. 177 | type ShortLinkUpdateRequest struct { 178 | ShortURL string `json:"short_url"` 179 | ShortID *string `json:"short_id,omitempty"` 180 | LongURL string `json:"long_url"` 181 | ExpireAtDatetime *string `json:"expire_at_datetime,omitempty"` 182 | ExpireAtViews *int `json:"expire_at_views,omitempty"` 183 | Description *string `json:"description,omitempty"` 184 | PublicStats *bool `json:"public_stats,omitempty"` 185 | Password *string `json:"password,omitempty"` 186 | Tags []int `json:"tags,omitempty"` 187 | Pixels []int `json:"pixels,omitempty"` 188 | Meta interface{} `json:"meta,omitempty"` 189 | } 190 | 191 | // CreateShortLink creates a new short link. 192 | func (c *Client) CreateShortLink(reqData ShortLinkCreateRequest) (*ShortLink, error) { 193 | var link ShortLink 194 | err := c.doRequest("POST", "/api/v1/link/shorten", "", reqData, &link) 195 | if err != nil { 196 | return nil, err 197 | } 198 | return &link, nil 199 | } 200 | 201 | // GetShortLink retrieves a short link using its URL. 202 | func (c *Client) GetShortLink(shortURL string) (*ShortLink, error) { 203 | query := "short_url=" + shortURL 204 | var link ShortLink 205 | err := c.doRequest("GET", "/api/v1/link", query, nil, &link) 206 | if err != nil { 207 | return nil, err 208 | } 209 | return &link, nil 210 | } 211 | 212 | // UpdateShortLink updates an existing short link. 213 | func (c *Client) UpdateShortLink(reqData ShortLinkUpdateRequest) (*ShortLink, error) { 214 | var link ShortLink 215 | err := c.doRequest("PUT", "/api/v1/link", "", reqData, &link) 216 | if err != nil { 217 | return nil, err 218 | } 219 | return &link, nil 220 | } 221 | 222 | // DeleteShortLink deletes a short link. 223 | func (c *Client) DeleteShortLink(shortURL string) error { 224 | reqBody := map[string]string{ 225 | "short_url": shortURL, 226 | } 227 | return c.doRequest("DELETE", "/api/v1/link", "", reqBody, nil) 228 | } 229 | 230 | // ExpandRequest is used to expand a short link. 231 | type ExpandRequest struct { 232 | ShortURL string `json:"short_url"` 233 | Password *string `json:"password,omitempty"` 234 | } 235 | 236 | // ExpandResponse represents the response when expanding a short link. 237 | type ExpandResponse struct { 238 | LongURL string `json:"long_url"` 239 | Expired bool `json:"expired"` 240 | } 241 | 242 | // ExpandShortLink expands a short URL to its original long URL. 243 | func (c *Client) ExpandShortLink(reqData ExpandRequest) (*ExpandResponse, error) { 244 | var resp ExpandResponse 245 | err := c.doRequest("POST", "/api/v1/link/expand", "", reqData, &resp) 246 | if err != nil { 247 | return nil, err 248 | } 249 | return &resp, nil 250 | } 251 | 252 | // ListShortLinks retrieves a list of short links using optional query parameters. 253 | // The queryParams map can include keys such as "search", "tag_ids", "pixel_ids", etc. 254 | func (c *Client) ListShortLinks(queryParams map[string]string) (string, error) { 255 | query := "" 256 | first := true 257 | for k, v := range queryParams { 258 | if !first { 259 | query += "&" 260 | } 261 | query += fmt.Sprintf("%s=%s", k, v) 262 | first = false 263 | } 264 | // The API returns a plain text JSON string. 265 | var result string 266 | err := c.doRequest("GET", "/api/v1/link/list", query, nil, &result) 267 | if err != nil { 268 | return "", err 269 | } 270 | return result, nil 271 | } 272 | 273 | // BulkShortenRequest is used for bulk shortening of links. 274 | type BulkShortenRequest struct { 275 | Domain string `json:"domain"` 276 | Links []string `json:"links"` // For simplicity, using a slice of URLs. 277 | Tags []int `json:"tags,omitempty"` 278 | Pixels []int `json:"pixels,omitempty"` 279 | } 280 | 281 | // BulkShortenLinks sends a bulk shorten request. 282 | func (c *Client) BulkShortenLinks(reqData BulkShortenRequest) (string, error) { 283 | var result string 284 | err := c.doRequest("POST", "/api/v1/link/bulk", "", reqData, &result) 285 | if err != nil { 286 | return "", err 287 | } 288 | return result, nil 289 | } 290 | 291 | // ===================== 292 | // Stats Management 293 | // ===================== 294 | 295 | // Stats represents the statistics for a short link. 296 | type Stats struct { 297 | Clicks int `json:"clicks"` 298 | UniqueClicks int `json:"unique_clicks"` 299 | Browsers []interface{} `json:"browsers"` 300 | Countries []interface{} `json:"countries"` 301 | Referrers []interface{} `json:"referrers"` 302 | Platforms []interface{} `json:"platforms"` 303 | DailyClicks []interface{} `json:"daily_clicks"` 304 | Data map[string]interface{} `json:"data"` 305 | } 306 | 307 | // GetStats retrieves statistics for a given short link. 308 | func (c *Client) GetStats(shortURL string) (*Stats, error) { 309 | query := "short_url=" + shortURL 310 | var stats Stats 311 | err := c.doRequest("GET", "/api/v1/link/stats", query, nil, &stats) 312 | if err != nil { 313 | return nil, err 314 | } 315 | return &stats, nil 316 | } 317 | 318 | // ===================== 319 | // Tag Management 320 | // ===================== 321 | 322 | // Tag represents a tag. 323 | type Tag struct { 324 | ID int `json:"id"` 325 | Tag string `json:"tag"` 326 | CreatedAt string `json:"created_at"` 327 | UpdatedAt string `json:"updated_at"` 328 | } 329 | 330 | // ListTags retrieves all tags. 331 | func (c *Client) ListTags() ([]Tag, error) { 332 | var tags []Tag 333 | err := c.doRequest("GET", "/api/v1/link/tag", "", nil, &tags) 334 | if err != nil { 335 | return nil, err 336 | } 337 | return tags, nil 338 | } 339 | 340 | // CreateTag creates a new tag. 341 | func (c *Client) CreateTag(tagValue string) (*Tag, error) { 342 | reqBody := map[string]string{ 343 | "tag": tagValue, 344 | } 345 | var tag Tag 346 | err := c.doRequest("POST", "/api/v1/link/tag", "", reqBody, &tag) 347 | if err != nil { 348 | return nil, err 349 | } 350 | return &tag, nil 351 | } 352 | 353 | // GetTag retrieves a tag by its ID. 354 | func (c *Client) GetTag(id int) (*Tag, error) { 355 | path := fmt.Sprintf("/api/v1/link/tag/%d", id) 356 | var tag Tag 357 | err := c.doRequest("GET", path, "", nil, &tag) 358 | if err != nil { 359 | return nil, err 360 | } 361 | return &tag, nil 362 | } 363 | 364 | // UpdateTag updates an existing tag. 365 | func (c *Client) UpdateTag(id int, tagValue string) (*Tag, error) { 366 | path := fmt.Sprintf("/api/v1/link/tag/%d", id) 367 | reqBody := map[string]string{ 368 | "tag": tagValue, 369 | } 370 | var tag Tag 371 | err := c.doRequest("PUT", path, "", reqBody, &tag) 372 | if err != nil { 373 | return nil, err 374 | } 375 | return &tag, nil 376 | } 377 | 378 | // DeleteTag deletes a tag by its ID. 379 | func (c *Client) DeleteTag(id int) error { 380 | path := fmt.Sprintf("/api/v1/link/tag/%d", id) 381 | return c.doRequest("DELETE", path, "", nil, nil) 382 | } 383 | --------------------------------------------------------------------------------