All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
New resource modules:
client.alerts— persistent saved queries with optional webhook delivery (list,create,delete)client.ownership— ownership-chain trace with circular-ownership detection (trace)
New methods on existing resources:
companies.timeline(uid, since, until, change_type)— chronological event timeline for a companycompanies.timeline_summary(uid, ...)— AI-generated narrative summary of a company's timelinecompanies.similar(uid, limit)— find companies scored on industry, canton, capital, legal form, auditor tiercompanies.ubo(uid)— ultimate beneficial owner resolutioncompanies.media(uid, sentiment, since, limit)— media/news items with optional sentiment filteringcompanies.media_analyze(uid)— trigger LLM sentiment analysis on unanalyzed media itemscompanies.export_csv(...)— canonical name for company data export (replacesexport_excel, kept as deprecated alias)persons.network(id)— person-centric network view (companies + co-directors + stats)analytics.flows(period, since, group_by)— market flow analytics (registrations/dissolutions over time)analytics.migrations(since)— canton migration analytics (legal seat changes between cantons)analytics.benchmark(uid, dimensions)— benchmark a company against industry peers with percentile ranksscreening.batch(uids)— batch sanctions screening (up to 100 UIDs per call)ai.risk_score_batch(uids)— batch AI risk scoring (up to 50 UIDs per call)
Pagination:
persons.board_members(uid, page, page_size)— pagination support (max 500, default 100)
Enrichment provenance fields (populated by new backend pipelines; all optional and backwards-compatible):
Company.direct_parent_lei,ultimate_parent_lei,ultimate_parent_name,gleif_parent_enriched_at— GLEIF-sourced parent linkageCompany.industry_source,industry_confidence,industry_classified_at— LLM-assisted industry classification provenanceClassification.industry_source,industry_confidence— same provenance on the classification endpointFingerprint.registration_date— Swiss register entry dateBoardMember.role_source,role_confidence,role_inferred_at— role extraction provenancePersonRoleDetail.role_source,role_confidence,role_inferred_at— same on person detail rolesPersonEntry.role_source,role_confidence,role_inferred_at— same on company full-response person entriesNetworkCompany.role_source,role_confidence,role_inferred_at— same on person network companies
Data coverage disclosure:
UboResponse.data_coverage_note— human-readable explanation when the chain can't be fully resolved (e.g. before weekly GLEIF run)FlowsResponse.data_coverage_note— surfaces asymmetries (e.g. historical dissolution under-counting)
New typed models:
TimelineEvent,TimelineResponse,TimelineSummaryResponseSimilarCompaniesResponse,SimilarCompanyResultUboResponse,UboPerson,ChainLink,OwnershipResponse,OwnershipEntity,OwnershipLink,PersonCompanyRole,KeyPerson,CircularFlagAlertMediaResponse,MediaItem,MediaAnalysisResponseFlowsResponse,FlowDataPoint,MigrationResponse,MigrationFlowBenchmarkResponse,BenchmarkDimensionBatchScreeningResponse,BatchScreeningResultByUid,BatchScreeningHitSummaryBatchRiskScoreResponse,RiskScoreResultPersonNetworkResponse,NetworkPerson,NetworkCompany,NetworkStats,CoDirector,CoDirectorCompanyHierarchyEntity— replaces the untypedAnyonHierarchyResponseWatchlistCompanyEntry— enriched entry onWatchlistCompaniesResponse.companies
Documentation:
examples/— 7 runnable scripts covering quickstart, due diligence, watchlist monitoring, company networks, bulk export, historical timelines, and UBO resolutionnotebooks/— 5 Jupyter notebooks producing publication-ready charts (Swiss market analytics, company deep dive, compliance screening, market flows, similar companies)- Full
README.mdrewrite with examples/notebooks section, 17 generated figures, and v3.1 feature overview
HierarchyResponse.parentandsubsidiaries/siblingsnow use typedHierarchyEntityinstead ofAnyWatchlistCompaniesResponsenow includes a typedcompanies: list[WatchlistCompanyEntry]field alongside the existinguidsCompany.industrybehaviour: field itself unchanged, but newindustry_sourcecompanion field lets consumers distinguish"zefix"/"keyword_match"from"llm"classificationsUboPerson.controlling_entity_uid,ChainLink.from_uid/to_uid,OwnershipLink.source_uid/target_uid— documented that non-Swiss parents (resolved via GLEIF) appear asLEI:<20-char-lei>synthetic identifiers
companies.export_excel()— kept as a deprecated alias forcompanies.export_csv(). The endpoint returns CSV (not Excel); the new name reflects reality. Will be removed in a future major release.
- Breaking: Base URL changed from
https://api.vynco.chtohttps://vynco.ch/api— matches the Rust reference SDK - Breaking: Response header names changed —
X-Rate-Limit-Limit→X-RateLimit-Limit(matching Rust SDK) - Breaking:
DashboardResponse.datamodel changed —DataCompletenessfields updated tototal_companies,enriched_companies,companies_with_industry,companies_with_geo,total_persons,total_changes,total_sogc_publications - Breaking:
PipelineStatusmodel changed —name→id,records_processed→items_processed,duration_secondsremoved - Breaking:
AuditorTenureStatsmodel rewritten — fields nowtotal_tracked,current_auditors,tenures_over_10_years,tenures_over_7_years,avg_tenure_years,longest_tenure - Breaking:
exports.download()andgraph.export()now returnExportFiledataclass (withbytes,content_type,filename,meta) instead ofResponse[bytes] - Company model expanded with 25+ new fields:
currency,purpose,founding_date,registration_date,deletion_date,legal_seat,municipality,data_source,enrichment_level,address_street,address_house_number,address_zip_code,address_city,address_canton,website,sub_industry,employee_count,auditor_name,latitude,longitude,geo_precision,noga_code,sanctions_hit,last_screened_at,is_finma_regulated,ehraid,chid,cantonal_excerpt_url,old_names,translations companies.list()now acceptsstatus,legal_form,capital_min,capital_max,auditor_category,sort_by,sort_descfilter parameters
ResponseMeta.rate_limit_remaining— remaining requests in rate limit window (X-RateLimit-Remaining)ResponseMeta.rate_limit_reset— Unix timestamp when rate limit resets (X-RateLimit-Reset)ExportFiledataclass — returned by file download endpoints (exports.download(),graph.export(),companies.export_excel())companies.get_full(uid)— full company details with persons, changes, relationshipscompanies.classification(uid)— industry classificationcompanies.structure(uid)— corporate structure (head offices, branches, M&A)companies.acquisitions(uid)— M&A relationshipscompanies.notes(uid),create_note(),update_note(),delete_note()— company notes CRUDcompanies.tags(uid),create_tag(),delete_tag(),all_tags()— company tags CRUDcompanies.export_excel()— Excel/CSV export of companiespersons.search(q, page, page_size)— search persons by namepersons.get(id)— get person detail with all rolesteams.join(token)— join a team via invitation tokendossiers.generate(uid)— generate a dossier for a company- New typed models:
CompanyFullResponse,PersonEntry,ChangeEntry,RelationshipEntry,Classification,CorporateStructure,RelatedCompanyEntry,Acquisition,Note,Tag,TagSummary,PersonSearchResult,PersonDetail,PersonRoleDetail,JoinTeamResponse,LongestTenure - Retry logic now respects
X-RateLimit-Resetheader in addition toRetry-After
- Breaking: Base URL changed from
https://api.vynco.ch/api/v1tohttps://api.vynco.ch— all resource paths now include/v1/prefix, matching the Rust SDK - Breaking:
companies.list()parameters changed — removedstatus,auditor_category,sort_by,sort_desc; addedchanged_since,page,page_size - Breaking:
companies.count()no longer accepts filter parameters - Breaking:
companies.statistics()now returns typedCompanyStatisticsmodel - Breaking:
companies.compare()now returns typedCompareResponsemodel - Breaking:
companies.news()andcompanies.reports()now return typed models and no longer acceptlimitparameter - Breaking:
companies.relationships()andcompanies.hierarchy()now return typed models - Breaking:
personsresource stripped to single methodboard_members(uid)returninglist[BoardMember] - Breaking:
dossiersresource rewritten —create(uid, level),list(),get(id_or_uid),delete(id)replace old API - Breaking:
changes.list()parameters changed — now acceptschange_type,since,until,company_search,page,page_size - Breaking:
changes.statistics()now returns typedChangeStatisticsmodel - Breaking:
analyticsresource rewritten —cluster()andanomalies()now takealgorithmparameter;velocity()removed;candidates()added - Breaking:
teams.create()simplified tonameonly;list_members()renamed tomembers();invite_member()now returnsInvitation - Breaking:
api_keys.create()parameters changed toname,environment,scopes; returns typedApiKeyCreated - Breaking:
billing.create_checkout()tierparameter now required; returns typedSessionUrl - Breaking:
credits.balance()returns typedCreditBalance;credits.usage()returnsCreditUsage;credits.history()returnsCreditHistory - Company model updated —
canton,status,legal_formnowOptional; addedshare_capital,industry; removedaddress,purpose,created_at - ApiKey model updated — added
environment,scopes,status; removedis_test_key - Team model simplified — removed
overage_rate,created_at,updated_at - TeamMember model updated — removed
is_active,invited_at,joined_at; addedlast_login_at - CompanyChange model updated — removed
sogc_id,is_reviewed,is_flagged; addeddescription,source;company_namenowOptional
healthresource —check()for API health statusauditorsresource —history(uid),tenures(min_years, canton, page, page_size)dashboardresource —get()for admin dashboard datascreeningresource —screen(name, uid, sources)for sanctions screeningwatchlistsresource —list,create,delete,companies,add_companies,remove_company,eventswebhooksresource —list,create,update,delete,test,deliveriesexportsresource —create,get,downloadfor bulk data exportsairesource —dossier,search,risk_scorefor AI-powered analysisgraphresource —get,export,analyzefor network graphscompanies.events()— company event feed (CloudEvents format)companies.fingerprint()— data fingerprint for a companycompanies.nearby()— find companies near a geographic pointanalytics.candidates()— audit candidate companiesteams.billing_summary()— now returns typedBillingSummary- New typed models:
HealthResponse,CompanyStatistics,EventListResponse,CompanyEvent,CompareResponse,NewsItem,CompanyReport,Relationship,HierarchyResponse,Fingerprint,NearbyCompany,AuditorHistoryResponse,AuditorTenure,AuditorTenureStats,DashboardResponse,DataCompleteness,PipelineStatus,ScreeningResponse,ScreeningHit,Watchlist,WatchlistSummary,WatchlistCompaniesResponse,AddCompaniesResponse,WebhookSubscription,CreateWebhookResponse,TestDeliveryResponse,WebhookDelivery,ExportJob,ExportDownload,DossierResponse,AiSearchResponse,RiskScoreResponse,RiskFactor,ApiKeyCreated,CreditBalance,CreditUsage,CreditHistory,SessionUrl,Invitation,BillingSummary,MemberUsage,ChangeStatistics,BoardMember,CantonDistribution,AuditorMarketShare,ClusterResponse,AnomalyResponse,RfmSegmentsResponse,CohortResponse,AuditCandidate,DossierSummary,GraphResponse,GraphNode,GraphLink,NetworkAnalysisResponse,NetworkCluster
watchesresource — replaced bywatchlistsnotificationsresource — removed (use webhooks/watchlists instead)newsresource (standalone) — company news is now viacompanies.news()companies.search()(POST) — usecompanies.list(query=...)insteadcompanies.batch_get()— removedpersons.list(),persons.get(),persons.roles(),persons.connections(),persons.network_stats()— persons simplified toboard_members()onlydossiers.statistics(),dossiers.generate()— replaced bydossiers.create()changes.by_sogc(),changes.batch(),changes.review()— removedanalytics.velocity()— removed- Typed models:
Person,CompanyWatch,ChangeNotification,CompanyRelationship
- Base URL updated from
/v1to/api/v1to match production API companies.search()renamed tocompanies.list()— the GET list endpoint with filteringcompanies.compare()andcompanies.batch_get()now use POST with request bodycompanies.relationships(),hierarchy(),news()now returndict(unstructured)dossiers.generate()parameter renamed fromleveltotype(values:standard,comprehensive); endpoint moved toPOST /dossiers/{uid}/generatechanges.by_company()endpoint updated from/changes/company/{uid}to/changes/{uid}analyticsendpoints now returndictinstead of typed models (API returns variable structures)credits.balance()andcredits.usage()now returndictinstead of typed modelscredits.history()now returnslist[CreditLedgerEntry]instead ofdictbillingendpoints updated — paths changed to/billing/checkout-sessionand/billing/portal-session; returndictapi_keys.create()simplified — returnsdict(full key returned once);is_testparameter renamed tois_test_keyteams.create()accepts optionalowner_emailandowner_name- Company model updated: removed
legal_seat,capital_nominal,capital_currency,auditor_name,registration_date,deletion_date,data_source,last_modified; addedaddress,auditor_category,created_at,updated_at - Person model simplified to
id,name,roles,companies(from first_name/last_name + PersonRole objects) - Dossier model updated to
company_uid,company_name,summary,risk_score,generated_at - CompanyChange model: removed
source_date, addedcompany_name,sogc_id,is_reviewed,is_flagged - ApiKeyInfo / ApiKeyCreated replaced by single ApiKey model with
id,name,prefix,is_test_key,created_at,last_used_at - Team model: added
updated_at - PaginatedResponse: removed
total_pages,has_previous_page,has_next_page - Development status classifier updated from Alpha to Production/Stable
companies.search()— new POST full-text search endpoint (FTS5 with Swiss diacritics)companies.reports()— get company financial reportschanges.by_sogc()— get changes by SOGC publication IDchanges.batch()— batch fetch changes for up to 50 UIDschanges.review()— mark a change as reviewedpersons.list()— list persons with optional searchpersons.roles()— get all roles held by a personpersons.connections()— get person network connectionspersons.board_members()— get board members of a companypersons.network_stats()— person network statisticsdossiers.list()— list all generated dossiersdossiers.get()— get a specific dossierdossiers.statistics()— dossier generation statisticsanalytics.cohorts()— cohort analytics with groupBy/canton paramsanalytics.cluster()— K-Means clustering (POST)analytics.anomalies()— anomaly detection (POST)analytics.velocity()now acceptsdaysparameterteams.billing_summary()— team billing summaryteams.list_members()— list team membersteams.invite_member()— invite a team memberteams.update_member_role()— update member roleteams.remove_member()— remove a team member- Watches resource (
client.watches) —list,add,removecompany watches - Notifications resource (
client.notifications) —listchange notifications - News resource (
client.news) —recentnews across all companies - CompanyWatch model — watch subscription with channel (InApp/Webhook/Email)
- TeamMember model — team member with role and invitation status
- ChangeNotification model — change notification record
- CreditLedgerEntry model — credit ledger entry (grant/debit/refund/expire)
- ConflictError (409) and ServiceUnavailableError (503) error types
- CI/CD pipeline — GitHub Actions for lint, typecheck, test (Python 3.11/3.12/3.13), and PyPI publishing on version tags
webhooksresource — replaced bywatchesin the APIusersresource — removed from the APIsettingsresource — removed from the APIcompanies.persons()— usepersons.board_members()insteadcompanies.dossier()— usedossiers.get()insteadcompanies.changes()— usechanges.by_company()instead- Typed models:
PersonRole,CompanyComparison,CompanyNews,Webhook,WebhookCreated,UserProfile,CheckoutSessionResponse,PortalSessionResponse,CreditBalance,UsageBreakdown,UsageOperation,ChangeStatistics,RelationshipsResponse,CantonAnalytics,AuditorAnalytics,RfmSegment
- Async client (
AsyncClient) with context manager support - Sync client (
Client) with context manager support - 12 resource modules covering the VynCo public API:
companies— search, get by UID, count, statistics, compare, board members, dossier, relationships, hierarchy, change history, batch get, newspersons— get by ID, search by namedossiers— generate AI company reports (summary/standard/comprehensive)changes— list recent changes, get by company, change statisticscredits— balance, usage breakdown, transaction historyapi_keys— list, create, revoke API keysbilling— Stripe checkout and portal sessionswebhooks— list, create, get, update, delete, testteams— get current team, create teamusers— get profile, update profilesettings— get and update user preferencesanalytics— canton analytics, auditor analytics, RFM segmentation, velocity
- Pydantic v2 models with camelCase alias generation for all API types
- Response metadata via
Response[T]wrapper exposing API headers:X-Request-Id— request tracingX-Credits-Used— credits consumedX-Credits-Remaining— remaining balanceX-Rate-Limit-Limit— tier rate limitX-Data-Source— OGD compliance (Zefix/LINDAS)
- Typed error handling with exception hierarchy mapping HTTP status codes:
AuthenticationError(401),InsufficientCreditsError(402),ForbiddenError(403)NotFoundError(404),ValidationError(400/422),RateLimitError(429),ServerError(5xx)ConfigError(client misconfiguration),DeserializationError(response parsing)
- Automatic retry with exponential backoff on 429 and 5xx responses
- Retry-After header support for rate-limited requests
- Environment variable support (
VYNCO_API_KEY) - PEP 561 type stub marker (
py.typed) - 41 tests with respx covering configuration, authentication, error mapping, retry logic, response metadata, and all resource operations