# Timer.Press.Zone API / SDK preview This client targets a **private deployment with optional real account sessions**. The owner-requested password-free developer entry remains enabled in private mode; it must not be exposed to the Internet. Browser sessions use HttpOnly cookies and CSRF, native sessions use bearer credentials, and no OAuth provider is silently configured. ## Contract `docs/api/openapi.json` (also `/api-docs/openapi.json`) documents the implemented API operations and paths. The generator checks that the registry matches the actual Go router, expanding catalog resources and named actions. Current route/model counts are recorded in the generated SDK manifest and verified during packaging. Import, audit and some financial result payloads deliberately remain additive/partially opaque; route coverage is not a claim that every response field or every R1 contract is complete. No endpoints are invented for public shares or Webhook delivery. Scheduled reports use the private application inbox. Account verification/recovery supports explicit opt-in Brevo REST or verified-TLS SMTP; no real mail delivery is claimed until an administrator configures and verifies it. The TypeScript models and operation map are generated from this contract. Account secrets and source-token operations are **refused by durable preparation** so credentials do not enter persisted request queues. Use the separate ephemeral account/source flows. Toggl 2.0 inventory is not an import; the legacy Track adapter is a different provider. ## TypeScript / JavaScript Build from the repository root: ```sh npm ci --ignore-scripts python3 scripts/export_openapi.py python3 scripts/export_sdk.py node node_modules/typescript/bin/tsc -p sdk/typescript/tsconfig.json node --test sdk/typescript/client.test.mjs ``` Read-only use (Node with fetch/Web Crypto or a modern browser): ```js import { TimerClient } from './typescript/dist/index.js'; const client = new TimerClient('http://192.168.122.109:18080'); const me = await client.execute(client.prepare('getV1Me', {})); const workspaces = await client.execute(client.prepare('getV1Workspaces', {})); console.log(me.display_name, workspaces.items); ``` For a mutation, **persist the prepared envelope before sending it**. The SDK does not claim that an in-memory object is durable storage. This example describes the order; `saveEncryptedEnvelope` and `deleteEnvelopeAfterCanonicalSave` represent your application's real protected storage: ```ts const prepared = client.prepare('postV1WorkspacesWidTimeEntries', { path: { wid: workspaceId }, body: { description: 'Reviewed manual work', project_id: projectId, start_at: '2026-09-23T08:00:00Z', stop_at: '2026-09-23T09:00:00Z', billable: true } }); await saveEncryptedEnvelope(prepared); const canonical = await client.execute(prepared); await saveCanonicalThenDeleteEnvelopeAtomically(canonical, prepared); ``` The storage functions above are explicitly application responsibilities, not exported SDK functions. The repository's Web and native clients demonstrate real durable storage implementations. Prepared requests are detached from subsequent UI changes and include the original method, URL, exact JSON string, version and idempotency key. After an uncertain response, retry the **same retained envelope**, not `prepare()` again. The API rejects changed bodies under an old key. Regular mutation replay lasts seven days; offline sync has its own documented retention rules. The client bounds attempts, time and response size; never follows a redirect; rejects a different origin or operation path; and does not automatically update versions on 409/412. `ApiError.request` retains the uncertain prepared request. Source credentials, authorization tokens and arbitrary headers are not silently forwarded. For binary exports, prepare the GET export operation and use `download()` rather than `execute()`. For `/sync/push`, inspect **each result's** `accepted`, `conflict` or `rejected` status: HTTP 200 alone is not a successful mutation acknowledgement. Persist canonical data and acknowledgement atomically before dropping local intent. ## Python and curl These bundled examples are read-only: ```sh python3 sdk/examples/list_workspaces.py --base-url http://192.168.122.109:18080 TIMER_BASE_URL=http://192.168.122.109:18080 sh sdk/examples/curl-read.sh ``` The address is this private VM's example, not a public service. A remote machine needs an actual private route. No network/router changes are performed by these examples. ## Limits No SDK package was published to npm/PyPI. Real account sessions and scoped personal tokens are implemented, but this SDK does not store their secrets. There is no webhook dispatcher or public report-sharing endpoint, and no promise of cross-browser/offline storage on every platform. Generated models provide compile-time assistance, not a substitute for server validation or a complete production permission model. Reference specifications: OpenAPI 3.1 (`https://spec.openapis.org/oas/v3.1.1.html`) and the pinned TypeScript 5.9 toolchain (`https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-9.html`). ## Account-aware transport The account Web UI obtains CSRF tokens from `/v1/auth/session` and attaches them only to same-origin writes. Prepared SDK envelopes never store account passwords, session tokens or verification links. For authenticated SDK writes, supply both `principalId` and `getSession(signal)` options documented in `typescript/src/index.ts`; do not copy credentials into the serialized envelope. Account verification/invitation acceptance requires a verified email. Existing developer data cannot be claimed simply by registering the same email. `getSession` returns `{principalId, csrfToken}` for a currently authenticated browser session, or `{principalId, bearerToken}` for a native session. The callback must read current session state and honor the abort signal. The fixed account UUID is persisted in the envelope; credentials are not. A different current UUID refuses the retained request rather than silently reassigning it. The no-hook mode is for the explicitly private developer flow/read examples, not a cross-account durable queue. ## Mapped Focus time snapshots (v0.10) The new local `imports/toggl2/analyze` and `imports/toggl2/preview` operations accept a source-inspection dataset, one selected source user and explicit existing target UUID/version mappings. They never accept an API key. Uploaded snapshots are labelled unverified; server read receipts are checked separately. Private source work cannot map to a public target, and changed destination membership/versions require a new preview. Applying the staged run uses the existing import endpoint with replay-safe keys. Unsupported/excluded source rows remain visible and unimported. This is not complete source catalog/account migration. ## v0.11 additions Standalone catalog transfers, reasoned manager time edits, estimates/budgets/Insights, private ICS snapshots and owner-managed metadata Webhooks now have explicit API routes. The outbound Webhook dispatcher is OFF unless a deployment operator sets WEBHOOK_DELIVERY_ENABLED=1. No configuration is changed by SDK calls, and no mail is sent. Treat returned signing keys as secrets and do not log the create/rotation result. The calendar endpoints never produce tracked time or fetch remote attachments. Team writes require the separate time:manage_team capability and a reason, and never bypass approval locks. ## Account mail readiness (v0.12) The server now supports the Brevo REST adapter as well as SMTP, but sending is explicitly opt-in via ACCOUNT_MAIL_ENABLED=1. A secret alone does not activate the queue. A Brevo activation cutoff and optional single-recipient filter keep an old backlog out of the first test. The account UI and GET /v1/account/mail distinguish provider acknowledgement from inbox delivery; no body, proof or API key is exposed. Use the operator guide in docs/36-brevo-account-mail.md. Live delivery cannot be called verified without the user's real key, authenticated sender/domain and reachable HTTPS account origin. ## Durable source scans (v0.13) The source/toggl2/scans API reads the declared scope into encrypted pages with versioned checkpoints. No raw key is persisted. Source creation/step calls are ephemeral and must not enter a durable SDK queue. A completed owned source_scan_id can feed the catalog and time previews instead of intermediate JSON files. Existing mapping, destination access and separate import confirmations still apply. See docs/37-durable-source-scans.md for technical bounds and fixture-versus-live evidence. ## v0.15: team source ownership The team-import API consumes completed server scans and explicit one-to-one source person to existing member mappings. It requires imports:team plus time:manage_team, retains the manager audit actor and employee work ownership, and supports versioned chunks, per-member reconciliation and protected rollback. It creates no accounts, grants or historical rates. Team child plans are inaccessible to personal import endpoints. ## Verify the actual downloadable SDK Run `python3 scripts/package_sdk.py` after API/SDK changes, then `python3 scripts/package_sdk.py --check` before release. The check compares current source hashes, generated browser modules, package contents and archive checksum; a stale Git-ignored download is not equivalent to current TypeScript source. No registry upload or deployment is performed.