# Janus Service Proxy — Gas-Metered Internal Calls

## What this skill covers
How to route internal service-to-service calls through Janus for gas metering, billing attribution, and the `X-Bill-Org` pass-through pattern.

## What It Is

By default, internal dependency calls are free — they go directly with no Janus involvement. Adding `transport: janus` to a dependency routes calls through Janus's `/proxy/{serviceName}` endpoint, which:

1. Checks the caller's gas balance before forwarding
2. Forwards the request to the target service
3. Debits 1 gas token from the caller on a 2xx response

## catalog-info.yaml

```yaml
spec:
  internalDependencies:
    - service: docman              # free, no metering (default)
    - service: docman
      transport: janus             # 1 token per successful call

  dependencies:
    - service: raterspot
      transport: janus             # metered + URL injected
      scopes: [raterspot:rate]
```

## What URL Gets Injected

| Transport | Injected URL format |
|-----------|-------------------|
| `direct` (default) | Platform-injected internal URL |
| `janus` | Janus proxy URL — `http://janus.janus-{env}.svc.cluster.local:3000/proxy/{service}` |

Your application code is identical either way — just the injected URL value changes.

## Billing Attribution

By default gas is charged to the **pod owner's org**. To charge the **end user's org** instead, send `X-Bill-Org`:

```typescript
const response = await fetch(`${process.env.DOCMAN_URL}/api/documents`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Bill-Org': req.user.orgSlug,   // bill the calling user's org
  },
  body: JSON.stringify(payload),
})
```

Priority order Janus uses:
1. `X-Bill-Org` header (if present)
2. `programId` from the service JWT (pod owner's org)
3. `serviceId` (fallback)

## Handling Insufficient Gas (402)

```typescript
const res = await fetch(`${process.env.DOCMAN_URL}/api/documents`, {
  headers: { 'X-Bill-Org': userOrgSlug },
})

if (res.status === 402) {
  return reply.status(402).json({
    error: 'BILLING_REQUIRED',
    message: 'Your organization has insufficient gas tokens.',
  })
}
```

## Headers Janus Adds to Proxied Requests

| Header | Value |
|--------|-------|
| `X-Request-ID` | Janus request ID for tracing |
| `X-Janus-Proxy` | `"true"` |
| `X-Caller-Org` | The billing org ID |

## When to Use janus Transport

| Use `transport: janus` when... | Use `transport: direct` when... |
|--------------------------------|--------------------------------|
| You want to meter internal calls | Calls are infrastructure/free |
| The target is a billable third-party plugin | The target is a platform service (septor, relay, etc.) |
| You need to pass costs through to user orgs | Gas attribution doesn't matter |
| Building a marketplace or multi-tenant product | Internal processing for your own service |

## Key Facts
- **1 token** per successful 2xx call — non-2xx and timeouts are not charged
- Service existence cached 60 seconds — new services go live within 1 minute of Koko registration
- Gas check fails open — if wallet is unreachable, proxy proceeds (avoids blocking services)
- `port:` field is ignored when `transport: janus` — Janus always runs on port 3000

## Common Mistakes
- Using `transport: janus` for platform services (septor, relay, iec-wallet) — these are free infrastructure
- Forgetting to handle 402 in code — end users will see unformatted errors
- Using `transport: gateway` when you need a URL env var — gateway does NOT inject a URL
- Expecting `X-Bill-Org` to work with `transport: direct` — billing attribution requires Janus
