Hub / Existing Solutions / Connect SDK

Connect SDK

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.

Version v3.0.0 · latest release Released 2026-10-20 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 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

Ten 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. A token call that cannot reach the identity provider fails as a connectivity error that names neither the URL nor the tenant.

PartnersFacade

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

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. 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.

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

V1 attachments: configuration, forwarding local files to a case, and the HTTPS push arrangement. Unchanged by 3.0.0.

AttachmentsV2Facade

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

AttachmentStorageFacade

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.

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. 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

Direct Delivery, and Why It Is Alpha

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.

SideWhat 3.0.0 gives you
SendingattachmentsV2().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 allowlistAn 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.
ReceivingattachmentStorage() 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.
Alpha means alpha. Every grant and storage-configuration operation is marked 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

Generated from the Spec, Wrapped by Hand

Your integration JVM, Java 21 connect-library TsaNetApi · TsaNetApiSession ten 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 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.
The library does not yet carry the two end-to-end encryption fields, 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

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. 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-uiA 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.
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. 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

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. 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 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, 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.

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 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.

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 — and, once you accept alpha, direct delivery too

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 library has had three majors in two months, and 3.0.0 removed V2 attachment methods and types, added methods to two interfaces and grew three records
  • 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. 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.

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.