A Java client for the TSANet Connect API, generated from the API's own OpenAPI specification and wrapped in nine typed facades — plus a console app, an attachment receiver and a web demo that show it working end to end. If you are building a custom integration on the JVM, this is the shortest path that is not hand-rolled HTTP.
Status
This is v2.0.0, released 2026-09-08 — the fourth release in a month, after v0.1.0 (2026-08-11), v0.2.0 (2026-08-27) and v1.0.0 (2026-08-28). The 1.0.0 major marked a runtime change (Spring Boot 3.3, past its supported life, to Spring Boot 4.1 and Jackson 3); this one marks a contract change on the same runtime. Every failure the API answers now arrives as one typed, value-free exception instead of Spring's; every login is validated against /v1/me and configured credentials renew in both modes; the V2 direct-delivery attachment client ships against a draft contract; and the pre-0.1.0 create signatures that silently made test cases, deprecated since 0.1.0, are gone. Of the two TSANet-built consumers, the Connect Gateway took 2.0.0 the next day, 2026-09-09 (tsanetgit/Connect_Gateway#37): its engine classifies operation errors on Spring's types from its own HTTP client, so the change it needed was to classify a failed login and to run that login outside the operation's wall-clock budget. The Fin / Intercom connector consumes the gateway engine and follows it on its next engine bump. The repository still describes itself as a demonstration.
Treat it as the reference implementation of how to talk to the Connect API correctly, and as a dependency you pin rather than float. If your service is still on Jackson 2, stay on 0.2.0 until you are not. If your code catches Spring's HttpClientErrorException or RestClientResponseException around library calls, read the 2.0.0 upgrade note before bumping: those catches compile unchanged and stop matching.
The value is not that it saves you HTTP calls — it is that the Connect API has a number of behaviours that are easy to get wrong on a first integration, and the client encodes the right answers. Request shapes are validated before they go out, the process-form field types are modelled rather than guessed at, both authentication modes are handled behind one session object, and webhook payloads and attachment configuration have types instead of being maps of strings.
API surface
Everything hangs off TsaNetApi and TsaNetApiSession. The facades divide the API the way the API actually divides, which makes them easy to hold in your head:
Login across both credential modes, with the token renewed before expiry in both when the credentials are configured; the company and user behind the session, fetched from /v1/me at login; logout. A 401 mid-session re-mints the token and retries once, and concurrent callers share one renewal.
Partner search by name and by semantic query, with the last results kept locally.
List, create and fetch cases; the partner's process-form template by company, department or document; a sync of everything the account can see.
The notes timeline per case or across all cases, and note creation with a priority.
The lifecycle actions: approve, reject, request and send information, close — plus the response history they produce.
Subscriptions and their delivery logs, and an inbound receiver that verifies the signature and refreshes the affected case.
Attachment configuration, forwarding local files to a case, and the HTTPS push arrangement.
Direct delivery: the file goes straight into the partner's store, streamed from disk one part at a time in whichever upload mode the grant names, and the outcome is the platform's recorded word, not the client's. Built against a draft grant/complete contract; the platform endpoint is not live yet.
Identity and company context for the authenticated account.
Alongside the facades sit typed DTOs for every payload — form templates and field types, case status, notes, responses, webhook deliveries, attachment configuration — and a validation layer that checks a request before it is sent rather than letting the API reject it. On an API where a validation failure may arrive as an HTTP 500 unless you opt into problem+json, catching the problem client-side is worth more than it sounds. The library opts in on every call and surfaces whatever the API answers as one typed exception carrying the API's own title and detail, never the URL or a token.
Construction
/v2/collaboration-requests/list, but every webhook call still goes to v1 — subscribe and list on /v1/webhooks, delete, secret rotation and delivery history on /v1/webhooks/{id} — while the generated v2 webhook client sits unused. The inbound receiver reads the v1 payload shape too: a flat eventType and request token, not a CloudEvent. Worth knowing before you plan the migration: only subscribe and list are deprecated (sunset 2027-01-01), and only those two have v2 equivalents. Delete, secret rotation and deliveries exist on v1 alone, unmarked, with nothing to move to — so this is not a split the library can close on its own. One more consequence of the sibling build: the generated client comes from the specification's beta branch, per the repository README, so it carries no fields for the caseDirections and partnerCompanyIds subscription filters the main-branch specification added in August 2026. Re-verified against the 2.0.0 source, its generated client, and the spec's own paths and tags. Where every shipped integration stands is tracked in Deprecation clocks.Modules
| Module | What it is |
|---|---|
connect-library | The client. This is the one you take as a dependency; everything else is demonstration and tooling around it. |
TSANet-integration-app | A console shell with a command per operation — create, approve, reject, information request and response, notes, close, attachments, webhooks — and an optional endpoint that receives webhooks into the local store, on port 8090 by default (tsanet.webhook.port). Useful as a worked example and as a way to exercise the API by hand. |
TSANet-integration-demo | Scripted two-party scenarios over the same facades, plus a scheduled poll for accounts that cannot receive webhooks. |
attachment-receiver | The storage half of receiving files: a streaming storage interface with AWS S3, Azure Files, Azure Blob Storage and Google Cloud Storage adapters, an encrypted per-member configuration store, and a go-live check that proves the storage and CRM credentials before anything is registered. The HTTPS endpoint that accepts the push is not built yet; it is being built in the Connect Gateway repository's attachments-only profile (tsanetgit/Connect_Gateway#35), not here. |
demo-ui | A branded web demo over the SDK facades — dashboard, partner search, the partner's form rendered live, every lifecycle action, webhook management — with its own runbook and hosting notes, listening on port 8090 — change one port before running it alongside the console app. It is not published as an artifact: building it needs access to the private repository the specification lives in, or a jar or image handed over. The create → approve → close round trip has been run end to end against a TSANet test environment. |
TSANET_DEMO_AUTH_PASSWORD is set. That is fine on a laptop and not fine on anything reachable from the internet.Getting it
This is the most important practical thing on the page, because the obvious approach does not work for most people.
connect-library is published to GitHub Packages as a public package, so you can depend on it without building anything. From 2.0.0 the attachment-receiver module (the storage SPI and its four cloud adapters, a plain library with no main class) is published alongside it at the same version. It does not depend on connect-library, and taking it means taking all four cloud SDKs at once — about 170 runtime artifacts, because the storage factory links every adapter; a per-provider split is a follow-up:
<!-- com.tsanet:connect-library — check the latest release for the current version -->
<dependency>
<groupId>com.tsanet</groupId>
<artifactId>connect-library</artifactId>
<version>2.0.0</version>
</dependency>
Four versions are published: 0.1.0, 0.2.0, 1.0.0 and 2.0.0. 1.0.0 and later require Jackson 3 (tools.jackson.core) and are built on the Spring Boot 4.1 line; a service still on Jackson 2 should take 0.2.0, which is the same facade surface with the older runtime. 2.0.0 changes what a failure looks like (ConnectApiException in place of Spring's exceptions) and removes the always-test create signatures; the upgrade note in the repository's consumption reference lists the sweep to do before bumping. Two upgrade traps worth knowing: Jackson 3's parse exceptions are unchecked, so a catch (IOException) around a mapper call compiles and silently stops catching malformed JSON; and Jackson 3 rejects a missing primitive by default where Jackson 2 zero-filled it. The library's own cache reader keeps the old behaviour; your mappers inherit the new one unless you say otherwise.
One wrinkle that catches everyone: GitHub Packages requires authentication for Maven even on public packages. You need a token with read:packages in your settings.xml against the github server, or the resolve fails in a way that looks like the artifact does not exist.
The client is generated from the Connect API's OpenAPI specification, and the build reads that specification from a sibling clone of a private repository. A clone of the SDK alone does not build. For TSANet engineers this is a one-line setup; for a member or vendor without access to that repository, building from source is simply not available.
There is a documented way around it if you need a hermetic build and have the spec by other means — vendor the specification into your own repository and point the codegen property at the vendored copy. That was the pattern the Fin / Intercom adapter used before it moved to the published engine artifact, and it also removes the cross-repository CI credential that the sibling-clone approach needs.
One runtime rule the apps enforce and the library does not: plain http to the API is refused at startup. The opt-outs are tsanet.api.allow-insecure-http=true for the console and scripted demo and tsanet.demo.allow-insecure-http=true for the web demo.
Authentication
The library models authentication as a mode rather than branching call sites, so the same facade calls work whichever credentials you were issued.
| Mode | Use |
|---|---|
| Client credentials | The OAuth 2.0 server-to-server path. Tokens are re-minted from a long-lived credential, so there is no static token to refresh — the arrangement described on the Zendesk and Salesforce pages. |
| Password | The legacy login that exchanges a username and password for a JWT lasting about an hour. The library tracks the lifetime the login states and, for configured credentials, logs in again before it runs out. |
An account registry sits alongside the session types, so a single process can hold sessions for more than one member account — which is what an integration serving several tenants needs, and what a single-account client makes painful.
Local state
The library ships storage services paralleling the facades — collaboration requests and their form templates, notes, responses, partner selections, user context, webhook subscriptions and inbound events, attachment configuration and forward results. Backed by SQLite, they give an integration somewhere to keep what it has already retrieved. Since 0.2.0 the store can also forget: an age-based sweep evicts closed and rejected cases older than a window you choose, never touches an open case, and leaves attachment forward results alone because nothing upstream can re-supply them. Nothing runs the sweep for you: it is a call, CacheRetention.sweep, that your own schedule makes.
This matters more than it first appears on an API where inbound events are thin. When a webhook tells you only that something changed on a token, the fetch is what establishes truth — and having a local record of what you last saw is how you work out what actually changed, and how you avoid re-notifying on every redelivery. Every connector on this hub ends up building some version of this; here it comes with the client.
Other languages
There is no Python, JavaScript or TypeScript client. A scoping note exists and is honest that the work has not begun. What it establishes is that the shape is well understood: each target needs a generated client from the same specification, a thin facade layer reproducing the same nine-facade surface so parity is auditable, and a local cache equivalent to the SQLite store.
No new code-generation infrastructure is required — the same generator already supports Python and TypeScript targets — so the cost is the facade layer and the tests, not the plumbing. Python is the stated first choice, driven by a real integration need rather than by demo convenience.
Fit
Build with an assistant
The SDK repository ships an agent skill — skills/connect-sdk-deploy — that carries this page's practical knowledge in a form an agentic coding assistant can apply while it works in your project: the artifact-versus-source decision above, the GitHub Packages token requirement, both credential modes with working configuration, the demo's build-run-host runbook, and a symptom-first troubleshooting file for the traps that recur. It is plain markdown: a SKILL.md that routes, three reference files it points into, and an evals folder that rides along when you copy it. Every skill TSANet ships, and how to load one into any assistant, is collected on Agentic usage with TSANet Connect.
Claude Code — copy the whole folder into your integration project's skills directory; the references must ride along with the SKILL.md that cites them:
git clone --depth 1 https://github.com/tsanetgit/Connect_SDK.git
cp -R Connect_SDK/skills/connect-sdk-deploy your-project/.claude/skills/
Other assistants — attach SKILL.md and the references/ files as project instructions; nothing in it is Claude-specific. The skill lives in the SDK repository and evolves with it, so refresh your copy when you move to a new SDK release.
read:packages requirement before it bites, rather than after a 401 that looks like a missing artifact.cannot find symbol ... WebhooksApi means one specific thing (the spec sibling is on the wrong branch), and the skill knows it; a paraphrase throws that away.