# Governance

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/http-api/governance/
> Summary: The governance endpoints of the Tracon HTTP API — 29 operations.

29 operations. `{prefix}` is the route prefix passed to
`MapTracon`; the template uses `/tracon`.

## GET {prefix}/api/approvals/rules

**Operation ID:** `TraconListApprovalRules`

**Lists persistent 'don't ask again' approval rules.**

Each rule pre-approves a tool call so it never reaches the approval mailbox again, which makes this list a standing grant worth reviewing. A rule with no agent name applies to every agent in the tenant. When it carries an arguments hash the rule matches only that exact call; without one it matches every call to that tool. Rules do not expire — remove one to start asking again.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `RunsRead` API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`ToolApprovalRule`](/http-api/schemas/toolapprovalrule/) | — |

## POST {prefix}/api/approvals/rules

**Operation ID:** `TraconCreateApprovalRule`

**Creates a persistent, argument-conditioned approval rule.**

Writes a standing 'don't ask again' rule with an admin-authored comparison (for example "amount <= 100"), evaluated on every call. There is no free-text expression field: the operator is a closed set and conditions combine with AND only. A code-defined policy (ITraconBuilder.AddToolApprovalPolicy) always runs first and can override this rule in both directions. For a tool of an MCP server the rule records the fingerprint of the server's current endpoint ('sourceFingerprint') and approves that tool only while the server keeps that endpoint; a tool of a server whose tools are not discovered right now answers 409.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `SecurityAdmin` API-key scope.

**Request body** (required):

- `application/json` → [`ToolApprovalRuleRequest`](/http-api/schemas/toolapprovalrulerequest/)

| Response | Body | Headers |
|---|---|---|
| **201** Created | `application/json` → [`ToolApprovalRule`](/http-api/schemas/toolapprovalrule/) | — |

## DELETE {prefix}/api/approvals/rules/{ruleId}

**Operation ID:** `TraconDeleteApprovalRule`

**Revokes a persistent approval rule.**

After the rule is deleted, approval is asked again for that tool.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `RunsWrite` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `ruleId` | path | yes | `string (uuid)` | — |

| Response | Body | Headers |
|---|---|---|
| **204** No Content | — | — |

## GET {prefix}/api/audit

**Operation ID:** `TraconListAudit`

**Lists audit entries, filterable by actor, action, entity, and date range.**

Filterable by actor, action, entity, and date range. Runs (an agent processing a message) are not written to this log; the runs table already keeps the full record. The one exception is the 'content.blocked' action: an IContentGuard's block decision is a GOVERNANCE decision, not a run detail, and must remain traceable even after the run record is deleted by retention policy. The entry carries only the guard and rule name, never the blocked TEXT.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AuditRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `actor` | query | no | `string` | — |
| `action` | query | no | `string` | — |
| `entity` | query | no | `string` | — |
| `after` | query | no | `string (date-time)` | — |
| `before` | query | no | `string (date-time)` | — |
| `limit` | query | no | `integer (int32)` | pattern `^-?(?:0\\|[1-9]\d*)$` |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`AuditEntry`](/http-api/schemas/auditentry/) | — |

## GET {prefix}/api/audit/{entity}

**Operation ID:** `TraconGetEntityAudit`

**Returns a single entity's change history, newest first.**

The path segment is the full entity key as it was recorded, in the form '<type>:<id>' — for example 'quota:<guid>' or 'retention:runs'. It is matched as written, not as a prefix. Entries carry the before and the after state, so one request answers 'who changed this and to what'. There is no paging: '?limit=' defaults to 100 and is clamped to 1..500, and only the newest entries are returned. An entity with no history returns an empty list, not 404.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AuditRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `entity` | path | yes | `string` | — |
| `limit` | query | no | `integer (int32)` | pattern `^-?(?:0\\|[1-9]\d*)$` |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`AuditEntry`](/http-api/schemas/auditentry/) | — |

## GET {prefix}/api/audit/verify

**Operation ID:** `TraconVerifyAuditChain`

**Walks the tenant's audit trail hash chain and reports whether it is intact.**

'Valid' means every entry's hash matches its content and links to the one before it. 'Broken' means an entry's stored hash no longer matches its content — it was altered after it was written. 'Gap' means a link between two entries is missing — a row was deleted, or a write never completed; 'firstFailingEntryId' names where. An entry written before this feature shipped carries no hash and is excluded from the walk, not misreported as broken. Without '?after='/'?before=' the whole tenant history is walked; a narrower range is cheaper but cannot judge a break exactly at its own edge, because the entry just before the range is not read.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AuditRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `after` | query | no | `string (date-time)` | — |
| `before` | query | no | `string (date-time)` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`AuditChainVerification`](/http-api/schemas/auditchainverification/) | — |

## DELETE {prefix}/api/data-subjects/{id}

**Operation ID:** `TraconEraseDataSubject`

**Deletes a data subject's content.**

'{id}' is resolved through IDataSubjectResolver; without one this returns 409, never a silent no-op that could be read as 'already erased'. '?dryRun=' DEFAULTS TO TRUE: a bare call previews the row counts and deletes nothing; '?dryRun=false' deletes for real. The audit trail (audit_log) is never touched — it is deliberately outside a data subject's erasable content (by design: an audit record is 'who did what', not the subject's own data) — but the erasure ITSELF is written there, with the row count per target; if that write fails, every delete is rolled back and this call fails, the same rule Approval decisions follow. A run of the subject that is still Running or Queued would write its session and checkpoints back after the erasure commits: '?dryRun=false' is refused with 409 that names those runs, and the preview lists them in 'activeRunIds'. '?force=true' erases anyway, for a row a crashed process left behind; cancel a run that is really running first.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `SecurityAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `id` | path | yes | `string` | — |
| `dryRun` | query | no | `boolean` | — |
| `force` | query | no | `boolean` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`DataSubjectErasureResult`](/http-api/schemas/datasubjecterasureresult/) | — |
| **409** Conflict | `application/problem+json` → [`ProblemDetails`](/http-api/schemas/problemdetails/) | — |

## GET {prefix}/api/data-subjects/{id}/export

**Operation ID:** `TraconExportDataSubject`

**Exports a data subject's content as one JSON document.**

'{id}' is resolved through IDataSubjectResolver, which is registered by the consumer — Tracon does not store personal identity. Without a registered resolver this returns 409, never an empty document. The document holds every column of every matching row, keyed by target table (session state, runs, run inputs, attachment METADATA only — no file bytes, voice session summaries, run scores, workflow checkpoints, conversations, conversation items, and responses); a target with no matching rows is present as an empty array, not omitted.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `SecurityAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `id` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | — | — |

## GET {prefix}/api/mcp-servers

**Operation ID:** `TraconListMcpServers`

**Lists registered remote MCP servers.**

No credential value is stored: 'headerConfigurationKeys' carries only the NAME of the configuration key each value is read from, and is returned as stored. Plain 'headers' are stored as sent and are returned with their NAMES only: every value is replaced with '***'.

**Authorization:** bearer authentication; `Reader` role policy when that policy is registered; `AgentsRead` API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`McpServerDefinition`](/http-api/schemas/mcpserverdefinition/) | — |

## PUT {prefix}/api/mcp-servers/{name}

**Operation ID:** `TraconSaveMcpServer`

**Adds or updates a remote MCP server.**

SECURITY BOUNDARY. Adding an MCP server means accepting tool definitions from an external source. Only http/https addresses are accepted; local process (stdio) transport is not supported. Tools require approval by default; saving 'requiresApproval: false' for a new server, for a server that required approval, or together with a new 'endpoint' also requires the 'SecurityAdmin' scope when the call uses an API key (403 otherwise). A configuration key name must be under the configured allowed prefix and inside the tenant's own key space — '{prefix}{tenantId}:...'; a flat name directly under the prefix belongs to the default tenant (400 otherwise). A credential header (Authorization, X-Api-Key, Cookie, any name ending in '-key') is declared in 'headerConfigurationKeys' as the NAME of the configuration key its value is read from; the same header in plain 'headers' is rejected with 400, because plain header values are stored as given. No response returns a plain header value: the saved record comes back with every one replaced by '***', and a header whose value is '***' is rejected with 400. A header name may appear only once across both maps (case-insensitive, 400 otherwise), and 'Authorization' in 'headerConfigurationKeys' cannot be combined with OAuth. A body that carries the removed field 'authorizationConfigurationKey' is rejected with 400 that names 'headerConfigurationKeys' as its replacement. 'headers' or 'headerConfigurationKeys' left out or null keeps the stored map; '{}' removes it. Every other field is replaced. Changing 'endpoint' without sending the maps keeps them, so the stored headers and the resolved credential values go to the NEW address.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AgentsAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |

**Request body** (required):

- `application/json` → [`McpServerRequest`](/http-api/schemas/mcpserverrequest/)

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`McpServerDefinition`](/http-api/schemas/mcpserverdefinition/) | — |

## DELETE {prefix}/api/mcp-servers/{name}

**Operation ID:** `TraconDeleteMcpServer`

**Deletes a remote MCP server.**

The registration is removed, so the tools it contributed stop being offered to agents. Agent definitions that name those tools are not rewritten and will fail validation on their next save — check which agents use the server before removing it. No request is made to the remote server itself. An unknown name returns 404.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AgentsAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **204** No Content | — | — |

## GET {prefix}/api/mcp-servers/{name}/oauth/callback

**Operation ID:** `TraconMcpOAuthCallback`

**Processes the OAuth provider's callback request.**

This endpoint is outside the access layers: the browser redirected by the provider cannot carry our bearer token. Security relies on the single-use 'state' value.

**Authorization:** anonymous.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |
| `code` | query | no | `string` | — |
| `state` | query | no | `string` | — |
| `iss` | query | no | `string` | — |
| `error` | query | no | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | — | — |

## POST {prefix}/api/mcp-servers/{name}/oauth/start

**Operation ID:** `TraconStartMcpOAuth`

**Starts the OAuth authorization flow for an MCP server.**

The administrator is redirected to the returned 'authorizationUri'. After approval, the provider redirects back to the '/oauth/callback' endpoint; that endpoint uses the 'state' value for CSRF protection.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `SecurityAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`McpOAuthStartResponse`](/http-api/schemas/mcpoauthstartresponse/) | — |

## GET {prefix}/api/mcp-servers/{name}/prompts

**Operation ID:** `TraconListMcpPrompts`

**Gets an MCP server's prompt list.**

If the server does not advertise the 'prompts' capability, the request is never sent.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AgentsRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`McpPromptSummary`](/http-api/schemas/mcppromptsummary/) | — |

## POST {prefix}/api/mcp-servers/{name}/prompts/{prompt}

**Operation ID:** `TraconGetMcpPrompt`

**Resolves an MCP prompt's content with arguments.**

The returned content is a SNAPSHOT: it must be copied into the agent's instructions; it is not re-fetched at runtime. The 'hash' field is for tracking changes on the server.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AgentsRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |
| `prompt` | path | yes | `string` | — |

**Request body** (optional):

- `application/json` → [`McpPromptArgumentsRequest`](/http-api/schemas/mcppromptargumentsrequest/)

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`McpPromptContent`](/http-api/schemas/mcppromptcontent/) | — |

## GET {prefix}/api/mcp-servers/{name}/resources

**Operation ID:** `TraconListMcpResources`

**Gets an MCP server's resource list.**

If the server does not advertise the 'resources' capability, the request is never sent.

**Authorization:** bearer authentication; `Reader` role policy when that policy is registered; `AgentsRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`McpResourceSummary`](/http-api/schemas/mcpresourcesummary/) | — |

## GET {prefix}/api/mcp-servers/{name}/resources/read

**Operation ID:** `TraconReadMcpResource`

**Reads an MCP resource.**

Only URIs advertised by the server's ListResourcesAsync are accepted; an arbitrary URI is rejected because it carries an SSRF risk.

**Authorization:** bearer authentication; `Operator` role policy when that policy is registered; `AgentsRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `name` | path | yes | `string` | — |
| `uri` | query | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`McpResourceContent`](/http-api/schemas/mcpresourcecontent/) | — |

## POST {prefix}/api/mcp-servers/refresh

**Operation ID:** `TraconRefreshMcpTools`

**Refreshes the tool list of remote MCP servers now.**

The refresh normally happens in the background at fixed intervals. This endpoint lets the tools of a newly added server appear without waiting for the next scheduled refresh.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `AgentsAdmin` API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`McpRefreshResponse`](/http-api/schemas/mcprefreshresponse/) | — |

## GET {prefix}/api/quotas

**Operation ID:** `TraconListQuotas`

**Lists a tenant's quota rules.**

Rules are definitions, not counters — read the counters from the usage endpoint. A rule with no agent name applies to the whole tenant, and a tenant-wide rule and an agent-specific rule can both be in force at once. A rule with 'enabled: false' is kept but not enforced. An agent with no matching rule is unlimited.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `PlatformRead` API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`QuotaDefinition`](/http-api/schemas/quotadefinition/) | — |

## PUT {prefix}/api/quotas

**Operation ID:** `TraconSaveQuota`

**Creates or updates a quota rule.**

The scope (tenant + agent + period) is unique: writing a second rule for the same scope overwrites the existing rule. Each limit can also be left empty; only the ones that are set are enforced.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `PlatformAdmin` API-key scope.

**Request body** (required):

- `application/json` → [`QuotaSaveRequest`](/http-api/schemas/quotasaverequest/)

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`QuotaDefinition`](/http-api/schemas/quotadefinition/) | — |

## DELETE {prefix}/api/quotas/{id}

**Operation ID:** `TraconDeleteQuota`

**Deletes a quota rule.**

Removing the last rule that covers an agent makes it unlimited, which is why the removal is written to the audit trail with the rule's previous values. Usage counters already recorded are not deleted; they simply stop being enforced. To keep the limits but stop enforcing them, save the rule with 'enabled: false' instead. An unknown id returns 404.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `PlatformAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `id` | path | yes | `string (uuid)` | — |

| Response | Body | Headers |
|---|---|---|
| **204** No Content | — | — |

## GET {prefix}/api/quotas/usage

**Operation ID:** `TraconGetQuotaUsage`

**Returns the current period's quota usage.**

An empty 'agentName' value shows the tenant-wide counter. Counters are approximate: the check happens before a run starts, and consumption is written after it finishes.

**Authorization:** bearer authentication; `Reader` role policy when that policy is registered; `PlatformRead` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `agentName` | query | no | `string` | — |
| `period` | query | no | [`QuotaPeriod`](/http-api/schemas/quotaperiod/) | — |

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`QuotaUsageResponse`](/http-api/schemas/quotausageresponse/) | — |

## GET {prefix}/api/skill-script-grants

**Operation ID:** `TraconListSkillScriptGrants`

**Lists the tenant's script run grants.**

A grant is permission to execute code on the server, so this list is the authoritative answer to 'what may run here'. A grant with no script name covers every script in that skill; one with a script name covers only that script. Entries may carry an expiry, and an expired grant no longer authorizes a run. Grants are also a retention target, so old ones are cleaned up.

**Authorization:** bearer authentication; `Reader` role policy when that policy is registered; `SecurityAdmin` API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`SkillScriptGrant`](/http-api/schemas/skillscriptgrant/) | — |

## POST {prefix}/api/skill-script-grants

**Operation ID:** `TraconGrantSkillScript`

**Grants run permission to a skill script.**

A grant for a skill stored in the database or registered in code pins the content it is given for: send 'expectedContentHash', read from GET /api/skills/{name} (scripts[].contentHash for one script, scriptSetHash when 'scriptName' is omitted and the grant covers every script). A missing or malformed hash returns 400, a 'scriptName' the skill does not carry 404, and a hash that no longer matches the content 409 - read the skill again and review it; the response never carries the current hash. A script whose content changes after the grant does not run until it is granted again. In a multi-tenant host a grant for a stored skill also needs platform authority (an API key with PlatformAdmin, the static token, or the Tracon.PlatformAdmin policy; 403 otherwise), because the script runs under the server's own operating-system identity. A name no stored or code skill carries is granted without a pin and authorizes scripts read from disk only. A key needs AgentsRead to read the hash and SecurityAdmin to grant. Granting while script execution is switched off returns 409 rather than succeeding: a grant that reads as active but never allows a run would be misleading. 'expiresAt' is optional but must be in the future when given (400 otherwise); without it the grant does not expire. A refused request writes nothing; every grant is written to the audit trail.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `SecurityAdmin` API-key scope.

**Request body** (required):

- `application/json` → [`SkillScriptGrantRequest`](/http-api/schemas/skillscriptgrantrequest/)

| Response | Body | Headers |
|---|---|---|
| **201** Created | `application/json` → [`SkillScriptGrant`](/http-api/schemas/skillscriptgrant/) | — |

## DELETE {prefix}/api/skill-script-grants/{skillName}

**Operation ID:** `TraconRevokeSkillScript`

**Revokes a script run grant.**

Revoking takes effect on the next run; a script already executing is not stopped. The optional '?scriptName=' must match how the grant was created — revoking one script does not remove a skill-wide grant, and the skill-wide grant keeps authorizing that script until it too is revoked. When no matching active grant exists the response is 404.

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `SecurityAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `skillName` | path | yes | `string` | — |
| `scriptName` | query | no | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **204** No Content | — | — |

## GET {prefix}/api/tenants

**Operation ID:** `TraconListTenants`

**Lists registered tenants.**

A tenant record is NOT REQUIRED. The tenant_id in other tables is the same text as this record's slug value, but it is not connected by a foreign key; a tenant with no record does not produce an error at runtime. The list names every tenant of the installation, so it requires platform authority (403 otherwise).

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `PlatformAdmin` API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → array of [`TenantDescriptor`](/http-api/schemas/tenantdescriptor/) | — |

## PUT {prefix}/api/tenants/{slug}

**Operation ID:** `TraconSaveTenant`

**Adds or updates a tenant record.**

The record is a display name for a tenant key that already works without it; creating one does not create the tenant and deleting one does not remove its data. The slug comes from the path and must be at most 64 characters of letters, digits, dots, underscores, and hyphens (400 otherwise) — it is the same text stored as 'tenant_id' on every other row, and it is folded to lower case for the same reason, so 'Acme' and 'acme' name one record. An empty display name falls back to the slug. A tenant other than the caller's own requires platform authority (403 otherwise).

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `PlatformAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `slug` | path | yes | `string` | — |

**Request body** (required):

- `application/json` → [`TenantRequest`](/http-api/schemas/tenantrequest/)

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`TenantDescriptor`](/http-api/schemas/tenantdescriptor/) | — |

## DELETE {prefix}/api/tenants/{slug}

**Operation ID:** `TraconDeleteTenant`

**Deletes a tenant record.**

Only the record is deleted; the tenant's agents, sessions, and runs remain. A tenant other than the caller's own requires platform authority (403 otherwise).

**Authorization:** bearer authentication; `Admin` role policy when that policy is registered; `PlatformAdmin` API-key scope.

| Parameter | In | Required | Type | Description and rules |
|---|---|---|---|---|
| `slug` | path | yes | `string` | — |

| Response | Body | Headers |
|---|---|---|
| **204** No Content | — | — |

## GET {prefix}/api/tenants/current

**Operation ID:** `TraconCurrentTenant`

**Returns the current request's tenant.**

The tenant is resolved from the request. In a single-tenant setup, it always returns the default tenant. This endpoint is in the protected group; /api/meta does not carry tenant information.

**Authorization:** bearer authentication; `Reader` role policy when that policy is registered; no endpoint-specific API-key scope.

| Response | Body | Headers |
|---|---|---|
| **200** OK | `application/json` → [`CurrentTenantResponse`](/http-api/schemas/currenttenantresponse/) | — |
