Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.10.0"
".": "0.11.0"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 28
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/formbricks/hub-eea9fcd4320ce8aaa8745747908c06bdd77e8b4052d9995dde1ecca8168283a8.yml
openapi_spec_hash: 3ee9cb170c7c23af4d2ca746bd1d26dc
config_hash: a2271788ad3aa0e64181e7131d48213f
configured_endpoints: 29
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/formbricks/hub-415f62eebdb4b52442a1a9463944d9038ffff980718442efde39aae9ac43073d.yml
openapi_spec_hash: f6526fad0afa1782ecec5e52610fbcc9
config_hash: 0b48961f5f0b4dca3a9a4342cd42bd51
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.11.0 (2026-07-31)

Full Changelog: [v0.10.0...v0.11.0](https://github.com/formbricks/hub-typescript/compare/v0.10.0...v0.11.0)

### Features

* expose per-tenant enrichment status and backlog metric (ENG-1670) ([6c72606](https://github.com/formbricks/hub-typescript/commit/6c72606ec41b69a44d9d3e9197dc357d123a1ff2))


### Bug Fixes

* **taxonomy:** 404 node records on a tenant mismatch (ENG-1887) ([bd87201](https://github.com/formbricks/hub-typescript/commit/bd87201a5af334044acad1a9427820a07148e00d))

## 0.10.0 (2026-07-18)

Full Changelog: [v0.9.0...v0.10.0](https://github.com/formbricks/hub-typescript/compare/v0.9.0...v0.10.0)
Expand Down
11 changes: 11 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,14 @@ Methods:
- <code title="get /v1/taxonomy/nodes/{node_id}/records">client.taxonomy.nodes.<a href="./src/resources/taxonomy/nodes.ts">listRecords</a>(nodeID, { ...params }) -> NodeListRecordsResponse</code>
- <code title="patch /v1/taxonomy/nodes/{node_id}">client.taxonomy.nodes.<a href="./src/resources/taxonomy/nodes.ts">rename</a>(nodeID, { ...params }) -> Node</code>
- <code title="delete /v1/taxonomy/nodes/{node_id}">client.taxonomy.nodes.<a href="./src/resources/taxonomy/nodes.ts">softRemove</a>(nodeID, { ...params }) -> Node</code>

# EnrichmentStatus

Types:

- <code><a href="./src/resources/enrichment-status.ts">TypeStatus</a></code>
- <code><a href="./src/resources/enrichment-status.ts">EnrichmentStatusRetrieveResponse</a></code>

Methods:

- <code title="get /v1/enrichment-status">client.enrichmentStatus.<a href="./src/resources/enrichment-status.ts">retrieve</a>({ ...params }) -> EnrichmentStatusRetrieveResponse</code>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@formbricks/hub",
"version": "0.10.0",
"version": "0.11.0",
"description": "The official TypeScript library for the Formbricks Hub API",
"author": "Formbricks Hub <hola@formbricks.com>",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"dxt_version": "0.2",
"name": "@formbricks/hub-mcp",
"version": "0.10.0",
"version": "0.11.0",
"description": "The official MCP Server for the Formbricks Hub API",
"author": {
"name": "Formbricks Hub",
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@formbricks/hub-mcp",
"version": "0.10.0",
"version": "0.11.0",
"description": "The official MCP Server for the Formbricks Hub API",
"author": "Formbricks Hub <hola@formbricks.com>",
"types": "dist/index.d.ts",
Expand Down
1 change: 1 addition & 0 deletions packages/mcp-server/src/code-tool-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const fuse = new Fuse(
'client.taxonomy.nodes.listRecords',
'client.taxonomy.nodes.rename',
'client.taxonomy.nodes.softRemove',
'client.enrichmentStatus.retrieve',
],
{ threshold: 1, shouldSort: true },
);
Expand Down
30 changes: 28 additions & 2 deletions packages/mcp-server/src/local-docs-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,14 +839,14 @@ const EMBEDDED_METHODS: MethodEntry[] = [
httpMethod: 'get',
summary: 'List feedback records for a taxonomy node',
description:
'Returns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped. The `limit` in the response reflects the applied cap.\n',
'Returns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped; returns 404 if the node does not belong to the tenant\nor has been removed. An empty `data` therefore means the node genuinely holds no records. The `limit`\nin the response reflects the applied cap.\n',
stainlessPath: '(resource) taxonomy.nodes > (method) list_records',
qualified: 'client.taxonomy.nodes.listRecords',
params: ['node_id: string;', 'tenant_id: string;', 'limit?: number;'],
response:
"{ data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]; limit: number; }",
markdown:
"## list_records\n\n`client.taxonomy.nodes.listRecords(node_id: string, tenant_id: string, limit?: number): { data: feedback_record_data[]; limit: number; }`\n\n**get** `/v1/taxonomy/nodes/{node_id}/records`\n\nReturns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped. The `limit` in the response reflects the applied cap.\n\n\n### Parameters\n\n- `node_id: string`\n\n- `tenant_id: string`\n Tenant that owns the node.\n\n- `limit?: number`\n Maximum number of feedback records to return.\n\n### Returns\n\n- `{ data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]; limit: number; }`\n\n - `data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]`\n - `limit: number`\n\n### Example\n\n```typescript\nimport FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub();\n\nconst response = await client.taxonomy.nodes.listRecords('019f177f-9abe-78cd-8008-f40b58e3147d', { tenant_id: 'org-123' });\n\nconsole.log(response);\n```",
"## list_records\n\n`client.taxonomy.nodes.listRecords(node_id: string, tenant_id: string, limit?: number): { data: feedback_record_data[]; limit: number; }`\n\n**get** `/v1/taxonomy/nodes/{node_id}/records`\n\nReturns the feedback records assigned to a node and all of its (visible) descendant nodes, via the\nclusters those nodes reference. Tenant-scoped; returns 404 if the node does not belong to the tenant\nor has been removed. An empty `data` therefore means the node genuinely holds no records. The `limit`\nin the response reflects the applied cap.\n\n\n### Parameters\n\n- `node_id: string`\n\n- `tenant_id: string`\n Tenant that owns the node.\n\n- `limit?: number`\n Maximum number of feedback records to return.\n\n### Returns\n\n- `{ data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]; limit: number; }`\n\n - `data: { id: string; collected_at: string; created_at: string; field_id: string; field_type: 'text' | 'categorical' | 'nps' | 'csat' | 'ces' | 'rating' | 'number' | 'boolean' | 'date'; source_type: string; submission_id: string; tenant_id: string; updated_at: string; emotions?: 'joy' | 'anger' | 'sadness' | 'fear' | 'surprise' | 'disgust'[]; field_group_id?: string; field_group_label?: string; field_label?: string; language?: string; metadata?: object; sentiment?: 'very_negative' | 'negative' | 'neutral' | 'positive' | 'very_positive' | 'mixed'; sentiment_score?: number; source_id?: string; source_name?: string; translation_lang_key?: string; user_id?: string; value_boolean?: boolean; value_date?: string; value_id?: string; value_number?: number; value_text?: string; value_text_translated?: string; }[]`\n - `limit: number`\n\n### Example\n\n```typescript\nimport FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub();\n\nconst response = await client.taxonomy.nodes.listRecords('019f177f-9abe-78cd-8008-f40b58e3147d', { tenant_id: 'org-123' });\n\nconsole.log(response);\n```",
perLanguage: {
typescript: {
method: 'client.taxonomy.nodes.listRecords',
Expand All @@ -859,6 +859,32 @@ const EMBEDDED_METHODS: MethodEntry[] = [
},
},
},
{
name: 'retrieve',
endpoint: '/v1/enrichment-status',
httpMethod: 'get',
summary: 'Get tenant enrichment status',
description:
'Returns a tenant\'s enrichment progress across the record-level enrichments\n(translation, sentiment, emotions). For each, `enabled` reports whether the enrichment\nis active for the tenant (deployment-configured and switched on / with a resolvable\ntarget language), and `eligible`/`done` are directory-level counts of feedback records\nthat qualify and that have been enriched — the UI derives "in progress" as\n`eligible - done`. When an enrichment is not enabled its counts are zero. The response\ncontains counts only (no record identifiers or content).\n',
stainlessPath: '(resource) enrichment_status > (method) retrieve',
qualified: 'client.enrichmentStatus.retrieve',
params: ['tenant_id: string;'],
response:
'{ emotions: { done: number; eligible: number; enabled: boolean; }; sentiment: { done: number; eligible: number; enabled: boolean; }; tenant_id: string; translation: { done: number; eligible: number; enabled: boolean; }; }',
markdown:
"## retrieve\n\n`client.enrichmentStatus.retrieve(tenant_id: string): { emotions: type_status; sentiment: type_status; tenant_id: string; translation: type_status; }`\n\n**get** `/v1/enrichment-status`\n\nReturns a tenant's enrichment progress across the record-level enrichments\n(translation, sentiment, emotions). For each, `enabled` reports whether the enrichment\nis active for the tenant (deployment-configured and switched on / with a resolvable\ntarget language), and `eligible`/`done` are directory-level counts of feedback records\nthat qualify and that have been enriched — the UI derives \"in progress\" as\n`eligible - done`. When an enrichment is not enabled its counts are zero. The response\ncontains counts only (no record identifiers or content).\n\n\n### Parameters\n\n- `tenant_id: string`\n Tenant whose enrichment status should be returned.\n\n### Returns\n\n- `{ emotions: { done: number; eligible: number; enabled: boolean; }; sentiment: { done: number; eligible: number; enabled: boolean; }; tenant_id: string; translation: { done: number; eligible: number; enabled: boolean; }; }`\n A tenant's enrichment progress across the record-level enrichments. Counts are directory-level totals.\n\n - `emotions: { done: number; eligible: number; enabled: boolean; }`\n - `sentiment: { done: number; eligible: number; enabled: boolean; }`\n - `tenant_id: string`\n - `translation: { done: number; eligible: number; enabled: boolean; }`\n\n### Example\n\n```typescript\nimport FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub();\n\nconst enrichmentStatus = await client.enrichmentStatus.retrieve({ tenant_id: 'org-123' });\n\nconsole.log(enrichmentStatus);\n```",
perLanguage: {
typescript: {
method: 'client.enrichmentStatus.retrieve',
example:
"import FormbricksHub from '@formbricks/hub';\n\nconst client = new FormbricksHub({\n apiKey: process.env['HUB_API_KEY'], // This is the default and can be omitted\n});\n\nconst enrichmentStatus = await client.enrichmentStatus.retrieve({ tenant_id: 'org-123' });\n\nconsole.log(enrichmentStatus.tenant_id);",
},
http: {
example:
'curl http://localhost:8080/v1/enrichment-status \\\n -H "Authorization: Bearer $HUB_API_KEY"',
},
},
},
];

const EMBEDDED_READMES: { language: string; content: string }[] = [
Expand Down
6 changes: 6 additions & 0 deletions packages/mcp-server/src/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ export const sdkMethods: SdkMethod[] = [
httpMethod: 'delete',
httpPath: '/v1/taxonomy/nodes/{node_id}',
},
{
clientCallName: 'client.enrichmentStatus.retrieve',
fullyQualifiedName: 'enrichmentStatus.retrieve',
httpMethod: 'get',
httpPath: '/v1/enrichment-status',
},
];

function allowedMethodsForCodeTool(options: McpOptions | undefined): SdkMethod[] | undefined {
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const newMcpServer = async ({
new McpServer(
{
name: 'formbricks_hub_api',
version: '0.10.0',
version: '0.11.0',
},
{
instructions: await getInstructions({ stainlessApiKey, customInstructionsPath }),
Expand Down
18 changes: 18 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import * as Errors from './core/error';
import * as Uploads from './core/uploads';
import * as API from './resources/index';
import { APIPromise } from './core/api-promise';
import {
EnrichmentStatus,
EnrichmentStatusRetrieveParams,
EnrichmentStatusRetrieveResponse,
TypeStatus,
} from './resources/enrichment-status';
import { Health, HealthCheckResponse } from './resources/health';
import {
WebhookCreateParams,
Expand Down Expand Up @@ -790,13 +796,18 @@ export class FormbricksHub {
* Automatic topic/subtopic taxonomy generation, run history, tree browsing, and node edits
*/
taxonomy: API.Taxonomy = new API.Taxonomy(this);
/**
* Tenant-scoped enrichment progress (translation, sentiment, emotions)
*/
enrichmentStatus: API.EnrichmentStatus = new API.EnrichmentStatus(this);
}

FormbricksHub.Health = Health;
FormbricksHub.FeedbackRecords = FeedbackRecords;
FormbricksHub.Webhooks = Webhooks;
FormbricksHub.Tenants = Tenants;
FormbricksHub.Taxonomy = Taxonomy;
FormbricksHub.EnrichmentStatus = EnrichmentStatus;

export declare namespace FormbricksHub {
export type RequestOptions = Opts.RequestOptions;
Expand Down Expand Up @@ -838,4 +849,11 @@ export declare namespace FormbricksHub {
type TaxonomyListFieldsResponse as TaxonomyListFieldsResponse,
type TaxonomyListFieldsParams as TaxonomyListFieldsParams,
};

export {
EnrichmentStatus as EnrichmentStatus,
type TypeStatus as TypeStatus,
type EnrichmentStatusRetrieveResponse as EnrichmentStatusRetrieveResponse,
type EnrichmentStatusRetrieveParams as EnrichmentStatusRetrieveParams,
};
}
90 changes: 90 additions & 0 deletions src/resources/enrichment-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { APIResource } from '../core/resource';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';

/**
* Tenant-scoped enrichment progress (translation, sentiment, emotions)
*/
export class EnrichmentStatus extends APIResource {
/**
* Returns a tenant's enrichment progress across the record-level enrichments
* (translation, sentiment, emotions). For each, `enabled` reports whether the
* enrichment is active for the tenant (deployment-configured and switched on /
* with a resolvable target language), and `eligible`/`done` are directory-level
* counts of feedback records that qualify and that have been enriched — the UI
* derives "in progress" as `eligible - done`. When an enrichment is not enabled
* its counts are zero. The response contains counts only (no record identifiers or
* content).
*/
retrieve(
query: EnrichmentStatusRetrieveParams,
options?: RequestOptions,
): APIPromise<EnrichmentStatusRetrieveResponse> {
return this._client.get('/v1/enrichment-status', { query, ...options });
}
}

/**
* One enrichment's progress for a tenant. When `enabled` is false, `eligible` and
* `done` are zero.
*/
export interface TypeStatus {
/**
* Eligible records that have been enriched.
*/
done: number;

/**
* Feedback records that qualify for this enrichment.
*/
eligible: number;

/**
* Whether the enrichment is active for the tenant (deployment-configured and
* switched on / with a resolvable target language).
*/
enabled: boolean;
}

/**
* A tenant's enrichment progress across the record-level enrichments. Counts are
* directory-level totals.
*/
export interface EnrichmentStatusRetrieveResponse {
/**
* One enrichment's progress for a tenant. When `enabled` is false, `eligible` and
* `done` are zero.
*/
emotions: TypeStatus;

/**
* One enrichment's progress for a tenant. When `enabled` is false, `eligible` and
* `done` are zero.
*/
sentiment: TypeStatus;

tenant_id: string;

/**
* One enrichment's progress for a tenant. When `enabled` is false, `eligible` and
* `done` are zero.
*/
translation: TypeStatus;
}

export interface EnrichmentStatusRetrieveParams {
/**
* Tenant whose enrichment status should be returned.
*/
tenant_id: string;
}

export declare namespace EnrichmentStatus {
export {
type TypeStatus as TypeStatus,
type EnrichmentStatusRetrieveResponse as EnrichmentStatusRetrieveResponse,
type EnrichmentStatusRetrieveParams as EnrichmentStatusRetrieveParams,
};
}
6 changes: 6 additions & 0 deletions src/resources/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

export {
EnrichmentStatus,
type TypeStatus,
type EnrichmentStatusRetrieveResponse,
type EnrichmentStatusRetrieveParams,
} from './enrichment-status';
export {
FeedbackRecords,
type FeedbackRecordData,
Expand Down
6 changes: 4 additions & 2 deletions src/resources/taxonomy/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import { path } from '../../internal/utils/path';
export class Nodes extends APIResource {
/**
* Returns the feedback records assigned to a node and all of its (visible)
* descendant nodes, via the clusters those nodes reference. Tenant-scoped. The
* `limit` in the response reflects the applied cap.
* descendant nodes, via the clusters those nodes reference. Tenant-scoped; returns
* 404 if the node does not belong to the tenant or has been removed. An empty
* `data` therefore means the node genuinely holds no records. The `limit` in the
* response reflects the applied cap.
*
* @example
* ```ts
Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const VERSION = '0.10.0'; // x-release-please-version
export const VERSION = '0.11.0'; // x-release-please-version
Loading
Loading