# Workload identity for the Reducto API

> Call Reducto with short-lived tokens from your existing identity provider instead of a long-lived API key.

Canonical: https://reducto.ai/blog/workload-identity-reducto-api

<Html html={"<style>.wif .fig{margin:32px 0;background:#fff;border:1px solid #e5e5e5;border-radius:8px;padding:28px;box-shadow:0px 1px 56px 0px rgba(12, 10, 9, 0.04)}.wif .fig figcaption{font-size:13px;color:#737373;margin-top:18px}.wif .fig code{background:#f5f5f5}.wif .cmp-row{display:grid;grid-template-columns:130px 1fr;align-items:center;gap:16px;margin:14px 0}.wif .cmp-label{font-size:13px;font-weight:500}.wif .track{position:relative;height:28px;background:#f5f5f5;border-radius:4px;overflow:hidden}.wif .bar.key{position:absolute;inset:0;background:#f3e1df;border-left:3px solid #b33c41;display:flex;align-items:center;padding-left:10px;font:12px \"Berkeley Mono\", ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;color:#7a2a2d;animation:grow 2.4s ease-out both}@keyframes grow{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}.wif .tok{position:absolute;top:4px;width:6.5%;height:20px;border-radius:3px;background:#e4efe9;border-left:3px solid #46a579;opacity:0;animation:pop 6s ease-out infinite}@keyframes pop{0%{opacity:0;transform:scaleX(.4)}6%{opacity:1;transform:none}70%{opacity:1}100%{opacity:.35}}.wif .axis{display:flex;justify-content:space-between;margin-left:146px;font:11px \"Berkeley Mono\", ui-monospace, \"SF Mono\", Menlo, Consolas, monospace;color:#a3a3a3}.wif .flow{position:relative;height:250px}.wif .node{position:absolute;top:0;width:22%;transform:translateX(-50%);border:1px solid #e5e5e5;border-radius:6px;padding:12px;background:#fafafa;font-size:12px;animation:glow 8s infinite;box-sizing:border-box;height:112px;display:flex;flex-direction:column}.wif .node .nk{font-weight:500;font-size:13px;margin-bottom:4px}.wif .node .nv{color:#525252;line-height:1.45;word-break:break-all}.wif .n1{left:12%}.wif .n2{left:37.5%}.wif .n3{left:62.5%;border-color:#d9c3d3}.wif .n4{left:88%}.wif .n1{animation-name:g1}.wif .n2{animation-name:g2}.wif .n3{animation-name:g3}.wif .n4{animation-name:g4}@keyframes g1{0%,100%{box-shadow:none}20%,30%,45%,55%,70%,80%{box-shadow:0 0 0 2px #8a3d76}}@keyframes g2{0%,20%{box-shadow:0 0 0 2px #8a3d76}25%,100%{box-shadow:none}}@keyframes g3{30%,45%{box-shadow:0 0 0 2px #8a3d76}0%,25%,50%,100%{box-shadow:none}}@keyframes g4{80%,96%{box-shadow:0 0 0 2px #8a3d76}0%,75%,100%{box-shadow:none}}.wif .pkt{position:absolute;top:128px;transform:translateX(-50%);background:#4b163d;color:#fff;border-radius:4px;padding:5px 9px;font-size:11px;white-space:nowrap;opacity:0;animation:8s linear infinite}.wif .pkt code{background:none;color:#fff;padding:0}.wif .p3{background:#46a579}.wif .p4{background:#171717}.wif .p1{animation-name:m1}.wif .p2{animation-name:m2}.wif .p3{animation-name:m3}.wif .p4{animation-name:m4}@keyframes m1{0%{left:37.5%;opacity:0}3%{opacity:1}22%{left:12%;opacity:1}25%,100%{left:12%;opacity:0}}@keyframes m2{0%,25%{left:12%;opacity:0}28%{opacity:1}47%{left:62.5%;opacity:1}50%,100%{left:62.5%;opacity:0}}@keyframes m3{0%,50%{left:62.5%;opacity:0}53%{opacity:1}72%{left:12%;opacity:1}75%,100%{left:12%;opacity:0}}@keyframes m4{0%,75%{left:12%;opacity:0}78%{opacity:1}96%{left:88%;opacity:1}100%{left:88%;opacity:0}}.wif .steps{position:absolute;bottom:0;left:0;right:0;display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.wif .s{font-size:12px;color:#a3a3a3;border-top:2px solid #e5e5e5;padding-top:8px;animation:8s infinite}.wif .s1{animation-name:h1}.wif .s2{animation-name:h2}.wif .s3{animation-name:h3}.wif .s4{animation-name:h4}@keyframes h1{0%,24%{color:#171717;border-color:#8a3d76}25%,100%{color:#a3a3a3;border-color:#e5e5e5}}@keyframes h2{0%,24%,50%,100%{color:#a3a3a3;border-color:#e5e5e5}25%,49%{color:#171717;border-color:#8a3d76}}@keyframes h3{0%,49%,75%,100%{color:#a3a3a3;border-color:#e5e5e5}50%,74%{color:#171717;border-color:#8a3d76}}@keyframes h4{0%,74%{color:#a3a3a3;border-color:#e5e5e5}75%,100%{color:#171717;border-color:#8a3d76}}.wif .mrow{display:grid;grid-template-columns:180px 1fr 200px;align-items:center;padding:12px 0;border-bottom:1px solid #f0f0f0;font-size:14px}.wif .mname{font-weight:500}.wif .mprov{color:#525252;font-size:13px}.wif .mstate{display:flex;gap:8px;align-items:center;font:12px \"Berkeley Mono\", ui-monospace, \"SF Mono\", Menlo, Consolas, monospace}.wif .old{color:#b33c41;text-decoration:line-through;animation:inherit}.wif .new{background:#e4efe9;color:#2c6b4e;border-radius:3px;padding:2px 6px}.wif .mrow .new,.wif .mrow .arrow{opacity:0;animation:fadein .5s forwards;animation-delay:inherit}.wif .mrow{animation:none}@keyframes fadein{to{opacity:1}}.wif .arrow{color:#a3a3a3}.wif .app{display:grid;grid-template-columns:150px 1fr;border:1px solid #e5e5e5;border-radius:6px;overflow:hidden;font-size:13px;min-height:420px}.wif .side{background:#fafafa;border-right:1px solid #e5e5e5;padding:14px 10px}.wif .logo{font-family:\"Reducto Serif\", \"Source Serif Pro\", Georgia, serif;font-size:18px;color:#4b163d;margin:0 6px 16px}.wif .si{padding:6px 8px;border-radius:4px;color:#525252;margin:2px 0}.wif .si.on{background:#fff;color:#171717;font-weight:500;box-shadow:0 0 0 1px #e5e5e5}.wif .main{padding:18px 22px}.wif .crumb{font-size:12px;color:#a3a3a3}.wif .h{font-size:18px;font-weight:500;margin:4px 0 12px}.wif .kv{display:grid;grid-template-columns:100px 1fr;margin:4px 0;color:#737373;font-size:12px}.wif .sub{font-weight:500;margin:18px 0 8px}.wif table{width:100%;border-collapse:collapse;font-size:12px}.wif th{text-align:left;color:#737373;font-weight:400;border-bottom:1px solid #e5e5e5;padding:6px 4px}.wif td{padding:8px 4px;border-bottom:1px solid #f0f0f0;vertical-align:top;line-height:1.7}.wif .pill{font-size:11px;padding:2px 8px;border-radius:10px}.wif .pill.on{background:#e4efe9;color:#2c6b4e}.wif .tok-in{border:1px solid #e5e5e5;border-radius:4px;padding:8px;background:#fafafa;font-size:11px}.wif .res{margin-top:10px;display:flex;gap:8px;align-items:center;font-size:12px;opacity:0;animation:fadein .6s 1.2s forwards}.wif .dot{width:8px;height:8px;border-radius:50%;background:#46a579}@media (prefers-reduced-motion:reduce){.wif *{animation-duration:0s!important;animation-iteration-count:1!important}}.wif .node{height:124px;padding:10px}.wif .node .nv{word-break:normal;overflow-wrap:anywhere;font-size:11px}.wif .node code{font-size:10.5px;padding:0 2px}.wif .pkt{top:140px}.wif .s{font-size:11px;line-height:1.4}@media (max-width:640px){.wif .fig{padding:16px}.wif .cmp-row{grid-template-columns:64px 1fr;gap:8px}.wif .axis{margin-left:72px}.wif .mrow{grid-template-columns:1fr auto;row-gap:4px}.wif .mprov{grid-column:1/-1;grid-row:2}.wif .mstate{font-size:11px}.wif .node{width:24%;padding:6px;height:150px}.wif .node .nk{font-size:11px}.wif .node .nv{font-size:9.5px}.wif .flow .node .nv code{font-size:9px;padding:0;background:none;word-break:break-all}.wif .flow .node .nv{line-height:1.3}.wif .pkt{top:166px}.wif .flow{height:280px}.wif .s{font-size:10px}}</style>"} />

Our customers send us loan files, medical records, contracts and tax returns, so we spend a lot of our engineering time on security. We don't use long-lived API keys to call AI models inside Reducto, and now our customers don't need one to call us either, with Workload Identity.

Here's why API keys became a target, what replaces them, and how to use it with Reducto.

## A rise in distillation attacks

In February Anthropic [wrote](https://www.anthropic.com/news/detecting-and-preventing-distillation-attacks) about three AI labs that tried to distill Claude, meaning they trained their own models on its outputs. They ran over 16 million exchanges through about 24,000 fraudulent accounts, a lot of it through proxy services that resell access to frontier models.

Fake accounts get banned, so the next step was obvious: steal real keys from companies that already have high rate limits. October was rough for developer platforms.

-   [OpenCode](https://techcrunch.com/2026/10/19/hackers-hit-ai-coding-tool-opencode-running-up-charges-on-users-ai-accounts/), the open source coding agent, said attackers got into an older system and reached the OpenAI, Anthropic and Google keys that 1.4% of its Console users had stored there. OpenCode tied the breach to "a large-scale distillation attack."
    
-   Days later [Railway](https://techcrunch.com/2026/10/22/developer-platform-railway-urges-all-users-to-revoke-secret-keys-after-cyberattack/) said an attacker may have decrypted some customers' environment variables, and told every user to rotate their keys.
    

The pattern is clear. A high-limit OpenAI or Anthropic key used to be a billing problem if it leaked, and now people actively hunt for it. Startups are an easy place to find them because their keys have high limits and are spread across laptops, CI and a dozen services.

The simplest defense is not to hold keys at all.

## What's wrong with API keys

An API key is like a password for a machine. It never expires unless someone remembers to rotate it, so a key pasted into a CI secret in 2023 probably still works. It's a bearer secret, which means whoever has it is you, and it can't tell which machine is using it. And it gets copied into env files, CI settings, Slack messages and logs, each of which is one more place it can leak from.

Rotating keys helps, but you're still storing a secret, just a newer one.

<Html html={"<div class=\"wif\"><figure class=\"fig\" id=\"fig-compare\"><div class=\"cmp\"><div class=\"cmp-row\"><div class=\"cmp-label\">API key</div><div class=\"track\"><div class=\"bar key\"><span>rd_live_8f2c\u2026 valid until someone deletes it</span></div></div></div><div class=\"cmp-row\"><div class=\"cmp-label\">Federated token</div><div class=\"track\"><div class=\"tok\" style=\"left:0%;animation-delay:0.0s\"></div><div class=\"tok\" style=\"left:8%;animation-delay:0.2s\"></div><div class=\"tok\" style=\"left:16%;animation-delay:0.5s\"></div><div class=\"tok\" style=\"left:25%;animation-delay:0.8s\"></div><div class=\"tok\" style=\"left:33%;animation-delay:1.0s\"></div><div class=\"tok\" style=\"left:42%;animation-delay:1.2s\"></div><div class=\"tok\" style=\"left:50%;animation-delay:1.5s\"></div><div class=\"tok\" style=\"left:58%;animation-delay:1.8s\"></div><div class=\"tok\" style=\"left:67%;animation-delay:2.0s\"></div><div class=\"tok\" style=\"left:75%;animation-delay:2.2s\"></div><div class=\"tok\" style=\"left:84%;animation-delay:2.5s\"></div><div class=\"tok\" style=\"left:92%;animation-delay:2.8s\"></div></div></div><div class=\"axis\"><span>day 1</span><span>month 6</span><span>year 2</span></div></div><figcaption>A key is one secret that lives for years. Federation mints a new one-hour token each time, from an identity that never leaves the platform.</figcaption></figure></div>"} />

## OIDC and workload identity

Almost always, your code already runs somewhere that knows who it is. A GitHub Actions job knows its repo and branch, and a Kubernetes pod knows its service account. AWS roles, Azure managed identities and Google service accounts know who they are too.

These platforms can prove it with an **OIDC token**, a short JSON Web Token that the platform signs. It says who the workload is (`sub`), who issued the token (`iss`), who it's for (`aud`) and when it expires (`exp`), usually within an hour. Anyone can check the signature against the issuer's public keys.

**Workload identity federation** builds on this. The workload hands its platform token to the service it wants to call. The service checks the signature and the claims, matches them against rules you set, and gives back its own short-lived token. Nobody stores a long-lived secret.

<Html html={"<div class=\"wif\"><figure class=\"fig\" id=\"fig-exchange\"><div class=\"flow\"><div class=\"node n1\"><div class=\"nk\">Workload</div><div class=\"nv\">GitHub Actions job<br><code>acme/payments@main</code></div></div><div class=\"node n2\"><div class=\"nk\">Platform issuer</div><div class=\"nv\"><code>token.actions.githubusercontent.com</code></div></div><div class=\"node n3\"><div class=\"nk\">Reducto</div><div class=\"nv\"><code>POST /oauth/token</code><br>check signature, claims, mapping</div></div><div class=\"node n4\"><div class=\"nk\">Reducto API</div><div class=\"nv\"><code>/parse</code> <code>/extract</code></div></div><div class=\"pkt p1\"><code>id_token</code> \u00b7 5 min</div><div class=\"pkt p2\"><code>id_token</code> \u2192 exchange</div><div class=\"pkt p3\"><code>rdat_\u2026</code> \u00b7 \u2264 1 h</div><div class=\"pkt p4\"><code>Bearer rdat_\u2026</code></div><div class=\"steps\"><span class=\"s s1\">1 Ask the platform who I am</span><span class=\"s s2\">2 Trade that for a Reducto token</span><span class=\"s s3\">3 Reducto checks the rules</span><span class=\"s s4\">4 Call the API. No key stored.</span></div></div><figcaption>The exchange. The workload never holds a long-lived secret.</figcaption></figure></div>"} />

Here is a trimmed token from a GitHub Actions job:

```json
{
  "iss": "https://token.actions.githubusercontent.com",
  "aud": "https://platform.reducto.ai",
  "sub": "repo:acme/payments:ref:refs/heads/main",
  "repository": "acme/payments",
  "ref": "refs/heads/main",
  "exp": 1793322545
}
```

If your rule says `sub` must start with `repo:acme/payments:` and `ref` must be `refs/heads/main`, only the main branch of that repo can get a token. Forks, feature branches and anyone holding a copied config file get nothing.

**X.509** does the same thing for workloads that have certificates instead of tokens. A lot of companies run their own certificate authority, a service mesh or SPIFFE, and those workloads already have a client certificate and a private key that stays on the machine. The workload proves who it is during the TLS handshake (mutual TLS), and we bind the token we issue to that certificate. A stolen token is useless without the private key.

## How we use it at Reducto

Due to the nature of the sensitive data we process, security has always been a critical part of our operations at Reducto. A few things we support:

-   **Hybrid VPC.** Your documents and results stay in your own bucket (S3, Azure Blob, Google Cloud Storage or Box) while we run the GPUs. On AWS we reach your bucket through a cross-account role with an external ID, and on Azure through a managed identity in your tenant, so we never store a storage secret.
    
-   **Least privilege.** Each service, GPU worker and CI job gets its own identity with only the permissions it needs. Our CI jobs read secrets through OIDC instead of storing them in GitHub.
    
-   **The basics, audited.** SOC 2 Type II, HIPAA with a BAA, AES-256 at rest, TLS 1.2+ in transit.
    

We use the same federation for our own AI credentials. Our API pods, GPU workers, agent service and CI jobs get short-lived tokens for Anthropic, OpenAI and OpenRouter from their own platform identity.

<Html html={"<div class=\"wif\"><figure class=\"fig\" id=\"fig-migration\"><div class=\"mig\"><div class=\"mrow\" style=\"animation-delay:0.4s\"><div class=\"mname\">API pods</div><div class=\"mprov\">Anthropic \u00b7 OpenAI \u00b7 OpenRouter</div><div class=\"mstate\"><span class=\"new\">OIDC</span><span class=\"arrow\">\u2192</span><span class=\"new\">short-lived token</span></div></div><div class=\"mrow\" style=\"animation-delay:0.9s\"><div class=\"mname\">GPU workers (Modal)</div><div class=\"mprov\">OpenAI \u00b7 Anthropic</div><div class=\"mstate\"><span class=\"new\">OIDC</span><span class=\"arrow\">\u2192</span><span class=\"new\">short-lived token</span></div></div><div class=\"mrow\" style=\"animation-delay:1.4s\"><div class=\"mname\">Agent service</div><div class=\"mprov\">OpenAI \u00b7 OpenRouter</div><div class=\"mstate\"><span class=\"new\">OIDC</span><span class=\"arrow\">\u2192</span><span class=\"new\">short-lived token</span></div></div><div class=\"mrow\" style=\"animation-delay:1.9s\"><div class=\"mname\">CI (GitHub Actions)</div><div class=\"mprov\">Anthropic \u00b7 OpenAI \u00b7 Doppler</div><div class=\"mstate\"><span class=\"new\">OIDC</span><span class=\"arrow\">\u2192</span><span class=\"new\">short-lived token</span></div></div></div><figcaption>Where our model credentials come from.</figcaption></figure></div>"} />

Each service has to get a few things right: refresh the token before it expires, run only one exchange at a time, and fail cleanly instead of falling back to a key. A leaked config file, laptop or CI log from Reducto has nothing in it that can call a model.

## Doing the same for our customers

Our customers have security teams that are trying to get rid of long-lived keys too, and a Reducto API key sitting in their CI secrets is one more item on that list. We'd like to be the vendor they can cross off quickly.

So Reducto now supports workload identity federation directly:

-   **Your existing identity provider.** GitHub Actions, Kubernetes, AWS, Azure, Google Cloud, Oracle Cloud, SPIFFE, or any issuer that signs standard JWTs.
    
-   **Rules in Studio.** Add an identity provider, then add mappings on the token's claims. You can paste a real token to test your rules before you ship.
    
-   **Short-lived tokens.** Reducto returns an access token that lasts an hour or less. If you disable a mapping, its tokens stop working within 60 seconds.
    
-   **X.509 and mTLS.** Upload your CA as a trust anchor. A token issued from a certificate only works alongside that certificate.
    

<Html html={"<div class=\"wif\"><figure class=\"fig\" id=\"fig-studio\"><img src=\"https://cdn.reducto.ai/blogs/workload-identity/fig-studio.webp\" alt=\"Workload identity settings in Reducto Studio\" style=\"width:100%;display:block;border-radius:8px\"><figcaption>Setting up a GitHub Actions provider and its mappings in Studio.</figcaption></figure></div>"} />

The SDKs do most of the work. The Python and Node SDKs exchange, cache and refresh tokens for you, and they ship token providers for GitHub Actions, Kubernetes (EKS, GKE, AKS or self-managed), AWS, Azure managed identity and Google Cloud. Picking one is usually a single line:

```python
from reducto import Reducto
from reducto.auth import github_actions_token_provider

client = Reducto(
    workload_identity={
        "identity_provider_id": "wip_...",
        "provider": github_actions_token_provider("https://platform.reducto.ai"),
    }
)
```

If your tokens come from somewhere else, you write a small provider with a `get_token` function and the SDK calls it whenever it needs a fresh token. Our Oracle Cloud guide has an example.

Workload identity federation is available to every Reducto organization today. Once it's set up, you can delete your Reducto API key.
