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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ TRASH-FILES.md
PRD-*.md
TODOS.md
bcapi_cli_prd.md
.planning/
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.5] — 2026-05-05

### Added

- **Business Central admin setup guide** — new
`docs/business-central-admin-setup.md` walks a zero-knowledge user
through Entra app registration, localhost redirect setup, delegated BC
permissions, admin consent, BC user permission sets, first `bcli
config init`, and verification.
- **`bcli-mcp` preview server** — an MCP (Model Context Protocol) server
that lets Claude Desktop and other MCP clients drive bcli. Four
read-only tools: `query`, `list_endpoints`, `describe_endpoint`,
Expand All @@ -18,12 +25,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- `bcli config init` now defaults to browser PKCE auth for local humans
and agents. New `--automation` and `--headless` shortcuts create
client-credentials and device-code profiles respectively.
- CLI runtime dependencies now ship with the base `bc-cli` install, so
`pip install bc-cli` and `uv tool install bc-cli` provide a working
`bcli` command without requiring an extra.
- `bcli company list` accepts `--format` (`json`, `markdown`, `csv`,
`ndjson`, `table`). Stable JSON shape:
`[{"id", "name", "alias", "is_default"}]`.
- `bcli endpoint list` and `bcli endpoint info` accept `--format json`.
Stable JSON shapes documented inline in each command's help text.

### Removed

- Removed WorkOS AuthKit support. Browser PKCE is now the delegated auth
path, Business Central remains the permission boundary, and
client-credentials profiles cover automation.

## [0.1.2] — 2026-04-29

Security release. Closes four findings from a strix.ai run against the
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue.svg)](pyproject.toml)

A Python SDK and CLI for Microsoft Dynamics 365 Business Central APIs, with a built-in [dlt](https://dlthub.com) source for ETL backup pipelines.
A Python SDK and CLI for Microsoft Dynamics 365 Business Central APIs, with
agent-friendly endpoint discovery, browser auth, custom API registries, and a
built-in [dlt](https://dlthub.com) source for ETL backup pipelines.

> **Status: Alpha (0.1.x).** Public surface may change before 1.0. Track
> [CHANGELOG.md](CHANGELOG.md) for breaking changes. Independent project
Expand Down Expand Up @@ -50,7 +52,7 @@ pip install bc-cli
# or
uv tool install bc-cli

# Configure (interactive — discovers companies automatically)
# Configure with browser auth (no client secret)
bcli config init

# Query standard APIs immediately
Expand All @@ -73,7 +75,7 @@ bcli get myCustomEntities --top 5
- **Multi-company** — Assign aliases to companies and query across all entities
- **OData query builder** — `--filter`, `--select`, `--expand`, `--orderby`, `--top`, `--skip` on every query
- **Multiple output formats** — table, JSON, CSV, NDJSON for pipeline use
- **Secure auth** — OS keychain integration (macOS Keychain, Windows Credential Manager), token caching, client credentials + device code flows
- **Secure auth** — Browser PKCE by default, OS keychain support for automation secrets, token caching, client credentials + device code fallback
- **Write safety** — SafeContext gate prevents wrong-environment writes, enforces draft status on financial documents
- **Programmatic auth** — Pass credentials directly for MCP servers, Airflow DAGs, and containers (no config files required)
- **Batch operations** — Execute sequences of API calls from YAML files
Expand Down Expand Up @@ -134,17 +136,14 @@ Requires Python 3.11+.
> documented.

```bash
# SDK only (for libraries, MCP servers, Airflow DAGs)
# CLI + SDK
pip install bc-cli

# SDK + CLI
pip install "bc-cli[cli]"

# SDK + ETL (dlt source for backup pipelines)
pip install "bc-cli[etl]"

# Everything
pip install "bc-cli[cli,etl]"
pip install "bc-cli[etl,mcp,telemetry]"

# Via uv (recommended)
uv tool install bc-cli
Expand All @@ -160,8 +159,9 @@ pip install -e ".[dev,etl]"
| Guide | Description |
|-------|-------------|
| [Getting Started](docs/getting-started.md) | First-time setup, authentication, your first query |
| [Business Central Admin Setup](docs/business-central-admin-setup.md) | Entra app registration and BC permissions from scratch |
| [Configuration](docs/configuration.md) | Profiles, environments, config file format |
| [Authentication](docs/authentication.md) | Client credentials, device code, OS keychain |
| [Authentication](docs/authentication.md) | Browser auth, client credentials, device code fallback |
| [Querying Data](docs/querying.md) | GET, OData filters, pagination, output formats |
| [Write Operations](docs/write-operations.md) | POST, PATCH, DELETE |
| [Custom APIs](docs/custom-apis.md) | Importing from Postman, JSON, or $metadata |
Expand Down
212 changes: 61 additions & 151 deletions docs/authentication.md
Original file line number Diff line number Diff line change
@@ -1,194 +1,104 @@
# Authentication

bcli supports four authentication methods for Business Central online:
bcli supports three Business Central online authentication methods:

| Method | Flow | Use case |
|--------|------|----------|
| `client_credentials` | App permissions (service-to-service) | CI/CD, MCP servers, background jobs |
| `browser` | Authorization code with PKCE (delegated) | Interactive dev, individual user permissions |
| `device_code` | Device code (delegated) | Headless hosts, SSH sessions, non-browser envs |
| `workos` | WorkOS AuthKit SSO → role-based BC client | Teams with role-based access control |
| `browser` | Authorization code with PKCE | Default for local humans and AI agents |
| `client_credentials` | App permissions | CI/CD, servers, scheduled jobs |
| `device_code` | Delegated device code | SSH/headless fallback |

Select a method per profile via `auth_method` or override per call with `bcli auth login --method <method>`.
For a full zero-knowledge Entra ID and Business Central setup, start with
[Business Central Admin Setup](business-central-admin-setup.md).

## Client Credentials (Service-to-Service)
## Browser Auth (Recommended)

The default method. Uses an Azure Entra ID app registration with application permissions.

### Prerequisites

1. An app registration in Azure Entra ID
2. `API.ReadWrite.All` application permission granted for Dynamics 365 Business Central
3. A client secret generated for the app registration

### Setup
Browser auth is the default for `bcli config init`. It opens the user's browser,
uses PKCE, needs no client secret, and Business Central enforces the signed-in
user's permission sets.

```bash
bcli config init
# Provide tenant ID, client ID, and the name of the env var holding your secret
```

### Secret Storage

bcli never stores secrets in config files. Instead, it resolves secrets at runtime in this order:

1. **OS Keychain** (recommended) — macOS Keychain, Windows Credential Manager
2. **Environment variable** — Referenced by name in `client_secret_env`
3. **Generic fallback** — `BCLI_SECRET` or `BCLI_CLIENT_SECRET` env vars

#### Store in Keychain (Recommended)

```bash
bcli auth store-secret
# Prompted for the secret (hidden input)
```

This is the best option because:
- The secret persists across shell sessions
- No env var to manage
- Works with any tool that runs bcli (Claude Code, scripts, cron)
- Secured by OS-level encryption

#### Store as Environment Variable

```bash
# In your shell profile (~/.zshrc or ~/.bashrc)
export BCLI_SECRET="your-secret-here"
```

### Token Caching

After authentication, bcli caches the access token at `~/.config/bcli/tokens.json`. Tokens are reused until 5 minutes before expiry (~55 minutes for BC tokens). While a cached token is valid, no secret is needed.

```bash
# Check token status
bcli auth status

# Force re-authentication
bcli auth logout
bcli auth login
bcli get customers --top 5
```

## Browser (Authorization Code with PKCE)
The Entra app registration must be a public/native client with delegated
Business Central permissions and a localhost redirect URI. See
[Business Central Admin Setup](business-central-admin-setup.md) for the portal
steps.

Interactive browser-based OAuth. The user authenticates in their default browser; bcli captures the redirect via a local loopback on port 8400. Uses PKCE — no client secret needed, and delegated permissions mean the token carries the *user's* BC permissions, not app-wide ones.

### Setup
Useful login options:

```bash
bcli config set profiles.interactive.auth_method browser
bcli config set profiles.interactive.tenant_id "your-tenant-id"
bcli config set profiles.interactive.client_id "your-client-id"
bcli config set profiles.interactive.environment "Production"
```

The app registration must:
- Have delegated permissions for Business Central (`user_impersonation` / `Financials.ReadWrite.All`)
- Include `http://localhost:8400/callback` as a redirect URI
- Be configured as a public client (no client secret required)

### Usage
# Fresh browser session, useful when switching accounts
bcli auth login --method browser --incognito

```bash
bcli -p interactive auth login --method browser

# Fresh session (no cached browser login) — useful for switching accounts:
bcli -p interactive auth login --method browser --incognito
# Explicit browser profile setup
bcli config init --auth browser
```

If a `login_hint` is set in your profile (e.g. via WorkOS), the BC account picker is skipped automatically.

## Device Code (Interactive)
## Client Credentials

For interactive use where a user is present but a browser redirect is not practical (SSH, headless hosts). The user authenticates via a browser on any device — no client secret needed.

### Setup

Set `auth_method = "device_code"` in your profile:

```bash
bcli config set profiles.interactive.auth_method device_code
bcli config set profiles.interactive.tenant_id "your-tenant-id"
bcli config set profiles.interactive.client_id "your-client-id"
bcli config set profiles.interactive.environment "Production"
```

### Usage
Use client credentials only for automation: CI/CD, background jobs, servers, and
scheduled exports. This path uses application permissions and a client secret,
so it should be set up deliberately.

```bash
bcli -p interactive auth login --method device
# Prints a URL and code — open the URL in your browser and enter the code
bcli config init --automation
bcli auth store-secret
bcli get customers --top 5
```

The app registration must have delegated permissions (not application permissions) for device code flow to work.

## WorkOS AuthKit (Role-Based BC Access)
bcli never stores client secrets in config files. It resolves secrets in this
order:

Two-step auth: users authenticate via WorkOS SSO, then their WorkOS group determines which BC client_id they use for the BC browser flow. Useful for teams where different roles need different BC app registrations (e.g. `finance-read-only` vs `finance-write`).
1. OS keychain: macOS Keychain, Windows Credential Manager, or equivalent.
2. The configured `client_secret_env` environment variable.
3. Generic fallback env vars: `BCLI_SECRET` or `BCLI_CLIENT_SECRET`.

### Flow
In CI, use environment variables:

1. User authenticates via WorkOS AuthKit (browser redirect)
2. bcli looks up the user's WorkOS group membership
3. bcli maps the group to a BC `client_id` via the profile's `workos.groups` table
4. BC browser OAuth runs with the resolved `client_id` (PKCE, delegated)

The WorkOS identity is cached at `~/.config/bcli/workos_identity.json` until it expires.
```yaml
env:
BCLI_SECRET: ${{ secrets.BC_CLIENT_SECRET }}

### Setup
steps:
- run: bcli get customers --top 1 -f json -q
```

```toml
[profiles.example]
tenant_id = "c6aabf12-..."
environment = "Production"
auth_method = "workos"
No `bcli auth login` is required when a valid secret is available; bcli acquires
tokens automatically.

[profiles.example.workos]
api_key_env = "WORKOS_API_KEY"
client_id = "client_01ABC..."
## Device Code

[profiles.example.workos.groups]
"finance-read" = "48074c7f-..." # BC app registration for read-only finance role
"finance-write" = "9a12d8e3-..." # BC app registration for write finance role
"ops-full" = "bf441e7a-..."
```

Install the `cli` extra (WorkOS SDK is included):
Device code is a fallback for hosts where a localhost browser callback is not
practical, such as SSH sessions or locked-down remote machines.

```bash
pip install "bc-cli[cli]"
bcli config init --headless
bcli auth login --method device
```

### Usage

```bash
bcli -p example auth login --method workos
The terminal prints a URL and code. Open the URL in any browser, enter the code,
and bcli caches the resulting delegated token.

# Switch to a different user without touching OS browser sessions:
bcli -p example auth login --method workos --incognito
```
## Token Cache

## Auth Commands
After authentication, bcli caches access tokens at
`~/.config/bcli/tokens.json`. Tokens are reused until shortly before expiry.

```bash
bcli auth login [--method ...] [-i] # Authenticate and cache token (see command-reference.md)
bcli auth status # Show token and keychain status
bcli auth logout # Clear cached tokens
bcli auth store-secret # Store secret in OS keychain
bcli auth delete-secret # Remove secret from OS keychain
bcli auth status
bcli auth logout
```

## CI/CD Usage

For CI/CD pipelines, use environment variables:

```yaml
# GitHub Actions example
env:
BCLI_SECRET: ${{ secrets.BC_CLIENT_SECRET }}

steps:
- run: bcli get customers --top 1 -f json -q
```
## Common Failures

No `bcli auth login` is needed — bcli acquires tokens automatically when a secret is available.
| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| Redirect URI mismatch | Entra app lacks localhost redirect URI | Add `http://localhost` as a mobile/desktop redirect URI |
| Consent required | Tenant admin has not granted API consent | Grant admin consent for Business Central delegated permissions |
| 403 Forbidden | User lacks BC permission set for that page/company | Assign the user the required Business Central permissions |
| Wrong tenant/account | Browser reused an existing Microsoft session | Re-run with `--incognito` |
| Secret missing | Automation profile cannot find `BCLI_SECRET` or keychain secret | Run `bcli auth store-secret` or set the env var |
Loading
Loading