Documentation
¶
Index ¶
- Variables
- func CentsFromReportNumber(v float64) int64
- func ListAll[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string) ([]T, error)
- func ListAllFunc[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string, ...) error
- func ListAllFuncWithOptions[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string, ...) error
- func ListAllWithOptions[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string, ...) ([]T, error)
- func ParseRateAmount(s string) (int64, error)
- type APIError
- type CircuitBreaker
- type CircuitBreakerConfig
- type CircuitBreakerOpenError
- type Client
- func (c *Client) Close()
- func (c *Client) Delete(ctx context.Context, path string) error
- func (c *Client) DeleteReports(ctx context.Context, path string) error
- func (c *Client) DeleteWithQuery(ctx context.Context, path string, query map[string]string) error
- func (c *Client) Get(ctx context.Context, path string, query map[string]string, out any) error
- func (c *Client) GetReports(ctx context.Context, path string, query map[string]string, out any) error
- func (c *Client) GetReportsRaw(ctx context.Context, path string, query map[string]string) (*RawResponse, error)
- func (c *Client) GetValues(ctx context.Context, path string, query url.Values, out any) error
- func (c *Client) Patch(ctx context.Context, path string, body any, out any) error
- func (c *Client) Post(ctx context.Context, path string, body any, out any) error
- func (c *Client) PostMultipart(ctx context.Context, path string, form url.Values, out any) error
- func (c *Client) PostMultipartWithFiles(ctx context.Context, path string, form url.Values, files []MultipartFile, ...) error
- func (c *Client) PostReports(ctx context.Context, path string, body any, out any) error
- func (c *Client) PostReportsRaw(ctx context.Context, path string, body any) (*RawResponse, error)
- func (c *Client) PostWithQuery(ctx context.Context, path string, query map[string]string, body any, out any) error
- func (c *Client) Put(ctx context.Context, path string, body any, out any) error
- func (c *Client) PutMultipart(ctx context.Context, path string, form url.Values, out any) error
- func (c *Client) PutMultipartWithFiles(ctx context.Context, path string, form url.Values, files []MultipartFile, ...) error
- func (c *Client) PutReports(ctx context.Context, path string, body any, out any) error
- func (c *Client) PutWithQuery(ctx context.Context, path string, query map[string]string, body any, out any) error
- func (c *Client) ReportsBaseURL() string
- func (c *Client) RequestJSONValues(ctx context.Context, reportsHost bool, method, path string, query url.Values, ...) error
- func (c *Client) RequestMultipartValues(ctx context.Context, reportsHost bool, method, path string, query url.Values, ...) error
- func (c *Client) RequestMultipartValuesWithFiles(ctx context.Context, reportsHost bool, method, path string, query url.Values, ...) error
- func (c *Client) RequestRawValues(ctx context.Context, reportsHost bool, method, path string, query url.Values, ...) (*RawResponse, error)
- func (c *Client) SetCircuitBreaker(cfg CircuitBreakerConfig)
- func (c *Client) SetUserAgent(ua string)
- type ClientEntity
- type ListAllOptions
- type MultipartFile
- type PaginationCapError
- type Project
- type ProjectMembership
- type Rate
- type RawResponse
- type Tag
- type Task
- type TimeEntry
- type TimeInterval
- type User
- type Workspace
Constants ¶
This section is empty.
Variables ¶
var ErrCircuitBreakerOpen = errors.New("clockify upstream circuit breaker open")
Functions ¶
func CentsFromReportNumber ¶
CentsFromReportNumber converts a JSON number coming from the reports host (always integer-cents-as-float, per findings/rates-and-reports.md) to int64 cents using banker's rounding. The raw float is preserved by the caller for audit.
func ListAll ¶
func ListAll[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string) ([]T, error)
ListAll fetches every page into a single slice, bounded by the default pagination row cap.
func ListAllFunc ¶ added in v1.2.1
func ListAllFunc[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string, onPage func([]T) error) error
ListAllFunc scans pages and invokes onPage for each non-empty page without retaining earlier pages.
func ListAllFuncWithOptions ¶
func ListAllFuncWithOptions[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string, opts ListAllOptions, onPage func([]T) error) error
ListAllFuncWithOptions scans pages using opts and invokes onPage for each non-empty page without retaining earlier pages.
func ListAllWithOptions ¶
func ListAllWithOptions[T any](ctx context.Context, c *Client, path string, baseQuery map[string]string, opts ListAllOptions) ([]T, error)
ListAllWithOptions fetches every page into a single slice using opts.
func ParseRateAmount ¶ added in v1.2.2
ParseRateAmount accepts the JSON number form that Clockify uses in rate-update request bodies ("amount":1234 or "amount":1234.0) and returns it as int64 cents. Used by tools that need to round-trip a caller's rate amount through the Clockify HTTP client.
Types ¶
type APIError ¶
type APIError struct {
Method string
Path string
StatusCode int
Status string
Body string
RetryAfter time.Duration
}
func (*APIError) Sanitized ¶
Sanitized returns the error string without the upstream response body, suitable for hosted/shared deployments where the body might leak per-tenant information across tenants. Server-side logs still call Error() so operators can see the full diagnostic; the sanitised form is only what we hand to the MCP client.
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
func NewCircuitBreaker ¶
func NewCircuitBreaker(cfg CircuitBreakerConfig) *CircuitBreaker
func (*CircuitBreaker) After ¶
func (b *CircuitBreaker) After(endpoint, method string, upstream5xx bool)
func (*CircuitBreaker) Before ¶
func (b *CircuitBreaker) Before(endpoint, method string) error
func (*CircuitBreaker) StateValue ¶
func (b *CircuitBreaker) StateValue(endpoint, method string) float64
type CircuitBreakerConfig ¶
type CircuitBreakerOpenError ¶
func (*CircuitBreakerOpenError) Error ¶
func (e *CircuitBreakerOpenError) Error() string
func (*CircuitBreakerOpenError) Is ¶
func (e *CircuitBreakerOpenError) Is(target error) bool
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
func (*Client) DeleteReports ¶
DeleteReports performs a DELETE against the reports host.
func (*Client) DeleteWithQuery ¶
func (*Client) GetReports ¶
func (c *Client) GetReports(ctx context.Context, path string, query map[string]string, out any) error
GetReports performs a GET against the reports host. Use this for shared-report endpoints; everything else should stay on Get.
func (*Client) GetReportsRaw ¶ added in v1.2.1
func (c *Client) GetReportsRaw(ctx context.Context, path string, query map[string]string) (*RawResponse, error)
GetReportsRaw is the binary-aware sibling of GetReports. It runs the same retry/tracing/metrics pipeline but skips the JSON unmarshal, exposing the raw body bytes and response headers so callers can inspect Content-Type and Content-Disposition. Use for export endpoints; use GetReports for JSON-shaped reads.
func (*Client) PostMultipart ¶
PostMultipart performs a POST with a multipart/form-data body. Each form key maps to one or more values; multi-value keys are written as repeated form fields under the same name.
func (*Client) PostMultipartWithFiles ¶
func (c *Client) PostMultipartWithFiles(ctx context.Context, path string, form url.Values, files []MultipartFile, out any) error
PostMultipartWithFiles performs a POST with form fields plus binary file parts. File payloads are bounded to keep MCP requests from becoming an unbounded memory sink.
func (*Client) PostReports ¶
PostReports performs a POST against the reports host.
func (*Client) PostReportsRaw ¶
PostReportsRaw is the binary-aware sibling of PostReports. It is used for report exportType values such as PDF/CSV/XLSX/ZIP where the reports API may return a file body rather than JSON.
func (*Client) PostWithQuery ¶
func (*Client) PutMultipart ¶
PutMultipart performs a PUT with a multipart/form-data body. Same encoding rules as PostMultipart.
func (*Client) PutMultipartWithFiles ¶
func (c *Client) PutMultipartWithFiles(ctx context.Context, path string, form url.Values, files []MultipartFile, out any) error
PutMultipartWithFiles performs a PUT with form fields plus binary file parts.
func (*Client) PutReports ¶
PutReports performs a PUT against the reports host.
func (*Client) PutWithQuery ¶
func (*Client) ReportsBaseURL ¶
ReportsBaseURL returns the base URL for endpoints that live on Clockify's reports host (reports.api.clockify.me/v1). The reports API is a separate host from api.clockify.me — hitting the reports paths on the primary host returns 404 ("No static resource"), and hitting them on the reports host with the /api/v1 prefix is also 404. See findings/shared-reports.md.
Derivation: when the primary base URL matches the canonical production host, swap to reports.api.clockify.me and drop the /api segment. Otherwise (test stubs, custom proxies, on-prem mirrors), return the primary base URL unchanged so existing fixtures continue to work without per-test wiring.
func (*Client) RequestJSONValues ¶ added in v1.2.2
func (c *Client) RequestJSONValues(ctx context.Context, reportsHost bool, method, path string, query url.Values, body any, out any) error
RequestJSONValues is the generic JSON transport used by the allowlisted documented-API MCP surface. It deliberately stays below the domain helpers: callers must supply a validated method/path and already-normalized query.
func (*Client) RequestMultipartValues ¶ added in v1.2.2
func (c *Client) RequestMultipartValues(ctx context.Context, reportsHost bool, method, path string, query url.Values, form url.Values, out any) error
RequestMultipartValues sends multipart form fields to a validated documented endpoint. File streaming is intentionally not exposed here; the probe-lab docs currently accept form fields for the covered MCP use cases.
func (*Client) RequestMultipartValuesWithFiles ¶
func (c *Client) RequestMultipartValuesWithFiles(ctx context.Context, reportsHost bool, method, path string, query url.Values, form url.Values, files []MultipartFile, out any) error
RequestMultipartValuesWithFiles sends multipart form fields plus file parts to a validated documented endpoint.
func (*Client) RequestRawValues ¶
func (c *Client) RequestRawValues(ctx context.Context, reportsHost bool, method, path string, query url.Values, body any) (*RawResponse, error)
RequestRawValues is the binary-aware sibling of RequestJSONValues.
func (*Client) SetCircuitBreaker ¶
func (c *Client) SetCircuitBreaker(cfg CircuitBreakerConfig)
func (*Client) SetUserAgent ¶
SetUserAgent sets the User-Agent string sent with every request.
type ClientEntity ¶
type ClientEntity struct {
ID string `json:"id"`
Name string `json:"name"`
Address string `json:"address,omitempty"`
Archived bool `json:"archived,omitempty"`
CCEmails any `json:"ccEmails,omitempty"`
CurrencyCode string `json:"currencyCode,omitempty"`
CurrencyID string `json:"currencyId,omitempty"`
Email string `json:"email,omitempty"`
Note string `json:"note,omitempty"`
WorkspaceID string `json:"workspaceId,omitempty"`
}
type ListAllOptions ¶
type ListAllOptions struct {
// MaxRows bounds total decoded rows before ListAll/ListAllFunc fail
// closed. 0 uses the default production cap.
MaxRows int
}
type MultipartFile ¶
MultipartFile is a single file part for multipart/form-data requests. Data is already decoded by the caller; callers must not pass filesystem paths because MCP clients should not grant arbitrary server file access.
type PaginationCapError ¶
PaginationCapError reports that a paginated scan exceeded its row cap.
func (*PaginationCapError) Error ¶
func (e *PaginationCapError) Error() string
type Project ¶
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
ClientID string `json:"clientId,omitempty"`
ClientName string `json:"clientName,omitempty"`
Client any `json:"client,omitempty"`
Color string `json:"color,omitempty"`
Archived bool `json:"archived"`
Billable bool `json:"billable,omitempty"`
BudgetEstimate any `json:"budgetEstimate,omitempty"`
CostRate *Rate `json:"costRate,omitempty"`
Currency any `json:"currency,omitempty"`
CustomFields any `json:"customFields,omitempty"`
Duration string `json:"duration,omitempty"`
Estimate any `json:"estimate,omitempty"`
EstimateReset any `json:"estimateReset,omitempty"`
Expenses any `json:"expenses,omitempty"`
Favorite bool `json:"favorite,omitempty"`
HourlyRate *Rate `json:"hourlyRate,omitempty"`
Memberships []ProjectMembership `json:"memberships,omitempty"`
Note string `json:"note,omitempty"`
Public bool `json:"public,omitempty"`
Tasks []Task `json:"tasks,omitempty"`
Template bool `json:"template,omitempty"`
TimeEstimate any `json:"timeEstimate,omitempty"`
WorkspaceID string `json:"workspaceId,omitempty"`
}
type ProjectMembership ¶
type ProjectMembership struct {
UserID string `json:"userId"`
TargetID string `json:"targetId,omitempty"`
MembershipType string `json:"membershipType,omitempty"`
MembershipStatus string `json:"membershipStatus,omitempty"`
HourlyRate *Rate `json:"hourlyRate,omitempty"`
CostRate *Rate `json:"costRate,omitempty"`
}
ProjectMembership models the `memberships[]` element returned on projects, tasks, workspaces, and users. Per-member rates here override their parent scope; nil means "fall through to parent."
type Rate ¶
type Rate struct {
Amount int64 `json:"amount"`
Currency string `json:"currency,omitempty"`
Inherited bool `json:"inherited,omitempty"`
}
Rate is Clockify's RateDtoV1 / HourlyRateDtoV1. The Amount is always in the minor unit of the currency (cents for EUR/USD/GBP, etc.), as an integer. e.g. {amount: 4321, currency: "EUR"} represents €43.21.
Clockify can also serialise a rate as the literal string "##default", meaning "inherit from the parent scope (workspace/project/task)." We preserve that as Inherited=true with Amount=0 so callers can render it as "(inherited)" rather than dropping it.
func (*Rate) DecimalString ¶
DecimalString renders Amount/100 with exactly two decimal places. Negative amounts are preserved verbatim. e.g. 4321 -> "43.21", 100 -> "1.00", -250 -> "-2.50".
func (*Rate) Display ¶
Display returns the rate in "<symbol><decimal> <CCY>" form for the few currencies we know symbols for, falling back to "<decimal> <CCY>". Examples: 4321 EUR -> "€43.21 EUR"; 4321 USD -> "$43.21 USD"; 4321 XYZ -> "43.21 XYZ".
func (*Rate) MarshalJSON ¶
func (*Rate) UnmarshalJSON ¶
type RawResponse ¶
RawResponse is the binary-aware envelope returned by GetReportsRaw — the raw response bytes plus enough header context for callers to recover Content-Type and Content-Disposition (filename). Used by the shared-reports export endpoint where the upstream returns binary (PDF/XLSX) or non-JSON text (CSV).
type Task ¶
type Task struct {
ID string `json:"id"`
Name string `json:"name"`
ProjectID string `json:"projectId"`
AssigneeID string `json:"assigneeId,omitempty"`
AssigneeIDs []string `json:"assigneeIds,omitempty"`
Billable bool `json:"billable"`
BudgetEstimate int64 `json:"budgetEstimate,omitempty"`
CostRate *Rate `json:"costRate,omitempty"`
Duration string `json:"duration,omitempty"`
Estimate string `json:"estimate,omitempty"`
HourlyRate *Rate `json:"hourlyRate,omitempty"`
Status string `json:"status,omitempty"`
UserGroupIDs []string `json:"userGroupIds,omitempty"`
}
type TimeEntry ¶
type TimeEntry struct {
ID string `json:"id"`
Description string `json:"description"`
ProjectID string `json:"projectId"`
ProjectName string `json:"projectName,omitempty"`
TaskID string `json:"taskId,omitempty"`
TagIDs []string `json:"tagIds,omitempty"`
Billable bool `json:"billable,omitempty"`
CostRate *Rate `json:"costRate,omitempty"`
CustomFieldValues any `json:"customFieldValues,omitempty"`
HourlyRate *Rate `json:"hourlyRate,omitempty"`
IsLocked bool `json:"isLocked,omitempty"`
KioskID string `json:"kioskId,omitempty"`
Type string `json:"type,omitempty"`
UserID string `json:"userId,omitempty"`
WorkspaceID string `json:"workspaceId,omitempty"`
TimeInterval TimeInterval `json:"timeInterval"`
}
func (TimeEntry) DurationSeconds ¶
type TimeInterval ¶
type User ¶
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
ActiveWorkspace string `json:"activeWorkspace,omitempty"`
CustomFields any `json:"customFields,omitempty"`
DefaultWorkspace string `json:"defaultWorkspace,omitempty"`
Memberships any `json:"memberships,omitempty"`
ProfilePicture string `json:"profilePicture,omitempty"`
Settings any `json:"settings,omitempty"`
Status string `json:"status,omitempty"`
}
type Workspace ¶
type Workspace struct {
ID string `json:"id"`
Name string `json:"name"`
CakeOrganizationID string `json:"cakeOrganizationId,omitempty"`
CostRate *Rate `json:"costRate,omitempty"`
Currencies any `json:"currencies,omitempty"`
FeatureSubscriptionType string `json:"featureSubscriptionType,omitempty"`
Features []string `json:"features,omitempty"`
HourlyRate *Rate `json:"hourlyRate,omitempty"`
ImageURL string `json:"imageUrl,omitempty"`
Memberships []ProjectMembership `json:"memberships,omitempty"`
Subdomain any `json:"subdomain,omitempty"`
WorkspaceSettings any `json:"workspaceSettings,omitempty"`
}