{"id":47675521,"url":"https://github.com/tphakala/go-autotask","last_synced_at":"2026-04-02T13:29:01.716Z","repository":{"id":346365910,"uuid":"1189617710","full_name":"tphakala/go-autotask","owner":"tphakala","description":"Go client library for the Autotask PSA REST API","archived":false,"fork":false,"pushed_at":"2026-03-24T11:43:38.000Z","size":115,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-24T11:46:48.042Z","etag":null,"topics":["api-client","autotask","autotask-api","autotask-psa","datto","go","golang","professional-services-automation","psa","rest-api"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tphakala.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-23T13:57:31.000Z","updated_at":"2026-03-24T11:43:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tphakala/go-autotask","commit_stats":null,"previous_names":["tphakala/go-autotask"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/tphakala/go-autotask","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-autotask","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-autotask/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-autotask/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-autotask/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tphakala","download_url":"https://codeload.github.com/tphakala/go-autotask/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tphakala%2Fgo-autotask/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31307096,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-client","autotask","autotask-api","autotask-psa","datto","go","golang","professional-services-automation","psa","rest-api"],"created_at":"2026-04-02T13:29:00.738Z","updated_at":"2026-04-02T13:29:01.701Z","avatar_url":"https://github.com/tphakala.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-autotask\n\nA Go client library for the [Autotask PSA](https://www.autotask.net/) REST API.\n\n```go\nclient, err := autotask.NewClient(ctx, autotask.AuthConfig{\n    Username:        os.Getenv(\"AUTOTASK_USERNAME\"),\n    Secret:          os.Getenv(\"AUTOTASK_SECRET\"),\n    IntegrationCode: os.Getenv(\"AUTOTASK_INTEGRATION_CODE\"),\n})\n```\n\n## Features\n\n- **Type-safe CRUD** — generic `Get`, `List`, `Create`, `Update`, `Delete` functions for any entity\n- **Query builder** — fluent API with `Where`, `Or`, `And`, field selection, and limits\n- **Iterator pagination** — `ListIter` returns `iter.Seq2` for memory-efficient large result sets\n- **Optional fields** — three-state `Optional[T]` type (unset / null / value) for correct API semantics\n- **Middleware** — composable rate limiter, circuit breaker, and API threshold monitor\n- **Raw operations** — `GetRaw`, `ListRaw`, etc. for entities not defined in the library\n- **Child entities** — `GetChild` and `CreateChild` for parent-child relationships\n- **Metadata introspection** — query field definitions, UDFs, and entity capabilities at runtime\n- **Code generation** — `autotask-gen` generates entity structs from live API metadata\n- **Test support** — `autotasktest.NewMockClient` for in-memory testing with fixtures\n- **Automatic zone discovery** — resolves the correct API endpoint for your account\n\n## Install\n\n```\ngo get github.com/tphakala/go-autotask\n```\n\nRequires Go 1.26 or later.\n\n## Quick start\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"os\"\n\n    autotask \"github.com/tphakala/go-autotask\"\n    \"github.com/tphakala/go-autotask/entities\"\n)\n\nfunc main() {\n    ctx := context.Background()\n    client, err := autotask.NewClient(ctx, autotask.AuthConfig{\n        Username:        os.Getenv(\"AUTOTASK_USERNAME\"),\n        Secret:          os.Getenv(\"AUTOTASK_SECRET\"),\n        IntegrationCode: os.Getenv(\"AUTOTASK_INTEGRATION_CODE\"),\n    })\n    if err != nil {\n        panic(err)\n    }\n    defer func() { _ = client.Close() }()\n\n    // Get a ticket by ID.\n    ticket, err := autotask.Get[entities.Ticket](ctx, client, 12345)\n    if err != nil {\n        panic(err)\n    }\n    if title, ok := ticket.Title.Get(); ok {\n        fmt.Println(title)\n    }\n}\n```\n\n## CRUD operations\n\nAll CRUD functions are generic over any type implementing the `Entity` interface:\n\n```go\n// Get by ID\nticket, err := autotask.Get[entities.Ticket](ctx, client, 42)\n\n// List with query\ntickets, err := autotask.List[entities.Ticket](ctx, client,\n    autotask.NewQuery().Where(\"status\", autotask.OpEq, 1),\n)\n\n// Count\nn, err := autotask.Count[entities.Ticket](ctx, client, autotask.NewQuery())\n\n// Create\ncreated, err := autotask.Create(ctx, client, \u0026entities.Ticket{\n    Title:     autotask.Set(\"Server down\"),\n    CompanyID: autotask.Set(int64(123)),\n    Status:    autotask.Set(1),\n    Priority:  autotask.Set(2),\n})\n\n// Update\nupdated, err := autotask.Update(ctx, client, ticket)\n\n// Delete\nerr = autotask.Delete[entities.Ticket](ctx, client, 42)\n```\n\n## Query builder\n\n```go\nq := autotask.NewQuery().\n    Where(\"status\", autotask.OpEq, 1).\n    Or(\n        autotask.Field(\"priority\", autotask.OpEq, 1),\n        autotask.Field(\"priority\", autotask.OpEq, 2),\n    ).\n    Fields(\"id\", \"title\", \"status\", \"priority\").\n    Limit(50)\n\ntickets, err := autotask.List[entities.Ticket](ctx, client, q)\n```\n\nAvailable operators: `OpEq`, `OpNotEq`, `OpGt`, `OpGte`, `OpLt`, `OpLte`, `OpBeginsWith`, `OpEndsWith`, `OpContains`, `OpExist`, `OpNotExist`, `OpIn`, `OpNotIn`.\n\n## Iterator pagination\n\nFor large result sets, `ListIter` returns a Go iterator that fetches pages on demand:\n\n```go\nfor ticket, err := range autotask.ListIter[entities.Ticket](ctx, client, autotask.NewQuery()) {\n    if err != nil {\n        return err\n    }\n    title, _ := ticket.Title.Get()\n    fmt.Println(title)\n}\n```\n\n## Optional fields\n\nAutotask fields can be unset, explicitly null, or have a value. `Optional[T]` handles all three states:\n\n```go\nticket := \u0026entities.Ticket{\n    Title:    autotask.Set(\"My ticket\"),    // set to a value\n    Priority: autotask.Null[int](),         // explicitly null\n    // Status is omitted — unset, not sent in the request\n}\n\nif title, ok := ticket.Title.Get(); ok {\n    fmt.Println(title)\n}\n```\n\n## Middleware\n\n### Rate limiter\n\n```go\nclient, err := autotask.NewClient(ctx, auth,\n    autotask.WithRateLimiter(\n        middleware.WithRequestsPerHour(8000),\n        middleware.WithBurstSize(10),\n        middleware.WithAdaptiveDelay(true),\n    ),\n)\n```\n\nToken-bucket rate limiting with adaptive delays. Automatically respects `Retry-After` headers on 429 responses.\n\n### Circuit breaker\n\n```go\nclient, err := autotask.NewClient(ctx, auth,\n    autotask.WithCircuitBreaker(\n        middleware.WithFailureThreshold(5),\n        middleware.WithOpenTimeout(30 * time.Second),\n    ),\n)\n```\n\nThree-state circuit breaker (closed → open → half-open) that stops sending requests after repeated failures.\n\n### Threshold monitor\n\n```go\nclient, err := autotask.NewClient(ctx, auth,\n    autotask.WithThresholdMonitor(\n        middleware.WithCheckInterval(5 * time.Minute),\n        middleware.WithWarningCallback(func(info middleware.ThresholdInfo) {\n            log.Printf(\"API usage at %.0f%%\", info.UsagePercent)\n        }),\n        middleware.WithCriticalCallback(func(info middleware.ThresholdInfo) {\n            log.Printf(\"CRITICAL: API usage at %.0f%%\", info.UsagePercent)\n        }),\n    ),\n)\n```\n\nPolls the Autotask ThresholdInformation endpoint in the background and invokes callbacks when usage crosses 75% (warning) or 90% (critical).\n\n## Raw operations\n\nFor entities not defined in the library, use the untyped API:\n\n```go\nresult, err := autotask.GetRaw(ctx, client, \"Companies\", 123)\nfmt.Println(result[\"companyName\"])\n\nresults, err := autotask.ListRaw(ctx, client, \"Companies\",\n    autotask.NewQuery().Where(\"isActive\", autotask.OpEq, true),\n)\n```\n\n## Child entities\n\n```go\n// Get all notes for a ticket\nnotes, err := autotask.GetChild[entities.Ticket, entities.TicketNote](ctx, client, ticketID)\n\n// Create a note on a ticket\nnote, err := autotask.CreateChild[entities.Ticket](ctx, client, ticketID, \u0026entities.TicketNote{\n    Title:       autotask.Set(\"Update\"),\n    Description: autotask.Set(\"Fixed the issue.\"),\n})\n```\n\n## Metadata\n\nQuery entity structure at runtime:\n\n```go\nimport \"github.com/tphakala/go-autotask/metadata\"\n\nfields, err := metadata.GetFields(ctx, client, \"Tickets\")\nfor _, f := range fields {\n    fmt.Printf(\"%s (%s) required=%v\\n\", f.Name, f.Type, f.IsRequired)\n}\n\nudfs, err := metadata.GetUDFs(ctx, client, \"Tickets\")\n\ninfo, err := metadata.GetEntityInfo(ctx, client, \"Tickets\")\nfmt.Printf(\"canCreate=%v canQuery=%v\\n\", info.CanCreate, info.CanQuery)\n```\n\n## Code generation\n\nGenerate entity structs from live API metadata:\n\n```\ngo run ./cmd/autotask-gen \\\n    -username user@example.com \\\n    -secret s3cret \\\n    -integration-code INT123 \\\n    -output ./entities\n```\n\n## Testing\n\nUse `autotasktest.NewMockClient` to create an in-memory client for tests:\n\n```go\nimport \"github.com/tphakala/go-autotask/autotasktest\"\n\nfunc TestMyCode(t *testing.T) {\n    client := autotasktest.NewMockClient(t,\n        autotasktest.WithFixture(\"GET\", \"/v1.0/Tickets/42\", 200, map[string]any{\n            \"item\": map[string]any{\"id\": 42, \"title\": \"Test\"},\n        }),\n    )\n    // use client in tests — server and client are cleaned up automatically\n}\n```\n\n## Client options\n\n| Option | Description |\n|--------|-------------|\n| `WithBaseURL(url)` | Override automatic zone discovery |\n| `WithHTTPClient(hc)` | Use a custom `*http.Client` |\n| `WithLogger(l)` | Structured logging via `*slog.Logger` |\n| `WithUserAgent(ua)` | Custom User-Agent header |\n| `WithImpersonation(id)` | Perform API calls as another resource |\n| `WithMiddleware(m)` | Add custom `http.RoundTripper` middleware |\n| `WithRateLimiter(opts...)` | Enable rate limiting |\n| `WithCircuitBreaker(opts...)` | Enable circuit breaker |\n| `WithThresholdMonitor(opts...)` | Enable API usage monitoring |\n\n## Available entities\n\n`Company`, `Contact`, `Ticket`, `Resource`, `Contract`, `Project`, `Task`, `ConfigurationItem`, `TicketNote`, `TimeEntry`\n\nAll entities use `Optional[T]` fields and support user-defined fields via `UserDefinedFields []autotask.UDF`.\n\n## Error handling\n\nAPI errors are returned as typed errors for easy matching:\n\n```go\nticket, err := autotask.Get[entities.Ticket](ctx, client, 999)\nif nf, ok := errors.AsType[*autotask.NotFoundError](err); ok {\n    fmt.Println(\"not found:\", nf.Err.Message)\n}\n```\n\n| Type | HTTP Status |\n|------|-------------|\n| `ValidationError` | 400 |\n| `AuthenticationError` | 401 |\n| `AuthorizationError` | 403 |\n| `NotFoundError` | 404 |\n| `ConflictError` | 409 |\n| `BusinessLogicError` | 422 |\n| `RateLimitError` | 429 |\n| `ServerError` | 5xx |\n\n`RateLimitError` includes a `RetryAfter` duration parsed from the response header.\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftphakala%2Fgo-autotask","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftphakala%2Fgo-autotask","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftphakala%2Fgo-autotask/lists"}