The four deprecated Surplus Lines multipart upload routes are removed, ahead of the 1 April 2027 sunset date they carried, because no integration was calling them. The multipart form of Reply to a Checklist Item Escalation is removed with them.
- Removed:
POST .../surplus-lines-filings/{filingId}/policy-documentandPOST .../surplus-lines-filings/{filingId}/checklist-item/attachments, on both the Carrier & MGA API (/v2/upstream) and the Agency API (/v2/downstream). A call now returns404. Onpolicy-documentthe404is about the method, not your filing:PUTon the same path is the replacement and has not changed. - Use instead: upload the file with Upload a Document (category Surplus Lines), then attach it by
fileDocumentIdwith Set the Policy Document or Attach Uploaded Files to a Checklist Item. Files on a Surplus Lines Filing and its Agency API version walk through both steps. - Escalation reply: Reply to a Checklist Item Escalation takes a JSON body only. Send files as
fileDocumentIds. Amultipart/form-datarequest now returns400, including one that carries only the message text.
NewChanged
Surplus Lines uploads go through the generic upload, files attach by id, and any document can be downloaded
Surplus Lines files no longer need their own multipart routes. Upload once, attach by id, download by id.
- Upload: the Upload a Document endpoint on both the Carrier & MGA API and the Agency API now accepts the category Surplus Lines (PDF, DOC, DOCX, JPEG or PNG, up to 10 MB). Every Surplus Lines upload creates a new document. The Carrier & MGA API returns the new document’s
_idand the Agency API returnsfileDocumentId; either one is what you pass asfileDocumentIdto the attach routes. On the Carrier & MGA API this isPOST /v2/upstream/file-documentsonly:v1keeps its seven categories and PDF only. The endpoint now also accepts anidempotency-keyheader, so a retried upload does not create a second document. - Attach by id: Set the Policy Document (
PUT .../surplus-lines-filings/{filingId}/policy-document), Attach Uploaded Files to a Checklist Item (POST .../surplus-lines-filings/{filingId}/checklist-item/file-documents) and the escalation reply, which now takesfileDocumentIdsin a JSON body. A file that is already on a filing, or is not yours, returns404. The one exception is Set the Policy Document called again with the file that is already the filing’s policy document: it returns the filing unchanged instead of404, so a retry is safe. - Download by id: Get a Download URL for a Document,
GET /v2/upstream/file-documents/{fileDocumentId}/download-urlfor carriers andGET /v2/downstream/documents/{fileDocumentId}/download-urlfor agencies, returns short-lived links that expire after one hour. The carrier route is limited to 120 requests per 60 seconds per organization. For a file on a filing, pass thefileDocumentIdthe filing response now shows on every checklist attachment, the policy document and every completion artifact. An attachment’s_ididentifies the attachment record, not the document. - Document lists: the document lists on both APIs no longer return Surplus Lines files on checklist items Turris keeps internal, and the Carrier & MGA list,
v1andv2, no longer returns deleted Surplus Lines files. Rows are removed; response shapes are unchanged. - Deprecated: the four multipart routes, Upload the Policy Document and Attach Files to a Checklist Item on both personas. They keep working until 1 April 2027 and return
DeprecationandSunsetheaders. - Also deprecated: the multipart
filespart of the escalation reply, in favour offileDocumentIds. This is deprecated in the reference docs only: the route sends no deprecation headers and has no removal date. - Reference: the
429these routes can return is now declared on both download-url routes, every Agency Surplus Lines filing route, and the Agency document list, upload and market document list. The Idempotency guide now lists every Surplus Lines write that accepts anidempotency-key, including filing creation and diligent effort requests, which were missing from it.
v2 filing response gains one field on each file, fileDocumentId; the v1 filing response is unchanged. Nothing else you already call has changed shape, and the four deprecated multipart routes behave exactly as before until the sunset date.POST /v2/downstream/contacts with upstreamDownstreamAssociationId and a downstreamEntityId that is not the entity holding that market now returns 400, with errorType validation_error, and writes nothing. Until now that request succeeded.A contact on a market belongs to the entity that holds the market. Leave downstreamEntityId out when you name a market and that entity is used. Before, an omitted downstreamEntityId meant your own organization even when the market belonged to one of your branches, so the contact was recorded against the wrong entity. Requests without upstreamDownstreamAssociationId are unchanged.Changed
The Postman collection is now imported from this site, and the public Postman workspace is retired
If you forked the
Turris Public API collection from our public Postman workspace, that fork will stop receiving updates. Import the collection from this site instead.The collection now lives at https://docs.turrisfi.com/postman/turris-public-api.postman_collection.json. In Postman, choose Import, paste that link, and set base-url, client-id and client-secret on the collection’s Variables tab. Tools & Integrations has the full steps, including how to import the complete OpenAPI document when you need an endpoint the curated collection leaves out.The collection and the reference pages on this site are generated from the same OpenAPI documents in the same release, so the file at that URL always matches the pages you are reading. The hand-maintained workspace could lag the reference, and did. To pick up a change, import the same URL again and choose Replace in the import dialog, then re-enter any variable value that came back empty on the collection’s Variables tab.The public Postman workspace at postman.com/turris is retired as of 29 September 2026. It stays visible for now so existing links resolve, but nothing in it will be updated again.Breaking
Add a Contact: a market-scoped contact now joins that market's contacts, and a second primary contact is refused with 409
POST /v2/downstream/contacts with isPrimary: true for a market that already has a primary contact now returns 409, with errorType conflict, and writes nothing. Until now that request succeeded.A market has one primary contact, and this endpoint has no way to hand the role over. Change the primary contact in Turris first, then add the contact without isPrimary. On a market with no primary contact, isPrimary: true is still accepted and makes the new contact the market’s primary. The check reads only the market you named in upstreamDownstreamAssociationId, so it tells you nothing you cannot already see on that market’s contacts.A contact added with upstreamDownstreamAssociationId now appears on that market’s contacts. Before, the endpoint recorded the market on the contact and left the market’s own contact list alone, so the contact never showed on the market in Turris. The contact and its place on the market’s list are now written together or not at all, whether the request creates a new contact or attaches your scope to one that already exists.A contact you added to a market before this change may hold that market’s scope without appearing on the market. Sending the same request again returns
409, because you already hold the scope, so it cannot repair the contact. Contact Turris support to have it added to the market’s contacts.You can now change the
externalId and producerCode on a downstream entity association after it has been created.The endpoint is PATCH /v2/upstream/downstream-entity-associations/{downstreamEntityAssociationId}. It is available on v2 only; v1 is frozen and does not receive new endpoints. The response is the full association, in the same shape the Get Association endpoint returns. It accepts an idempotency-key header like the other association writes.Both fields are optional and at least one must be present. A non-empty string sets the field, null clears it, and a key you leave out is not touched. An empty string is rejected with validation_error, because it is neither a value nor a clear. Nothing else on the association is writable through this endpoint.Uniqueness is enforced per upstream entity, the same rule the Add and Invite endpoints apply at creation. A value another of your associations already carries returns 409 with errorType duplicate_external_id_error or duplicate_producer_code_error. Sending an association’s own current value back is not a conflict, so replaying a full record from your side is safe.Until now these two values could only be set when the association was created. An integration that keyed an association to the wrong record in its own system had to ask us for a data fix. It can now move the external ID itself.Breaking
Appointment compliance gains a third status for just-in-time states, and those states now report compliant
If you branch on
appointmentStatus.statusName, a comparison against "Not Appointed" is about to stop matching a whole class of states, and those same states will start reporting complianceStatus: COMPLIANT.Appointment regulation is not two-valued. Some states need no appointment filed at all. Most need one before an agency or agent is authorized to sell. And some are just-in-time: the appointment is filed once business is actually bound, not in advance. We were reporting the third group as though it were the second, which meant an agency with nothing yet bound appeared to be in breach of an obligation that had not started.The new value is "Appointment on Binding", and it is not an error state. A state carrying it reports complianceStatus: COMPLIANT. We do not receive binding dates, so we do not assert a breach we cannot evidence. licenseStatus is unaffected.Nothing in the schema changed. statusName was always published as an unconstrained string on both v1 and v2, and it still is, so no generated client will see a type change and no field narrows or disappears. That is precisely why this is tagged Breaking rather than left to be discovered: the contract is unchanged while the set of values flowing through it is not.States move onto it gradually, not in one release. Turris reclassifies a state and product at a time, so you will see this value appear against states that previously reported "Not Appointed", over days rather than in a single burst on deploy day.If you maintain a compliance dashboard fed by these webhooks, the change to make is to treat
"Appointment on Binding" as passing wherever you treat "Not Required" as passing today. Both mean no appointment is owed right now; they differ only in whether one will ever be owed.residencyStatus was published as a bare string. It now declares the values it can take, and there are two: resident and non-resident.If you wanted to branch on residency, the reference gave you nothing to branch on. Four schemas carried the field with no allowed values at all: LicenseResponse and SurplusLinesLicenseResponse, on both v1 and v2. All four now declare the enum.The Agency API’s DownstreamLicenseResponse loses a value. It was the one schema that already published an enum, and it listed a third member, non-resident-no-home-state. No licence endpoint has ever returned it. Both paths that write residency onto a licence derive it from a single NIPR flag and produce only the two values above, and a check across every licence in the platform found zero instances of the third. It is tagged Breaking because the published schema is narrower than it was, so a client generating types from the spec will see that member disappear. No payload changes, and no value that was ever sent stops being sent.non-resident-no-home-state is a real category, but it belongs to licence applications rather than issued licences: it is one of the renewal transaction types NIPR uses for a holder with no resident state. It has never been part of a licence response and is not becoming one.Residency is roughly nine non-resident licences to every resident one, so
non-resident is the common case rather than the exception. The examples in the reference were updated to reflect that."Resident" and "Non-Resident" in title case. Deliveries have always carried lowercase. If you copied a sample into a string comparison, that comparison has been failing silently on every delivery. The samples and the field tables now show what is actually sent.Breaking
The spec now declares authentication and the persona of every tag, and the compliance-config line of business is constrained
If you generate a client or a Postman collection from our OpenAPI documents, regenerate it. Three declarations that were missing are now there, and your generated code will change.Authentication is now declared, not just described. Both schemes were listed under
securitySchemes and bound to nothing, so a generated client sent no credential at all and every call it made came back 401. The documents now carry a root security requirement naming both, as alternatives: a Bearer token or an x-restricted-access-token header authenticates a request, matching what the API has always done. The two endpoints that take no credential, the token endpoint and the heartbeat, declare that explicitly rather than inheriting the requirement.Every tag now says which surface it belongs to. The documents shipped an empty tag list while the tags on individual operations were already persona-prefixed, so the only machine-readable signal for “is this the carrier API or the agency API” was the second segment of the URL. Each tag is now declared with the organization category it requires. On v1 there are no prefixes and there never will be, because it has no persona segment and is carrier-only by construction, which the tag descriptions now say outright.lineOfBusiness on a compliance configuration is constrained. Creating one has always accepted exactly four values, but the response documented an unconstrained string and gave Personal Lines as an example, which is not one of the four and never was. Request and response now declare the same four. No payload changes: every configuration we have ever stored already carried one of these values. It is tagged Breaking only because a client generating types from the spec will see the field narrow from string to a union.Improved
The wrong-surface 403 is now documented on every endpoint, and the response names the credential you actually hold
Calling the wrong half of the API has always returned a 403. Now every endpoint that can return it says so, and the response tells you which kind of credential it expected.Your credential belongs to exactly one kind of organization. A carrier or MGA credential reaches the Carrier & MGA API, an agency credential reaches the Agency API, and neither reaches the other. Using one on the other surface returns Previously the only way to answer “which surface is this credential for” was a second call to Re-minting a credential never fixes a wrong-surface 403: the credential is fine, the base URL is not. Which API is mine? has the routing rules.
403 with errorType invalid_organization_category.That has always been the behaviour. What was missing was any mention of it in the reference: the response appeared on none of our published operations, so pressing Send on a prefilled example with a working token could produce a failure the page you were reading could not explain. Nothing about enforcement has changed, and no request that worked before behaves differently now.Every persona-scoped operation now declares the response. 51 on v1 and 86 on v2. The /auth/* and /heartbeat operations do not, because they serve either audience and genuinely cannot return it.The response now carries details. A wrong-surface 403 names the category it expected and the one your credential carries:GET /v2/auth/test-oauth, which returns orgCategory. That still works and is still the way to check a credential before you use it. Now the failure answers the question by itself.A 403 has more than one cause. Missing entitlement returns
product_feature_subscription_required, a carrier at its associated-agency ceiling returns invite_limit_reached, and an IP outside a Restricted Access Token’s allowlist returns forbidden. Branch on errorType rather than on the status code. Error Handling lists them.The licence reference documented an Five fields that were already being returned are now documented as well, in the same pass. None of them is new on the wire:
expirationDate that no response has ever carried. It is gone.The field was published on the licence responses and on the agency line of authority, with an example value on the latter. No licence schema has ever declared it and no code path has ever written it, so anyone reading the property was reading undefined. No payload changes, and nothing that ever had a value stops having one. It is tagged Breaking because the published schema is narrower than it was, so a client generating types from the spec will see the property disappear.nextRenewalDate is the expiry date you want. It is the date a licence must be renewed by, it is what every expiry calculation in the platform reads, and it is what the licence expiring and expired webhooks fire against. If you mapped expirationDate, repoint that mapping at nextRenewalDate.nextRenewalDate is optional and genuinely absent on roughly 9% of producer licences, for the reason described under Agent License Expiring. Handle the absent case rather than assuming every licence has an expiry.lastRenewalDateon the producer and agency licence responses (/agents/*/licensesand/downstream-entity-associations/*/licenses). The surplus-lines responses do not carry it, and the Agency API’s own licence response already documented it.issueDate,statusReason,statusReasonDateandcontinuingEducationon each line of authority in those same responses and in the surplus-lines responses, alongside thecode,nameandstatusalready listed.continuingEducationcarriesstatus,renewalDateandcreditsNeeded, and is populated for producer licences while usually absent on agency-held ones.
/v2/downstream/*) is a separate, narrower object. The removal of expirationDate applies to it as well; none of the five newly documented fields is added to it.Apart from the removed property, nothing about the responses themselves changed. This is the reference catching up with what these endpoints have been sending, in both v1 and v2.A webhook is no longer one URL bound to one event type. It now carries a list of them, and the
type field on the
webhook resource has been replaced by types.If you read GET /v1/webhooks or GET /v2/upstream/webhooks, or you generate a client from our OpenAPI spec, this
changes your integration. Four things moved:Previous notes here told you to watch the
type enum’s values when we added event types. This release changes
the field’s shape, which is a different kind of change, so it is called out rather than folded into a list of new
values.Why it is worth the break, and it is the new header that matters most. Each webhook mints its own signing secret.
A single URL registered for all 18 event types therefore held 18 different secrets, and nothing in the request said
which one signed it. That left two options, both bad: try every secret until one verifies, or read webhookType out
of the body to pick the key — using unverified content to decide how to verify it. We tell you signature verification
is a required step, not a hardening measure, so that was a contract working against you.One webhook per URL means one secret per URL, and the question does not arise. X-Turris-Webhook-Id fixes it for
anyone holding more than one secret for any other reason.Existing webhooks were migrated for you. Each became a one-element types array. No URL was merged and no
signing secret was rotated, so nothing needs reconfiguring on your side beyond reading the new field.You can now change a webhook’s event types without losing its secret. Adding an event type used to mean
registering a second webhook. Doing it by deleting and recreating would have minted a new secret and forced a
redeploy, so an update path landed alongside this change.Subscriptions are still per event type: selecting Agency Created does not deliver Agency Updated. What changed is
that both now go on one webhook instead of two.The List Agents endpoint (
GET /v1/agents and GET /v2/upstream/agents) now accepts an email query param.
It resolves an agent by primary email or any labeled email (business, personal, or other), case-insensitively.
Emails that belong to an agent outside your organization return an empty array.Two new webhook events tell you when a regulatory action already on file is revised.Agent Regulatory Action Updated (Three things to know before you build against them.The whole action is delivered, not just the diff. So you can apply one of these as a wholesale overwrite of your copy and ignore
AGENT_REGULATORY_ACTION_UPDATED) and Agency Regulatory Action Updated (DOWNSTREAM_ENTITY_REGULATORY_ACTION_UPDATED) fire when the daily NIPR pass changes any of the nine amendable fields on an action you have already been told about.The two Added events announce an action the first time we see it. Until now a revision to that action reached you only if you polled for it, and revisions are where the severity often lands: the case that prompted these events had a disposition escalate to a cease-and-desist three weeks after the original filing.changedFields entirely. Key on actionId + npn + stateCode, the same key the Added events use, and the amendment updates the record you already hold.A daily pass that changes nothing delivers nothing. We re-read every action already on file and compare it
against what we hold. When nothing changed, nothing is written and nothing is sent, so the volume of this event
tracks real regulator activity rather than our polling schedule. Expect it to be rare and bursty.Subscriptions are per event type, so take the Updated event alongside its Added sibling. On its own it tells you
about revisions to actions you were never told about in the first place.Four new webhook events tell you when a licence is about to lapse, and when it has.Agent License Expiring (Four things to know before you build against them. The first two surprise people.Size your receiver for the spike, not for the median. These are not debounced and not immediate: a daily sweep at
05:00 America/New_York, one delivery per licence per boundary. A typical sweep is single digits and the 95th
percentile is under 50, but the busiest measured sweep for a single carrier carries about 2,800 deliveries of
any one event, and about 8,000 across all four if you subscribe to every one. Renewal dates cluster on the last
day of the month, so the spikes land on the 1st or 2nd. Return
AGENT_LICENSE_EXPIRING) and Agency License Expiring (DOWNSTREAM_ENTITY_LICENSE_EXPIRING) fire 60 days before a licence reaches its renewal date. Agent License Expired (AGENT_LICENSE_EXPIRED) and Agency License Expired (DOWNSTREAM_ENTITY_LICENSE_EXPIRED) fire the day after it passes.Until now a lapsing licence only reached you if it happened to flip a product/state compliance status. It often does not: a licence expiring in a state you carry no requirement for, or one covered by another licence the producer holds, was silent.2xx quickly and queue.Roughly 9% of producer licences and 11% of agency licences never fire either event, because they carry no
renewal date. If you reconcile deliveries against your own licence count, that is the gap. There is also no catch-up
pass: if a sweep does not run, that day’s licences are not announced later.Licences marked do-not-renew do fire, which is the opposite of how Turris treats its own renewal reminders. For
a carrier, a producer deliberately letting a licence lapse is the most actionable case there is.If you generate a client from our OpenAPI spec, the type enum on GET /v1/webhooks and GET /v2/webhooks now
includes the four new values. Subscriptions are per event type, so subscribe to each event you want.Two new webhook events tell you when NIPR reports a regulatory action you have not seen before.Agent Regulatory Action Added (Three things to know before you build against them:An amended action does not fire. If an existing action’s disposition changes, or it is removed, there is no
event. Poll the GET endpoint if you need to track those.Deduplicate on
AGENT_REGULATORY_ACTION_ADDED) and Agency Regulatory Action Added (DOWNSTREAM_ENTITY_REGULATORY_ACTION_ADDED) fire when the daily NIPR PDB-alerts pass finds an adverse or regulatory action against a producer or an agency on your book.Regulatory actions were already readable on Get Agent Regulatory Actions and its agency equivalent, but nothing told you when a new one appeared, so acting on one meant polling every entity you distribute through. An action also does not always move a compliance status, so the compliance-status events could not stand in for this.actionId + npn + stateCode. And note lengthOfOrderInDays: 0 is ambiguous in NIPR’s own
data — it means both “not specified” and “indefinite” — so do not render it as “0 days”.Both events are sent immediately rather than debounced, and volume is bounded by what NIPR newly reports. If you
generate a client from our OpenAPI spec, the type enum on GET /v1/webhooks and GET /v2/webhooks now includes
the two new values. Subscriptions are per event type, so subscribe to both if you want producer and agency actions.A new webhook event tells you when an agency record changes. Match on
DOWNSTREAM_ENTITY_UPDATED fires when an agency’s name, identity or contact details change, and carries which fields changed along with what they were before.Agency names change on their own. A name filed with NIPR propagates into Turris on the daily sync, and an agency invited under a working name is renamed to its legal name the moment it submits its NPN. Neither previously produced any signal, so a system keying reports on agency name drifted out of sync and the same agency began appearing under two names.The payload carries the full current record plus changedFields and previousValues, so you can either overwrite your copy wholesale or act only on the fields you care about:downstreamEntityAssociationId, the same identifier carried by Agency Created and Producer Agreement Executed. Do not match on name — that is the failure this event exists to prevent.Two behaviours to design for. A change to legal identity is delivered once per relationship you hold with that agency, each with its own downstreamEntityAssociationId. And an edit spanning both the relationship and the agency record produces a delivery per write, so for a multi-relationship agency that is one plus one per relationship. Treat deliveries as idempotent and let the last write win.See Agency Updated for the full field reference and the list of changes that do not fire it.Your subscription and product features are now verified on every API request, not only when a credential is created. This applies to the carrier surface on both
v1 and v2, and to the agency surface.Nothing changes for an organization whose subscription and features are in place. Your client id and secret are unchanged, no request shape changes, and no endpoint moves.What changes is the failure mode when an entitlement is missing. Previously an API credential kept working until somebody deleted it, even if the organization had stopped being a Turris customer or had the Public API feature switched off. Those requests now return 403 with an errorType of product_feature_subscription_required.The errorMessage names which of three things is missing, so it is actionable rather than generic:- no active Turris subscription
- the Public API feature is not enabled, in which case no endpoint is available
- a specific feature is not enabled, in which case only that area is unavailable and the rest of your integration keeps working
Agencies can now call the Turris API directly. 38 endpoints under Every list is paginated and returns
/v2/downstream/* cover licensed entities, producers, contacts, licences and licensing overviews, appointments, licence requests, markets, corporate registrations, compliance configurations, documents and surplus-lines filings.This is what the persona segment introduced with v2 was for. A carrier credential reaches /v1/* and /v2/upstream/*; an agency credential reaches /v2/downstream/*. Neither reaches the other, and there is no v1 agency surface.items, total, page, limit and totalPages. A list covers your entity plus every branch beneath it, so rows carry the entity they belong to; narrow to one with downstreamEntityId.Reads see your subtree, never above or beside it. An id outside it returns 404 rather than 403, because a 403 would confirm the record exists. See Getting Started for Agencies.Each area needs the same product feature as the matching screen in the agency application. API access opens the door; it does not grant an area your organization has not bought. A missing feature returns 403 with product_feature_subscription_required and a message naming the feature to ask for.Agencies can mint either credential type from Settings, API: an OAuth API Client, recommended wherever you have a backend, or a Restricted Access Token for callers that cannot perform the token exchange.v2 is live. Every v1 endpoint is available on v2 at a new path. Request bodies, query parameters, response shapes and status codes are unchanged, so migrating is a change to your base URL rather than to your code.upstream segment names the kind of organization your credentials belong to. v1 has no such segment because it serves carriers and MGAs only, which is precisely why v2 exists: an agency-facing API needs somewhere in the path to say so. Agency, enterprise carrier and enterprise agency personas will appear under v2 without disturbing anything you build today.Four endpoints are shared across personas and take no persona segment: POST /v2/auth/jwt, GET /v2/auth/test-oauth, GET /v2/auth/test-restricted-access-token and GET /v2/heartbeat. Your existing client ID and secret work unchanged, and a token minted on either version is accepted by both, so you can migrate one endpoint at a time.Three deprecated endpoints are not in v2. dangerously-invite, dangerously-add and licenses/dangerously-surplus-lines remain on v1 until 1 January 2027 and now return Deprecation, Sunset and Link response headers. The Link header names the replacement endpoint with rel="successor-version", so a client can discover it without reading this page. Each has a drop-in replacement with an identical request and response; switching is a rename you can make on v1 today.v1 is not deprecated. It is stable, fully supported and has no sunset date. Migrate when you want the endpoints that will only be added to v2.The API Reference now has a version switcher at the top. See the migration guide for the endpoint-by-endpoint mapping.One note if you generate a client from our OpenAPI spec: the documents at /v1/docs-json and /v2/docs-json are now filtered to their own version. Previously any version segment returned one combined document, so a generated client could include endpoints that did not exist on the version it targeted.Two new webhook events tell you when an agency relationship appears in or leaves your account.Agency Created (
DOWNSTREAM_ENTITY_CREATED) fires as soon as an agency is added, invited, bulk-uploaded, or synced in from HubSpot. It exists for the parent/child case: when you onboard an agency underneath one that already executed a producer agreement, no PRODUCER_AGREEMENT_EXECUTED fires for the child, so previously nothing told you the child existed.The payload carries the agency’s position in the hierarchy (path, level, parentAssociationId, ultimateParentAssociationId) plus ultimateParentAgreementExecuted, so you can tell whether the new agency is covered by a parent’s agreement without calling the API. A false there is corrected by the Producer Agreement Executed event you already receive for that parent.Agency Deleted (DOWNSTREAM_ENTITY_DELETED) is its counterpart, covering both an outright removal and an archival, discriminated by deletionType. Subscriptions are per event type, so subscribe to both or you will keep agencies in your records that no longer exist in Turris.The same hierarchy fields were added to List Associations and Get Association, so you can reconcile agencies that predate these events.Two notes if you generate a client from our OpenAPI spec: the type enum on GET /v1/webhooks now includes the two new values, and Agency Created is sent immediately rather than debounced, so a large CSV import produces one delivery per agency.The four agency creation endpoints now run entirely within the public API instead of delegating to an internal service. Request bodies and success responses are byte for byte unchanged, and so is every HTTP status code on success. Two things about failures changed.Affected endpoints:
POST /v1/downstream-entity-associations/invitePOST /v1/downstream-entity-associations/addPOST /v1/downstream-entity-associations/dangerously-invitePOST /v1/downstream-entity-associations/dangerously-add
errorType is now specific rather than generic. These endpoints return the precise code for the failure, where they previously returned a broad classification such as validation_error or conflict. HTTP status codes are unchanged, so only code that branches on errorType is affected. Codes you may now see include invalid_email, producer_agreement_required, duplicate_producer_code_error, duplicate_external_id_error, organization_already_exists, and organization_slug_already_used.Action required: If you branch on errorType for these endpoints, match the specific codes. Treat an unrecognised value as a generic failure of its HTTP status rather than falling through to a success path.2. A new 403 response is possible. If your account is configured with a limit on associated agencies and you have reached it, these endpoints return 403 with errorType: "invite_limit_reached". Handle the code if you create agencies programmatically; contact your Turris representative if you need a limit raised.503 service_unavailable no longer occurs on these four endpoints. It signalled that the internal service was unreachable, and there is no internal call left to fail.The full set of errorType values is now published as an enum on the error response schema, so it appears in the API reference and in generated clients. Authentication failures from our identity provider are still forwarded verbatim, so a 401 can carry a code outside that list.GET /v1/market-contacts returns the market contacts your organization publishes to its agency relationships, one entry per contact, with the relationships they are assigned to grouped under associations. Each association carries the built-in roles held there (underwriter, territory manager, credentialing, account manager, claims, general) plus any carrier-defined custom roles as { id, name }.This is the mirror image of GET /v1/contacts, which returns the agency’s contacts as seen by you.An association entry with no downstreamEntityAssociationId is carrier-wide and applies to every one of your agency relationships. Filter with downstreamEntityAssociationId to scope to one relationship, which returns its assigned contacts plus your carrier-wide contacts. An id outside your organization returns an empty array rather than an error.See the List Market Contacts reference page for full request and response details.Two new endpoints let you retrieve licenses by National Producer Number (NPN), without first resolving an internal agent or association id:
GET /v1/agents/licenses?npn=returns the licenses for the agent with the given NPN under your account.GET /v1/downstream-entity-associations/licenses?npn=returns the licenses for the agency (downstream entity) with the given NPN under your account.
npn is required (digits only); both endpoints also accept the optional stateCode and status filters. Each returns the same license shape as the existing per-id license endpoints, and returns 404 if no matching agent or agency is associated with your account. The existing /{id}/licenses endpoints are unchanged.See the Get All Agent Licenses and Get All Downstream Entity Licenses reference pages for full request and response details.All four license endpoints (agent and agency, both the
/{id}/licenses and the new ?npn= variants) now return only the fields listed in the license response schema. Previously-undeclared internal fields such as _id, createdAt, and renewalDecision are no longer included in the payload.Action required: None. If your integration relied on any of these undocumented fields, switch to the documented response fields.Two new endpoints let you read the agency contacts across your downstream entity associations:
GET /v1/contactsreturns every contact you can see, one entry per contact. The agencies you can see them at are grouped underassociations, each carrying the business roles the contact holds there and whether they are a primary contact. Both agency-wide default contacts and market-specific contacts are included. Filter bydownstreamEntityAssociationId(a single agency relationship),businessRole, or both.GET /v1/contacts/{contactId}returns a single contact by id, including every agency relationship you can see them at and the roles they hold at each. It returns404if the contact is not visible to your organization.
The
PRODUCER_AGREEMENT_EXECUTED webhook payload now includes four new optional fields tying the executed agreement back to its ContractContainer instance and template:Action required: None. Existing fields are unchanged. New fields are strictly additive and optional. Legacy in-flight envelopes signed before the ContractContainer migration may omit them, so treat them as optional in your handler.See the Producer Agreement Executed webhook documentation for the updated payload reference.
NewImproved
Contract Container Templates endpoint + invite endpoint accepts contractContainerTemplateId
New endpoint:
GET /v1/contract-container-templates returns active ContractContainer templates for your organization. Use the returned _id as contractContainerTemplateId when inviting a downstream entity.Invite endpoint improvement: POST /v1/downstream-entity-associations/invite now accepts contractContainerTemplateId directly. The legacy producerAgreementId field is deprecated but still accepted for backward compatibility, internal resolution still maps it to the matching ContractContainer template.Action required: None. Existing integrations continue to work. For new integrations, prefer contractContainerTemplateId obtained from the new endpoint.A new webhook event —
PRODUCER_AGREEMENT_EXECUTED — is now available. It fires when a downstream entity and your upstream entity have fully executed a producer agreement (both parties have signed).Payload fields:This webhook is sent immediately — no debouncing is applied.See the Producer Agreement Executed webhook documentation for payload examples and field reference.
The
ENTITY_COMPLIANCE_DATA_SYNCHRONIZED webhook payload no longer includes an entityId field. It has been replaced by npn (National Producer Number).What changed:Why NPN instead of an entity ID? A single NPN can be linked to multiple downstream entity associations and downstream entities across your network. The NPN is the natural, universal identifier for any NIPR-driven operation and lets you look up all relevant records for that producer. Use
niprDataSubscriptionId to retrieve the specific subscription record via the Compliance Data Subscriptions API.Note: The previous entityId field was always sent as the string "undefined" due to a bug — it did not contain a valid entity ID. If you were reading this field, update your handler to use npn instead.Action required: Update your webhook handler to read npn instead of entityId. See the Compliance Data Synchronized documentation for the updated payload reference.Look up National Producer Numbers directly through the API. Send up to 20 NPNs in a single request and instantly find out whether each NPN belongs to an agency (firm), an individual agent, or is not recognized by NIPR — along with entity details like name, resident states, FEIN, and date of birth.See the Validate NPNs endpoint for full details.
When the same
Add Downstream Entity or Invite Downstream Entity request is submitted multiple times simultaneously, the API now returns a clear 409 Conflict response instead of an unhandled 500 Internal Server Error.The response includes the error code transient_transaction_error with the message: “This request has already been processed. A duplicate concurrent request was detected.”No action required — this is a non-breaking improvement. If you are handling 409 status codes already, you will now receive this new error code for concurrent duplicate requests.The
AGENT_COMPLIANCE_STATUS_CHANGE and DOWNSTREAM_ENTITY_COMPLIANCE_STATUS_CHANGE webhook payloads now use a hierarchical structure grouped by entity → product → state, replacing the previous flat structure.What changed:New fields added:
legalName— Legal name of the downstream entitybranchName— Branch name of the associationproductName— Human-readable product name (alongsideproductId)statusMessages— Optional array of detailed status explanations onlicenseStatusandappointmentStatus
Compliance status change webhooks (
AGENT_COMPLIANCE_STATUS_CHANGE and DOWNSTREAM_ENTITY_COMPLIANCE_STATUS_CHANGE) are now debounced with a 1-minute sliding window. When a change is detected, Turris waits for 1 minute of inactivity before sending the webhook. If additional changes occur within that window, the timer resets. A maximum cap of 30 minutes ensures delivery even during continuous activity.This means there is a minimum ~1 minute delay between a data change and webhook delivery. The debouncing consolidates rapid sequences of changes (e.g., PDB alert processing, bulk operations) into a single webhook with a batched payload.ENTITY_COMPLIANCE_DATA_SYNCHRONIZED webhooks are not affected — they are still sent immediately.See the Webhooks guide for details.Webhook requests are no longer encrypted with AES-256-GCM. Instead, payloads are sent as plaintext JSON (
Content-Type: application/json) and signed with HMAC-SHA256 using your client secret.Each request now includes two new headers:Action required: Update your webhook endpoint to verify the
X-Turris-Signature header instead of decrypting the request body. See the Webhooks guide for code examples.If you request the licenses for an
agentId that does not exist, the API now returns an error message that explicitly
states that an agent could not be found.For each agent or agency (downstream entity association) we now auto-populate the
niprDataSubscription field,
allowing you to immediately see the compliance synchronization status.Call the new endpoint with a specific email to receive a sign-in URL that you can share with the contact owning that email. They can use it to sign into the Agency application to either complete the onboarding flow with your organization or manage your market relationship by updating contacts, agents, or documents.The response returns a
404 error if the agency or a contact with the email at the agency could not be found.You can now find your associated downstream entities by email domain or a specific email address.
The
GET /downstream-entity-associations endpoint now accepts an emailDomain query param. Clients can find all of
their associated agencies by the email domain of the associated agency contacts.We have moved all requests into the parent domain, simplifying the authentication collection structure.
All protected API endpoints now accept either authentication method:
- If only a Bearer token is provided, OAuth authentication is used
- If only a Restricted Access Token is provided, token authentication is used
- If both headers are present, OAuth is attempted first; if it fails, the API falls back to Restricted Access Token authentication
- Both methods produce the same authenticated context (
upstreamEntityIdandupstreamEntityMemberId)
This new endpoint allows clients to retrieve all active surplus lines licenses for a downstream entity association,
filtering by FEIN and state code.
Previously, the
GET agency/agent endpoints returned all records, including those that are soft-deleted. Since our
users are only interested in active records, we now exclude any records with isDeleted: true.Agents and Downstream Entity Associations can now be found and filtered either by their NPN or by their license number
and state code. Both filter types are mutually exclusive. If a license number is provided, a valid state code must be
provided as well.
The new regulatory actions endpoints allow you to retrieve any regulatory actions for your downstream entity
associations and their agents.
Retrieve all appointments for your downstream entity associations and their agents. You can filter by
stateCode and
status.Previously, the licenses endpoint returned all active licenses. We have added the
status filter as a query param.
Now, this endpoint returns active, inactive, or all licenses. Future iterations may add additional status values.After an agent or downstream entity has been added to the tenancy, you can call this endpoint to retrieve the latest compliance data associated with the entity’s NPN. The subscription includes daily updates of all compliance data.This endpoint runs asynchronously — it returns a success response immediately but the sync process may be queued and/or processing for minutes. Two solutions are available:
- Webhook: Subscribe to a webhook and get notified when the compliance data sync completes
- Polling: Poll the compliance data subscription status endpoint until
entityInfo.status: success
Retrieve all active licenses for an agent or a downstream entity association. Optionally, provide
stateCode to
filter by state.- URL params were changed to reflect the new
downstreamEntityAssociationIdnomenclature -stateCodequery string is now optional; in absence of this param, the endpoint returns the compliance status for all states
Users can now upload any document and associate it with any downstream entity via the downstream entity association
ID.
The URL path and query strings for the E&O/Cyber compliance endpoint have changed.
The
GET and POST endpoints for the Downstream Entity Associations resource now return the association data alongside the downstream entity detail. The _id field is the downstreamEntityAssociationId and should be thought of as the main identifier for any association with a downstream entity.What changed:- URL path and query strings have changed
- The response body now includes the association data that describes the relationship between the Downstream and the Upstream Entity
The
GET and POST endpoints for the Agent resource now return the agent detail as well as the top-level association resource. Information describing the relationship between two resources is returned as sibling fields of the agent property.What changed:- URL path and query strings have changed
- The response body now includes the association data that describes the relationship between the Agent and the Upstream Entity
If you have created a template producer agreement in the web app with custom fields (e.g. an agency-specific
commission number), you can now assign those values when sending a request to the Invite Downstream Entity endpoint.
The Invite Downstream Entity endpoint now accepts an optional
downstreamEntityId field. If provided, the endpoint
creates a new downstream entity association with the existing downstream entity. If not provided, it creates both a
new downstream entity and a new association.This endpoint allows you to invite downstream entities to your organization. It creates a new downstream entity and
association. The downstream entity receives an email invitation to complete the onboarding process.
Subscribe to webhooks to receive real-time notifications when events occur in your organization. Available events include:
- Downstream entity association created/updated
- Agent created/updated
- Compliance data sync completed
- Document uploaded
- Contract signed
Upload, retrieve, and manage documents associated with downstream entities. Documents can be categorized by type
(e.g., E&O policy, cyber policy, W-9) and are automatically linked to the appropriate downstream entity association.
Agents can now be added to downstream entity associations with assignment status tracking. The API supports:
- Adding agents to agencies
- Updating agent assignment status (pending, approved, rejected)
- Tracking assignment history
- Managing agent authority by product and state
Create and manage producer agreement templates with custom fields. Templates can be assigned to downstream entities
during the invitation process, with custom field values populated at invitation time.
New endpoints provide aggregated compliance data for dashboard views:
- Overall compliance status by downstream entity
- License expiration summaries
- E&O/Cyber policy status
- Regulatory action counts
- Appointment status summaries
New batch endpoints allow you to perform operations on multiple entities at once:
- Batch invite downstream entities
- Batch add agents
- Batch update authority
- Batch sync compliance data