OpenBI Docs Buy

API reference

The OpenBI SDK’s contract, type by type, generated from the source of OpenBI Studio 0.3.74’s SDK and its documentation comments. The SDK’s zip has the same documentation as OpenBI.Studio.Sdk.xml, for your editor.

Start with IStudioPlugin, what you write, and IStudioHost, what the studio gives. What a plug-in can do explains them by task.

The contract of OpenBI Studio 0.3.78: version 1.2 (SdkInfo.Contract), 12 public types in the OpenBI.Studio.Sdk namespace.

IStudioPlugin

public interface IStudioPlugin

A plug-in of OpenBI Studio: a specialty module (Customer Service ...). The studio makes it (its entry type has a constructor without parameters) when it is turned on, gives it the host, and shows its views as tabs under Specialty; its agent tools join the studio's own, for the agent built into the studio and for agents that connect over MCP.

void Initialize(IStudioHost host)
Once, when the plug-in is turned on (on the UI thread).
IReadOnlyList<PluginView> Views
Its tabs, under Specialty in the studio's navigation.
IReadOnlyList<Type> AgentTools
Its agent tools: [McpServerToolType] classes, made with the studio's services (IStudioHost among them, and what ConfigureServices adds). Has a body of its own: a plug-in need not implement it.
void ConfigureServices(IServiceCollection services)
Its own services, for its agent tools. Has a body of its own: a plug-in need not implement it.
string? AgentInstructions
What it tells agents about itself (added to the studio's instructions), or null. Has a body of its own: a plug-in need not implement it.
void Shutdown()
When the studio closes or the plug-in is turned off. Has a body of its own: a plug-in need not implement it.

PluginView

public sealed record PluginView(string Id, string Title, string Glyph, string ToolTip, Func<object> Create, bool NeedsProject = false)

A tab of a plug-in: its title and glyph in the navigation, and its view, made once when first shown (on Windows, a WPF FrameworkElement: the SDK itself stays free of WPF, so a plug-in's logic builds and tests anywhere).

Its members: string Id { get; init; }, string Title { get; init; }, string Glyph { get; init; }, string ToolTip { get; init; }, Func<object> Create { get; init; }, bool NeedsProject { get; init; }.

IStudioHost

public interface IStudioHost

What the studio gives its plug-ins.

string StudioVersion
The studio's version (0.3.16).
Task InvokeAsync(Action action)
Runs work on the studio's UI thread (views, dialogs) and waits for it.
void Post(Action action)
Runs work on the UI thread later, without waiting.
object MainWindow
The studio's main window (on Windows, a WPF Window: the owner of a plug-in's dialogs).

Also: Task<T> InvokeAsync<T>(Func<T> func).

The open project and its model

event EventHandler? ProjectChanged
A project was opened or closed, or its model loaded again.
IReadOnlyList<TableInfo> Tables
The model's tables and their columns.
Task<QueryResult> DaxAsync(string dax, CancellationToken ct = default)
An OAX query (compatible with DAX) on the open model (as the developer: all data).
void ShowDax(string dax)
Shows an OAX query in the OAX tab (what the agent ran, to look at).
void ShowView(string view)
Shows one of the studio's views: report, tables, model, dax, prediction, operations-research, quantitative, networks, versions; or asks the person for a project: open-project (a project folder), import-powerbi (a Power BI model, .pbip or TMDL); or new-window: the tab in front (a plug-in's own) in a window of its own, for another screen. A plug-in's own tab keeps its state while another view shows, and in its window.

Also: bool HasProject, string? ProjectName, string? ProjectFolder.

A data plug-in's model (an ERP's or a SaaS's tables)

Task ReloadModelAsync(bool refresh = false, CancellationToken ct = default)
Reads the open project's model again from its folder and loads its data, after the plug-in wrote its files (tables, M queries, measures, relationships: OpenBI.Studio.Core's StudioProject; report pages in its reports folder), as after a git pull. refresh: every table read from its source again; else a table whose definition did not change keeps its rows. A source without a sign-in on this computer asks the developer for one, as the studio's own refresh does. Has a body of its own: a plug-in need not implement it.
Task SetSourceCredentialAsync(string address, string auth, string user, string? secret, CancellationToken ct = default)
Keeps a data source's sign-in in this account's credential store (the engine's, sealed for this user: DPAPI on Windows, the keychain's key on a Mac), as the developer's own sign-in does: the model's queries then reach the source without the secret in the model, its M, the project or the logs. address: a web origin (https://api.example.com) or a source's address; auth: key (user: the header's name, Authorization when empty; secret: its value, Bearer ... for a bearer token), password (user and password) or anonymous. The studio's Data sources change or remove it later. Has a body of its own: a plug-in need not implement it.
Task<bool> HasSourceCredentialAsync(string address, CancellationToken ct = default)
Whether this account's credential store has a sign-in for the address (its secret is never given out). Has a body of its own: a plug-in need not implement it.
Task RemoveSourceCredentialAsync(string address, CancellationToken ct = default)
Removes this account's sign-in for the address, if it has one. Has a body of its own: a plug-in need not implement it.

What plug-ins keep

IPluginStorage Storage(string pluginId)
A plug-in's settings and secrets on this computer (never in a project).

Sign-ins

Task<string?> MicrosoftTokenAsync(string[] scopes, bool interactive, string? clientId = null, string authority = "organizations", string? loginHint = null, CancellationToken ct = default)
A Microsoft access token for the scopes (quietly, else - interactive - Microsoft's sign-in window). clientId: an Entra application of the plug-in's (else the studio's choice for the scopes); authority: organizations, consumers or common.

Privacy

PrivacySettings Privacy
How personal data is protected from agents: off, OpenBI's own detectors, or those and Presidio's (IT's policy can impose it).
Pseudonymizer NewPseudonymizer()
A new pseudonymizer (personal data to numbered tokens and back), with the detectors the settings name.
bool SetPrivacy(string mode, string? presidioUrl, string language = "en")
Sets the studio's privacy (off, identifiers, presidio; Presidio's analyzer address), unless IT's policy imposes it (then false).

The agent

AgentEndpoint Agent
The studio's MCP endpoint, where an agent of one's own connects (Claude Code, Claude Desktop ...): its address, whether it listens, and how an agent connects. Its Key is empty (contract 1.2): the studio's agent key is the person's, never a plug-in's. A plug-in that calls the endpoint itself asks with RequestAgentAccessAsync.
Task<PluginAgentAccess?> RequestAgentAccessAsync(bool ask = true, CancellationToken ct = default)
This plug-in's own access to the studio's agent endpoint (contract 1.2): a key of its own, for the tools its plugin.json names (AgentAccess). The person allows it once per version of the plug-in: OpenBI Studio asks them, with the reason plugin.json gives, the first time (ask: false never asks: the access when it is allowed already, else null). Null also when plugin.json asks for none, when the person says no, or when IT's policy turns it off. The key lives while the studio runs and the plug-in is on, in memory only: when the person withdraws it (Specialty modules), or the plug-in is turned off or removed, the endpoint refuses it from the next request and Revoked is cancelled. Every call made with it shows in the Activity panel as the plug-in's. Ask with the host your Initialize was given (keep it): the host the studio's services give agent tools is the studio's, which knows no plug-in, and answers null. Has a body of its own: a plug-in need not implement it.

The studio

void Log(string title, string detail = "", bool error = false, bool byAgent = false)
A line in the activity panel (byAgent: done for an agent).
void SetStatus(string text)
The status bar's text.
void ShowPluginView(string pluginId, string viewId)
Shows one of the plug-in's tabs.

IPluginStorage

public interface IPluginStorage

A plug-in's settings (a JSON object) and secrets (encrypted for the Windows user), on this computer.

Its members: T Load<T>(string name) where T : new(), void Save<T>(string name, T value), string? Secret(string key), void SetSecret(string key, string? value).

AgentEndpoint

public sealed record AgentEndpoint(string Url, [property: Obsolete("Empty from OpenBI SDK contract 1.2: the studio's agent key is never a plug-in's. Declare agentAccess in plugin.json and call IStudioHost.RequestAgentAccessAsync for a key of the plug-in's own.")] string Key, bool Listening, string ClaudeCommand, string JsonConfig)

The studio's MCP endpoint: its address, whether it listens, and how an agent connects - Claude Code's command and any client's settings, both the stdio server (OpenBI.Studio.exe --mcp) that finds the endpoint and its key by itself: neither has the key in it. Key: empty for plug-ins (contract 1.2; 1.0 and 1.1 gave the studio's agent key).

Its members: string Url { get; init; }, [property: Obsolete("Empty from OpenBI SDK contract 1.2: the studio's agent key is never a plug-in's. Declare agentAccess in plugin.json and call IStudioHost.RequestAgentAccessAsync for a key of the plug-in's own.")] string Key { get; init; }, bool Listening { get; init; }, string ClaudeCommand { get; init; }, string JsonConfig { get; init; }.

PrivacySettings

public sealed record PrivacySettings

Whether, and how, personal data is pseudonymized before an agent sees it.

string Mode
off (personal use) | identifiers (OpenBI's own: identifiers and known names) | presidio (those and Presidio's analyzer).
bool Enforced
IT's policy sets it (the user cannot turn it off).

Also: string? PresidioUrl, string PresidioLanguage, bool On.

PluginManifest

public sealed record PluginManifest

A plug-in's manifest (plugin.json, beside its assembly): who it is, what it needs and where its entry point is.

{ "id": "openbi.customer-service", "name": "Customer Service", "version": "1.0.0", "publisher": "Norway Analytics", "description": "...", "assembly": "OpenBI.CustomerService.dll", "entry": "OpenBI.CustomerService.CustomerServicePlugin", "minStudio": "0.3.16", "sdk": "1.1", "license": { "product": "customer-service", "required": false } }
string Id
In plugin.json: id.
string Name
In plugin.json: name.
string Version
In plugin.json: version.
string Publisher
In plugin.json: publisher.
string Description
In plugin.json: description.
string Assembly
In plugin.json: assembly.
string Entry
In plugin.json: entry.
string? MinStudio
The oldest studio it runs in (its SDK's). In plugin.json: minStudio.
string? Sdk
The SDK contract it was built against (major.minor, SdkInfo): what its assembly says wins; this, for a plug-in built without the SDK's build targets; neither: 1.0, built before contracts were numbered. In plugin.json: sdk.
string? Glyph
Segoe Fluent glyph of its card on the Plug-ins page. In plugin.json: glyph.
PluginLicense? License
Its licence (paid modules): the product a key unlocks, and whether one is needed to run. In plugin.json: license.
PluginAgentAccessRequest? AgentAccess
What it asks of the studio's agent endpoint (contract 1.2): a key of its own, for the tools named, which the person allows once per version (RequestAgentAccessAsync). Null: it asks for none, and gets none.
"agentAccess": { "reason": "Starts Claude Code on your project to draft the month's commentary.", "tools": ["studio_status", "model_measures", "dax_query"] }
In plugin.json: agentAccess.

PluginLicense

public sealed record PluginLicense
string Product
In plugin.json: product.
bool Required
In plugin.json: required.

SdkInfo

public static class SdkInfo

The contract between OpenBI Studio and its plug-ins, and its version: major.minor. A plug-in built with the SDK carries the contract it was built against in its assembly (the SDK's build targets write [assembly: AssemblyMetadata("OpenBI.Studio.Sdk.Contract", "1.2")]; a plug-in built otherwise can say it in plugin.json: "sdk": "1.2"), and the studio compares it with its own when it finds the plug-in:

  • the same major, an older minor: it runs. A minor version only adds (a new member of IStudioHost has a body of its own), so what the plug-in was built against is all still there;
  • an older major: it does not run. A major version breaks plug-ins built before it (a member changed or removed): the plug-in must be built again with the new SDK;
  • a newer contract than the studio's: it needs a newer OpenBI Studio.

A plug-in that says nothing was built before contracts were numbered (OpenBI SDK 0.3.66 or older): contract 1.0. The contract's version is not the SDK's (the SDK's version is OpenBI Studio's) nor its assemblies' (1.0.0.0, kept so whatever version a build is given). 1.2 also empties AgentEndpoint.Key, which 1.0 and 1.1 filled with the studio's agent key: no plug-in of Norway Analytics' read it (they show the endpoint's address and Claude Code's command, which hold no key), and the key is the person's, for the agents they connect. The member stays (a plug-in that reads it still loads, and gets "").

const string Contract
The contract of this SDK, and of the OpenBI Studio built with it.
const string ContractMetadataKey
The key of the assembly metadata that carries a plug-in's contract.
const string Unstated
The contract of a plug-in that states none: built before contracts were numbered (SDK 0.3.66 or older).
static IReadOnlyList<SdkContractVersion> History
Each version of the contract: the studio it came with, what it added or changed, and - when a plug-in built against an older minor misses something people would notice - a sentence that says so (null: nothing to say).

SdkContractVersion

public sealed record SdkContractVersion(string Contract, string Studio, string Added, string? Note)

A version of the SDK's contract: the studio it came with, what it added, and what a plug-in built before it misses (shown to people only when not null).

Its members: string Contract { get; init; }, string Studio { get; init; }, string Added { get; init; }, string? Note { get; init; }.

PluginAgentAccessRequest

public sealed record PluginAgentAccessRequest

A plug-in's request for the studio's agent endpoint, in its plugin.json (contract 1.2): why, in a sentence the person reads before they allow it, and the agent tools it may call (their names: dax_query, model_measures ...; none named: every tool, the studio's and its plug-ins', as an agent the person connects has).

string Reason
In plugin.json: reason.
IReadOnlyList<string>? Tools
In plugin.json: tools.

PluginAgentAccess

public sealed record PluginAgentAccess(string Url, string Key, IReadOnlyList<string> Tools, string JsonConfig, CancellationToken Revoked)

A plug-in's own access to the studio's agent endpoint (RequestAgentAccessAsync): the endpoint's address, the plug-in's key (a password: send it as the header Authorization: Bearer <key>, never show it, never write it to a file), the tools it may call (empty: every tool), an MCP client's settings with the key in them (for an agent the plug-in starts itself: pass them in memory or a file only that agent reads, deleted after), and Revoked, cancelled when the key ends (the person withdrew it, the plug-in was turned off or removed).

Its members: string Url { get; init; }, string Key { get; init; }, IReadOnlyList<string> Tools { get; init; }, string JsonConfig { get; init; }, CancellationToken Revoked { get; init; }.

Types from OpenBI.Studio.Core

The contract hands a plug-in these types of the studio's own library (OpenBI.Studio.Core, which the SDK carries as a reference assembly): the open model's tables, a query's rows, the pseudonymizer. Their public members:

TableInfo

public sealed record TableInfo(string Name, bool Hidden, long Rows, IReadOnlyList<ColumnInfo> Columns, IReadOnlyList<string> Measures)
bool Live
A live connection's table: its rows stay on the server.
string? Error
Why a calculated table is empty (a prediction that cannot score yet), or null.

Also: string Name { get; init; }, bool Hidden { get; init; }, long Rows { get; init; }, IReadOnlyList<ColumnInfo> Columns { get; init; }, IReadOnlyList<string> Measures { get; init; }, string? Description, string? DataCategory, bool Calculated, string? Expression, IReadOnlyList<MeasureInfo> MeasureDetails, string RowsText.

ColumnInfo

public sealed record ColumnInfo(string Name, string Type, bool Hidden, bool Calculated, long Cardinality)
bool Override
A live connection's column whose properties this project sets (format, folder, …: ProjectSets), not the server.
string? Problem
A live connection's: what the server's model no longer has for it (renamed or removed there), or null.
string? Warning
What the column's load found that Power BI refuses and OpenBI keeps (a PATH over a parent that is no row's id: the rows, by their ids), or null.

Also: string Name { get; init; }, string Type { get; init; }, bool Hidden { get; init; }, bool Calculated { get; init; }, long Cardinality { get; init; }, string? Format, string? Description, string? Folder, string? DataCategory, string? SortBy, bool IsKey, string? Expression, IReadOnlyList<string> ProjectSets.

MeasureInfo

public sealed record MeasureInfo(string Table, string Name, string Expression, string? Format, string? Folder, string? Description, bool Hidden)
bool Remote
A live connection's measure defined on the server (not one of the project's own).
bool Override
The server's measure whose properties this project sets (format, folder, …: ProjectSets), not the server.
string? Problem
A live connection's: what the server's model no longer has for it (renamed or removed there), or null.
string? Warning
Its name a column's of its table, or another measure's (Power BI refuses both; the model opens): the engine's words, or null.

Also: string Table { get; init; }, string Name { get; init; }, string Expression { get; init; }, string? Format { get; init; }, string? Folder { get; init; }, string? Description { get; init; }, bool Hidden { get; init; }, IReadOnlyList<string> ProjectSets.

QueryResult

public sealed class QueryResult

Rows of a DuckDB preview or a DAX query.

bool IsStatement
A statement (ATTACH, SET, ...) that returns no rows.
int Statements
How many statements a DuckDB text ran (separated by ;): the rows are the last one's.
ServerTimings? Timings
Where a DAX query's time went, when it was asked for (DaxAsync).
QueryPlan? Plan
Its plan as it ran (asked for with plan: true), else null.
IReadOnlyList<DraftScored> Drafts
The model versions no one has approved, or retired ones, that PREDICT("name", n) scored in it (only a studio test call may: asked for with drafts: true): the answer is a test's - a report page or OpenBI Server refuses it. Empty: none.

Also: IReadOnlyList<ResultColumn> Columns, IReadOnlyList<JsonElement[]> Rows, bool Truncated, double Milliseconds, static string Text(JsonElement v).

ResultColumn

public sealed record ResultColumn(string Name, string Type)

A column of a result: its name and its type (DuckDB or DAX).

Its members: string Name { get; init; }, string Type { get; init; }.

Pseudonymizer

public sealed partial class Pseudonymizer

Personal data to numbered tokens and back: what an agent sees is "Hi <PERSON_1>, your card <CARD_1> ...", each person or number one token wherever it is written (the mail, the operator's question, the model's rows), so the agent can still tell them apart and match them; its drafts and tool calls get the real values back here. The tokens and their values stay in this object, in memory on this computer.

NameDetector Names
The names it finds wherever they are written (add the mail's people, the model's person names).
int Count
How many values it has replaced so far.
void Keep(string value)
A value never replaced, wherever it is written: what is not personal data of the people written about (a mailbox's own address: the side the agent writes for).
string TokenFor(string type, string value)
The token of a value (a new one the first time it is seen).
Task<List<PiiSpan>> FindAsync(string text, CancellationToken ct = default)
The spans to replace: the detectors' findings, each on one line, overlaps settled (the longest, then the surest, wins; a guess never runs past the end of a name it knows).
event Action<string>? PresidioFailed
Presidio's analyzer did not answer: the text was pseudonymized by OpenBI's own detectors only. It says why in a fixed short text (PresidioDetector.Reason: "Presidio did not answer (HTTP 503)"), never with the analyzer's answer or the text sent.
Task<string> AnonymizeAsync(string text, CancellationToken ct = default)
The text with its personal data replaced by tokens (tokens already in it are kept).
Task<string> AnonymizeNameAsync(string displayName, CancellationToken ct = default)
A name from a mail's header (From, To, Cc): a person's, whatever the name lists say - learned (so it is found wherever it is written, with their first name and surname alone) and given as a token, never as it is. Its person is one token ("Hansen, Kari" and "Kari Hansen" the same); a company after it ("Ingrid Nordmann - Fjordlast Transport") stays text, itself pseudonymized.
string Restore(string text)
The tokens in a text (an agent's draft, a tool's argument) replaced by their values; unknown ones stay.
JsonNode? Restore(JsonNode? node)
Every string of a JSON value restored (a tool call's arguments).
IReadOnlyList<(string Token, string Value)> Map()
The tokens and their values, for the operator to see what the agent was not shown.
static bool IsToken(string text)
Whether a text is one token, as this gives them (<PERSON_1>).

Also: Pseudonymizer(IEnumerable<IPiiDetector> detectors, NameDetector? names = null).

Last updated · OpenBI Studio 0.3.78