# gRPC API reference

Service `semantic_firewall.v1.FirewallService`. Default port `50051`, plaintext — the server does not terminate TLS itself; put it behind your own TLS-terminating proxy (or an mTLS mesh) if traffic leaves a trusted network.

Proto source: `protos/semantic_firewall/v1/firewall.proto`.

## When to use gRPC vs. REST

gRPC exposes the same core `Inspect` verdict shape as `POST /v1/inspect` with lower per-call overhead, which matters in a high-throughput, low-latency service mesh. It comes with real gaps relative to REST — see [Divergences from REST](#divergences-from-rest) — so:

- **Use REST** for anything that needs sessions, tenant/API-key scoping, the proxy, the tool registry, or the dashboard.
- **Use gRPC** for the lowest-latency `Inspect` call inside a trusted network where you don't need those features.

## RPCs

| RPC | Request | Response |
| --- | --- | --- |
| `Inspect` | `InspectRequest` | `Verdict` |
| `InspectAsync` | `InspectAsyncRequest` | `TaskStatus` |
| `Deanonymize` | `DeanonymizeRequest` | `DeanonymizeResponse` |
| `Health` | `HealthRequest` | `HealthResponse` |

### `Inspect`

Runs the same detection pipeline as `POST /v1/inspect` and returns a `Verdict`.

```protobuf
message InspectRequest {
  string input_text = 1;
  optional string system_goal = 2;
  optional string request_id = 3;
  map<string, string> metadata = 4;
}
```

| Field | Type | Notes |
| --- | --- | --- |
| `input_text` | `string` | The text to inspect. |
| `system_goal` | `optional string` | Drift-detection baseline. |
| `request_id` | `optional string` | Caller-supplied correlation id. |
| `metadata` | `map<string, string>` | Freeform. |

```protobuf
message Verdict {
  Action action = 1;
  float confidence = 2;
  repeated Issue issues = 3;
  optional string sanitized_input = 4;
  map<string, string> pii_mapping = 5;
  float processing_time_ms = 6;
}
```

| Field | Type | Notes |
| --- | --- | --- |
| `action` | `Action` | See enum below. |
| `confidence` | `float` | 0–1, agreement across channels. |
| `issues` | `repeated Issue` | |
| `sanitized_input` | `optional string` | PII-pseudonymized text; unset if none found. |
| `pii_mapping` | `map<string, string>` | `{pseudonym: original}`. |
| `processing_time_ms` | `float` | |

```protobuf
message Issue {
  IssueType type = 1;
  Severity severity = 2;
  string detail = 3;
  optional Span span = 4;
  optional string view = 5;
}

message Span {
  int32 start = 1;
  int32 end = 2;
}
```

`view` is set only for a finding surfaced by a normalized view of the input (e.g. `base64-decoded`); unset for the raw view, matching REST's `view: null`.

### `InspectAsync`

```protobuf
message InspectAsyncRequest {
  InspectRequest request = 1;
  string webhook_url = 2;
}

message TaskStatus {
  string task_id = 1;
  string status = 2;
}
```

**Current server behavior: this is a stub.** It mints a random `task_id`, returns `status: "processing"`, and does **not** dispatch the request or call `webhook_url`. Use `POST /v1/inspect/async` on REST for working async dispatch.

### `Deanonymize`

```protobuf
message DeanonymizeRequest {
  string text = 1;
  map<string, string> pii_mapping = 2;
}

message DeanonymizeResponse {
  string text = 1;
}
```

Replaces every pseudonym key found in `text` with its mapped original value — identical behavior to `POST /v1/deanonymize`.

### `Health`

```protobuf
message HealthRequest {}

message HealthResponse {
  string status = 1;
}
```

Always returns `status: "healthy"` once the server is up.

## Enums

### `Action`

| Value | Number |
| --- | --- |
| `ACTION_UNSPECIFIED` | 0 |
| `ACTION_ALLOW` | 1 |
| `ACTION_FLAG` | 2 |
| `ACTION_BLOCK` | 3 |

### `Severity`

| Value | Number |
| --- | --- |
| `SEVERITY_UNSPECIFIED` | 0 |
| `SEVERITY_LOW` | 1 |
| `SEVERITY_MEDIUM` | 2 |
| `SEVERITY_HIGH` | 3 |
| `SEVERITY_CRITICAL` | 4 |

### `IssueType`

| Value | Number |
| --- | --- |
| `ISSUE_TYPE_UNSPECIFIED` | 0 |
| `ISSUE_TYPE_PII` | 1 |
| `ISSUE_TYPE_INJECTION` | 2 |
| `ISSUE_TYPE_DRIFT` | 3 |

## Divergences from REST

The gRPC surface is intentionally smaller than REST. Concretely, as of this proto:

- **No `session_id` on `InspectRequest`.** There is no cross-turn injection-pressure tracking over gRPC — every `Inspect` call is judged in isolation.
- **No `dry_run` on `Verdict`.** The response never tells you whether monitor mode suppressed enforcement; `action` is always the real would-be decision, same as REST, but you can't distinguish "enforced" from "would have."
- **No `channel` on `Issue`.** You get `type`, `severity`, `detail`, `span`, and `view`, but not which detector (`heuristic`, `classifier`, `llm_judge`, …) fired.
- **No authentication or tenant scoping.** There is no API-key equivalent of `X-SFW-Key` on the gRPC surface, and `Inspect` calls the pipeline without a tenant or API-key id — every call runs against the same implicit, unscoped context. **Deploy the gRPC port only on a trusted network** (private VPC, service mesh, mTLS) — never expose it the way you would the authenticated REST surface.
- **`InspectAsync` is a stub** (see above) — it does not dispatch.

For the tool registry, the transparent proxy, per-key/tenant config, and metrics, there is no gRPC equivalent at all — those are REST-only.
