A Java client for the TSANet Connect API, generated from the API's own OpenAPI specification and wrapped in ten 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 v3.0.0, released 2026-10-20 — the fifth release, after v0.1.0 (2026-08-11), v0.2.0 (2026-08-27), v1.0.0 (2026-08-28) and v2.0.0 (2026-09-08). 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) and 2.0.0 a contract change on that runtime: every failure the API answers arrives as one typed, value-free exception instead of Spring's. 3.0.0 keeps the runtime and is a major for five reasons: the V2 direct-delivery client is rebuilt from the Connect API's own specification and its public types change; /v1/ failures now arrive as problem details with their documented status; API calls run on the JDK's HttpClient; interfaces and records grew (a tenth facade, note types, a receiver allowlist); and a V2 upload link missing its expiry or size is refused rather than quietly tolerated. Of the two TSANet-built consumers, the Connect Gateway moved its pin to 3.0.0 and released its own v3.0.0 the same day, and the Fin / Intercom connector, which consumes the gateway engine, moved with it. The release notes' list of known consumers predates both moves and still shows the gateway on 2.0.0. 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. Only the current release is supported, so Jackson 3 on the Spring Boot 4.1 line is a prerequisite, not a choice. Before bumping to 3.0.0, read its upgrade entry in the repository's consumption reference: code that branches on kind() == LEGACY, on status() or on message text now sees different values, and a wrong password is a problem detail like any other failure.
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. A token call that cannot reach the identity provider fails as a connectivity error that names neither the URL nor the tenant.
Partner search by name, with the last results kept locally. The semantic search method is still there but marked do not use: the endpoint behind it answers 500 on Beta and Development (see Working with the API).
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. From 3.0.0, listNoteTypes() returns the note types the server offers, from GET /v1/note-types, and createNote takes one of those codes. There is no default: leave it out and the note has no type; send one the server does not offer and the answer is a 400 invalid-note-type. Notes read back carry their type, its display name, the authoring company and the direction.
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.
V1 attachments: configuration, forwarding local files to a case, and the HTTPS push arrangement. Unchanged by 3.0.0.
Direct delivery: one method per grant call in the specification, plus a send that grants, uploads in whichever mode the grant names, and completes. Generated from operations the specification marks alpha; see Direct delivery (V2).
New in 3.0.0, for the receiving side of V2: get, register and test the company's own S3 or Azure Blob storage, the place partners' files will land. Alpha, like the operations it calls.
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. From 3.0.0 the library opts in on every call, /v1/ included (2.0.0 did not, and saw 500s where the API documents a 4xx), and surfaces whatever the API answers as one typed exception carrying the API's own title and detail, never the URL or a token.
Attachments, V2
The V1 attachment path relays a file through TSANet, which then pushes it to the receiving member. V2 skips the relay: the sender asks the platform for a grant, the grant comes back with short-lived upload links into the receiver's own storage, the sender uploads straight to it, and a complete call has the platform check the stored upload and record the outcome. The file never passes through the Connect API. The details are on File sharing.
| Side | What 3.0.0 gives you |
|---|---|
| Sending | attachmentsV2().send(...) grants, uploads and completes in one call, in whichever mode the grant names — single, s3Multipart or azureBlock. Upload links are requested just before use, in batches, and refreshed shortly before they expire; each part is retried a bounded number of times and honours Retry-After; links never appear in logs or messages. Only the company that submitted the case can create a grant for it. The individual grant, link, complete and abandon calls are there too, one method per specification operation. |
| Receiver allowlist | An optional list of company IDs a sender is willing to deliver to — ApplicationUserAccount.withAllowedReceiverCompanyIds, or allowed-receiver-company-ids in the console's account configuration. With a list set, a case whose receiving company is not on it is refused with attachment/receiver-not-allowed before any grant is requested; the platform's own allowlist refusal is reported under the same code. Empty means unrestricted. |
| Receiving | attachmentStorage() gets, registers and tests the company's own storage: an S3 bucket, reached through a role TSANet assumes, or an Azure Blob container. No secret is sent to TSANet or stored by the library. |
x-stability-level: alpha in the Connect API specification, so its shape may still change without a deprecation clock. The operations were in Beta's specification first; since 2026-10-26 Production's specification carries them too, still marked alpha (spec-read 2026-10-28). The release was verified live on Beta only, with an S3 receiver: a single-mode send and a three-part S3 multipart send, both completed. Azure Blob and the Development environment were not live-verified. A few of the platform's answers are still settling — a missing storage configuration and one the endpoint does not show are not yet told apart — and the library marks those spots as provisional in its source.Construction
/v2/collaboration-requests/list, but every webhook call still goes to v1 — subscribe and list on /v1/webhooks, delete and delivery history on /v1/webhooks/{id} — while the generated v2 webhook client sits unused; the webhook code is unchanged from 2.0.0 to 3.0.0. The facade has no secret-rotation method at all. 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 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 a pinned commit on the specification's beta branch, and the commit 3.0.0 is built from (2026-10-20) carries the caseDirections and partnerCompanyIds subscription filters. They are live on Beta. When 3.0.0 shipped, the specification Production served lacked them. Its openapi.yaml at that commit is byte-identical to what both Beta and Production serve as of 2026-10-28, so Production's specification has had them since 2026-10-26 (spec-read; not probed). So the generated request type carries both fields, but createSubscription(callbackUrl, eventTypes) does not pass them. Re-verified against the 3.0.0 source, its generated client, and the beta specification it was built from. Where every shipped integration stands is tracked in Deprecation clocks.manifest and encrypted, so an SDK-based integration cannot send or read an encrypted case. Tracked in Connect_SDK#137; the contract is on the end-to-end encryption topic.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. It serves the V1 relay path, where TSANet pushes the file to you; a V2 receiver registers its storage through attachmentStorage() instead. Its source is unchanged since 2.0.0. The HTTPS endpoint that accepts the V1 push is still not built; it is planned for the Connect Gateway's attachments-only profile, 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, a Deliver directly (V2) action on a case, and a Settings page that shows and tests the company's V2 receiver storage — 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. Registering storage from its Settings page is off by default — the page is read-only unless TSANET_DEMO_RECEIVER_STORAGE_EDITABLE=true — and should stay off on a shared instance.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. Since 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>3.0.0</version>
</dependency>
Only the current release is supported — v3.0.0 today. Earlier versions are still in the registry, but they are obsolete and not offered, and there are no external deployments to keep them for. It requires Jackson 3 (tools.jackson.core) and the Spring Boot 4.1 line; a service still on Jackson 2 or Spring Boot 3 has to move before it can take the library. 3.0.0 changes what a failure looks like again, this time on the wire: /v1/ failures carry their documented status and problem type, and because calls now run on the JDK's HttpClient, a default Authenticator for an authenticating proxy is no longer honoured, a redirect is not followed, and an interrupted call stops with the thread left interrupted. Implementers and decorators of the facade interfaces must add the new methods, and a record pattern over a grown record stops compiling. The 3.0.0 entry in the repository's consumption reference lists every removed type, method and error code. Two Jackson 3 traps from the 1.0.0 move still apply: 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, and from 3.0.0 the sibling has to be on a specification branch that carries the Attachment Grants and Attachment Storage Config operations — beta does; the repository's CI also builds against the specification's develop branch daily, to see changes coming. 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 ten-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. At 3.0.0 it is part-way there: its consumption reference carries the 3.0.0 upgrade guide, but SKILL.md still says it was checked at v2.0.0 and the reference still names 2.0.0 as current in one place.
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.