Hub / Connectors / Connect SDK

Connect SDK

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.

Version v2.0.0 · latest release Released 2026-09-08 Runtime Java 21 · Spring Boot 4.1 · Jackson 3 Repo tsanetgit/Connect_SDK

Status

What it is, and what it is not yet

Read the version number before you plan around it

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

Nine facades over one session

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:

AuthFacade

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.

PartnersFacade

Partner search by name and by semantic query, with the last results kept locally.

CollaborationRequestsFacade

List, create and fetch cases; the partner's process-form template by company, department or document; a sync of everything the account can see.

CaseNotesFacade

The notes timeline per case or across all cases, and note creation with a priority.

CaseResponsesFacade

The lifecycle actions: approve, reject, request and send information, close — plus the response history they produce.

WebhooksFacade

Subscriptions and their delivery logs, and an inbound receiver that verifies the signature and refreshes the affected case.

AttachmentsFacade

Attachment configuration, forwarding local files to a case, and the HTTPS push arrangement.

AttachmentsV2Facade

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.

UserFacade

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

Generated from the spec, wrapped by hand

Your integration JVM, Java 21 connect-library TsaNetApi · TsaNetApiSession nine typed facades DTOs + request validation auth modes · account registry hand-written — the stable surface Generated client openapi-generator output regenerated at build time not consumed directly Connect API /v1 + /v2 openapi.yaml build-time input, private repo codegen The spec is a build input, not a runtime dependency — which is why consuming the published artifact avoids it.
The generated layer is regenerated from the API's own specification at build time, so the wire types cannot drift from the contract. The facades are hand-written on top, which is what gives the library a surface that stays stable while the generated code underneath changes with the spec.
One consequence of wrapping the generated client by hand: the library currently spans both API versions. List reads call /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

Five modules, one of which you actually depend on

ModuleWhat it is
connect-libraryThe client. This is the one you take as a dependency; everything else is demonstration and tooling around it.
TSANet-integration-appA 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-demoScripted two-party scenarios over the same facades, plus a scheduled poll for accounts that cannot receive webhooks.
attachment-receiverThe 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-uiA 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.
If you host the demo UI, set its auth password. The demo runs with no authentication gate unless TSANET_DEMO_AUTH_PASSWORD is set. That is fine on a laptop and not fine on anything reachable from the internet.

Getting it

Take the artifact, not the source tree

This is the most important practical thing on the page, because the obvious approach does not work for most people.

The path that works: consume the published library

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 path that does not work from outside TSANet: build from source

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.

The practical consequence. If you are an external integrator, plan on consuming the published artifact and treating the source as reference reading. If you need to build it yourself — to patch it, or to run it in an air-gapped pipeline — that is a conversation with TSANet about spec access, not something you can resolve on your own.

Authentication

Both credential modes, behind one session

The library models authentication as a mode rather than branching call sites, so the same facade calls work whichever credentials you were issued.

ModeUse
Client credentialsThe 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.
PasswordThe 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

A storage layer for what you fetched

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

Java today; Python and TypeScript scoped, not started

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.

If you are building on a non-JVM stack today, you are writing against the REST API directly. The submit-outcome taxonomy and the polling-cursor guidance elsewhere on this hub are the parts worth copying, because they are the parts that are easy to get subtly wrong.

Fit

When to reach for it

Good fit

  • A custom JVM integration against a platform with no TSANet connector
  • A middleware or translation service between the Connect API and an in-house system
  • Learning the API's real shape before committing to a design — the console app exercises every operation
  • Anything sending attachments, where forwarding a file to a case is a library call

Look elsewhere

  • Your platform already has a connector — use Salesforce, Zendesk or Dynamics rather than rebuilding
  • You are not on the JVM — there is no port yet
  • You need a frozen, long-supported API surface; the facades have held across four releases and 2.0.0 removed the deprecated signatures, but the library is weeks old
  • You cannot pin dependencies and need something that floats safely

Build with an assistant

The deployment skill, and how to load it

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.

Loading it

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.

Typical first steps

Boundaries worth keeping. Member credentials, client secrets and tenant IDs never go into an assistant session — the skill is written against placeholders and never needs a real value. And it is distilled guidance, not the source of truth: it instructs the assistant to read the repository's own documentation from your checkout, and where they seem to disagree, the repository is newer.