Skip to content

Protect Your First Real API

In Get Started, your agent called an LLM provider through Caracal. In this tutorial you protect a service that matters to you - and, just as important, you watch Caracal refuse a request that policy does not allow. By the end you will have proven both halves of enforcement: the allow and the deny.

You will:

  1. pick one HTTP service the Gateway can reach;
  2. register it in Caracal as a resource named resource://pipernet;
  3. write one policy rule allowing your application to read it;
  4. prove the allowed call works end to end;
  5. prove a disallowed request is denied before it ever reaches your service.
  • Complete Add SDK to Your App.
  • Keep the stack running (caracal status --ready succeeds) and your caracal.toml profile from Get Started.

Choose one HTTP endpoint you own: an internal REST API, a staging service, or a route on a provider you use. Two things matter:

  • The Gateway must be able to reach it. The upstream URL is resolved from inside the Gateway’s container, so localhost there means the Gateway itself. Use a hostname or IP the Gateway can resolve - the same rule you met in First Protected Call.
  • Know whether it needs its own credential. In Get Started the LLM provider needed one, held by a Caracal provider. Internal services often accept any request. Note which kind yours is - it decides one form field in the next step.

No suitable service handy? Start another disposable container on the stack’s network and treat it as your “real” API; every step still teaches the same skills:

Terminal window
docker run --rm -d --name pipernetUpstream --network caracalData nginx:stable-alpine

Its Gateway-visible URL is http://pipernetUpstream:80.

After this step: you have one upstream URL and you know whether it needs a credential.

A resource is Caracal’s record of what it protects and where to forward verified requests - you created one in guided setup; now you create one from the everyday form. In the web console at http://localhost:3001, open Resources and create:

FieldValueWhy
Resource identifierresource://pipernetThe stable name everything else refers to: policy, SDK code, Gateway headers, and audit events.
Scopespipernet:readThe one named permission you will allow. Add more actions later, one at a time.
Upstream URLYour Gateway-reachable URL from step 1Where the Gateway forwards verified requests.
ProviderSee belowHow the Gateway attaches your service’s own credential, if it needs one.

You met providers in Get Started, where one held your LLM key: a provider is a credential source you configure once - the upstream’s API key or OAuth client - so the Gateway can attach that credential to verified requests on the way through. Your program never sees it.

  • If your service needs no credential (including the disposable container), choose the None provider: the Gateway still enforces policy and records audit, it just attaches nothing.
  • If your service needs a key or token, create a provider for it first and select it on the resource. Define Resources and Providers covers the provider forms; you can also start with None now and attach a provider later.

After this step: resource://pipernet appears in the Resources list. Nothing can call it yet - that is the point of the next step.

Caracal denies everything not explicitly allowed, so a new resource starts unreachable. Open Policies and add the rule allowing your existing application (Anton from Get Started) to request pipernet:read on resource://pipernet, then activate the change.

A good first rule names exactly four things - the application, the resource, the scopes, and the zone - and nothing more. Author Policy Data and Activate a Policy Set go deeper when you need real policy structure.

After this step: the active policy set includes your new rule. The Policies page shows which set is active.

Your SDK example from Get Started is already wired for this - it reads its target from environment variables. Point it at the new resource:

Terminal window
export CARACAL_CONFIG=/path/to/caracal.toml
export CARACAL_RESOURCE_ID=resource://pipernet
export CARACAL_RESOURCE_PATH=/
export CARACAL_RESOURCE_SCOPE=pipernet:read

Run the example. It should print your service’s response - the same code that reached the LLM provider now reaches your real API, because authority comes from configuration and policy, not from the code.

After this step: web console Audit shows two events for your request ID: the authorization decision and the Gateway’s action result.

Enforcement you have never seen fail is enforcement you cannot trust. Request a permission your policy does not allow:

Terminal window
export CARACAL_RESOURCE_SCOPE=pipernet:write

Run the example again. This time it fails: policy allows pipernet:read only, so Caracal’s token service refuses to issue a mandate for pipernet:write, and the request never reaches your service. The SDK surfaces the denial with a request ID.

Set the scope back to pipernet:read afterward.

After this step: Audit contains a deny decision with your request ID. Keep that ID - Trace One Protected Request dissects one just like it two steps from now.

The same SDK flow that reached the LLM provider now reaches your real API through the Gateway, and you have seen both outcomes in Audit: an allow with an action result, and a deny that stopped before the upstream. You did not change a line of code to switch targets - only configuration and policy.

  • Do not reuse resource://openai for a different target; each protected service gets its own identifier.
  • Do not put an upstream secret in application code; bind a credential provider to the resource instead.
  • Do not treat a successful direct call to the upstream as enforcement proof - only the Gateway path checks anything.

Continue with Make Runs Identifiable with Labels to make your app’s runs identifiable in the audit trail.