← Back to Skills

Janus Service Proxy

Route internal calls through Janus for gas metering and billing attribution.

Ruleconfigure

About

How to use transport: janus in catalog-info.yaml to meter internal service-to-service calls. Covers the difference between direct and janus transport, the X-Bill-Org header for charging end-user orgs instead of the pod owner, handling 402 Insufficient Gas responses, and when to use metered vs free transport.

Skill Content

This is the raw markdown that gets installed as a Claude Code rule.

# 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

Install

Copy the skill content and save it to:

~/.claude/rules/janus-service-proxy.md
Download .md

Coming soon via CLI:

tawa chaac install janus-service-proxy

Details

Format
Rule
Category
configure
Version
1.0.0
Tokens
~800
Updated
2026-03-01
janusgasmeteringbillinginternalproxytransport