clockify

package
v1.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CentsFromReportNumber

func CentsFromReportNumber(v float64) int64

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

func ParseRateAmount(s string) (int64, error)

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) Error

func (e *APIError) Error() string

func (*APIError) Sanitized

func (e *APIError) Sanitized() string

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 Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(apiKey, baseURL string, timeout time.Duration, maxRetries int) *Client

func (*Client) Close

func (c *Client) Close()

Close releases idle connections held by the client.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, path string) error

func (*Client) DeleteReports

func (c *Client) DeleteReports(ctx context.Context, path string) error

DeleteReports performs a DELETE against the reports host.

func (*Client) DeleteWithQuery

func (c *Client) DeleteWithQuery(ctx context.Context, path string, query map[string]string) error

func (*Client) Get

func (c *Client) Get(ctx context.Context, path string, query map[string]string, out any) error

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) GetValues

func (c *Client) GetValues(ctx context.Context, path string, query url.Values, out any) error

func (*Client) Patch

func (c *Client) Patch(ctx context.Context, path string, body any, out any) error

func (*Client) Post

func (c *Client) Post(ctx context.Context, path string, body any, out any) error

func (*Client) PostMultipart

func (c *Client) PostMultipart(ctx context.Context, path string, form url.Values, out any) error

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 (the encoding the Clockify expense endpoints use for fields like changeFields). No file upload support — the expense endpoints accept absent file fields with an empty fileId, so callers don't currently need it.

func (*Client) PostReports

func (c *Client) PostReports(ctx context.Context, path string, body any, out any) error

PostReports performs a POST against the reports host.

func (*Client) PostReportsRaw

func (c *Client) PostReportsRaw(ctx context.Context, path string, body any) (*RawResponse, error)

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 (c *Client) PostWithQuery(ctx context.Context, path string, query map[string]string, body any, out any) error

func (*Client) Put

func (c *Client) Put(ctx context.Context, path string, body any, out any) error

func (*Client) PutMultipart

func (c *Client) PutMultipart(ctx context.Context, path string, form url.Values, out any) error

PutMultipart performs a PUT with a multipart/form-data body. Same encoding rules as PostMultipart.

func (*Client) PutReports

func (c *Client) PutReports(ctx context.Context, path string, body any, out any) error

PutReports performs a PUT against the reports host.

func (*Client) PutWithQuery

func (c *Client) PutWithQuery(ctx context.Context, path string, query map[string]string, body any, out any) error

func (*Client) ReportsBaseURL

func (c *Client) ReportsBaseURL() string

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) 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) SetUserAgent

func (c *Client) SetUserAgent(ua string)

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 PaginationCapError

type PaginationCapError struct {
	Path        string
	RowsScanned int
	MaxRows     int
	Page        int
	PageSize    int
}

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"`
	Color          string              `json:"color,omitempty"`
	Archived       bool                `json:"archived"`
	Billable       bool                `json:"billable,omitempty"`
	BudgetEstimate any                 `json:"budgetEstimate,omitempty"`
	CostRate       *Rate               `json:"costRate,omitempty"`
	Duration       string              `json:"duration,omitempty"`
	Estimate       any                 `json:"estimate,omitempty"`
	EstimateReset  any                 `json:"estimateReset,omitempty"`
	HourlyRate     *Rate               `json:"hourlyRate,omitempty"`
	Memberships    []ProjectMembership `json:"memberships,omitempty"`
	Note           string              `json:"note,omitempty"`
	Public         bool                `json:"public,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

func (r *Rate) DecimalString() string

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

func (r *Rate) Display() string

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 (r *Rate) MarshalJSON() ([]byte, error)

func (*Rate) UnmarshalJSON

func (r *Rate) UnmarshalJSON(b []byte) error

type RawResponse

type RawResponse struct {
	Header http.Header
	Body   []byte
}

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 Tag

type Tag struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Archived    bool   `json:"archived"`
	WorkspaceID string `json:"workspaceId,omitempty"`
}

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

func (e TimeEntry) DurationSeconds() int64

func (TimeEntry) EndTime

func (e TimeEntry) EndTime() (time.Time, error)

func (TimeEntry) IsRunning

func (e TimeEntry) IsRunning() bool

func (TimeEntry) StartTime

func (e TimeEntry) StartTime() (time.Time, error)

type TimeInterval

type TimeInterval struct {
	Start    string `json:"start"`
	End      string `json:"end,omitempty"`
	Duration string `json:"duration,omitempty"`
}

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"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL