# Uploading & Accessing Secrets in Oracles \[Declare, upload, and read API keys inside Newton PolicyData oracles. JSON Schema declaration, HPKE-encrypted upload via SDK or CLI, and the newton:provider/secrets WASM host interface.]

PolicyData oracles often need an API key to fetch external data. Newton lets you store those secrets **encrypted** so they are never exposed on-chain, and decrypts them only inside the operator at evaluation time — your WASM oracle reads them through a host interface without ever handling the ciphertext.

This guide covers the full lifecycle: **declaring** which secrets an oracle needs, **uploading** them encrypted, and **accessing** them inside your oracle code. For the underlying encryption design, see [Encrypting Secrets](/developers/advanced/encrypting-secrets) and the [Privacy Layer](/developers/concepts/privacy-layer).

## 1. Declare the secrets schema

Each PolicyData oracle declares the secrets it requires as a JSON Schema, stored on-chain as the oracle's `secretsSchemaCid`. Uploaded secrets are validated against this schema before being stored.

Create `secrets_schema.json`:

```json
{
  "type": "object",
  "properties": {
    "VAULTS_FYI_API_KEY": { "type": "string", "minLength": 1 }
  },
  "required": ["VAULTS_FYI_API_KEY"],
  "additionalProperties": false
}
```

Pass it when generating CIDs so it is published with the oracle:

```bash
newton-cli policy-files generate-cids \
  -d ./dist \
  --entrypoint my_policy.allow \
  --secrets-schema-file ./secrets_schema.json \
  -o ./dist/policy_cids.json
```

An oracle that needs no secrets simply omits `secretsSchemaCid` (drop `--secrets-schema-file`).

## 2. Upload secrets (encrypted)

Secrets are encrypted **client-side** with HPKE before they ever leave your machine. The operator decrypts them in memory at evaluation time; the plaintext is never persisted and never sent over the network. Only the on-chain owner of the PolicyClient can upload secrets.

:::warning
Secrets are scoped **per `policy_data_address`**. If you redeploy a PolicyData contract you get a new address, so you must re-upload its secrets. In a multi-oracle policy, upload each oracle's secrets to its own PolicyData address.
:::

:::code-group
```bash [CLI]
# 1. Create a secrets file matching the oracle's schema
cat > secrets.json <<'EOF'
{ "VAULTS_FYI_API_KEY": "sk-prod-xxxxxxxxxxxx" }
EOF

# 2. Upload — the CLI fetches the HPKE key, encrypts locally, and uploads
newton-cli --chain-id 11155111 secrets upload \
  --secrets-file secrets.json \
  --policy-client 0xYourPolicyClient \
  --policy-data-address 0xYourPolicyData \
  --api-key $NEWTON_API_KEY
```

```ts twoslash
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia } from 'viem/chains'
import { newtonWalletClientActions } from '@newton-xyz/sdk'
// ---cut---
async function storeSecrets() {
  const client = createWalletClient({
    account: privateKeyToAccount('0x' + '1'.repeat(64) as `0x${string}`),
    chain: sepolia,
    transport: http(),
  }).extend(newtonWalletClientActions({
    apiKey: 'test-api-key',
    policyContractAddress: '0x1234567890123456789012345678901234567890',
  }))

  const result = await client.storeEncryptedSecrets({
    policyClient: '0x1234567890123456789012345678901234567890' as `0x${string}`,
    policyDataAddress: '0x2222222222222222222222222222222222222222' as `0x${string}`,
    plaintext: { VAULTS_FYI_API_KEY: 'sk-prod-xxxxxxxxxxxx' },
    chainId: 11155111,
    // recipientPublicKey is fetched automatically if omitted
  })

  if (!result.success) throw new Error(`Upload failed: ${result.error}`)
}
```
:::

Both paths fetch the operator's HPKE public key via `newt_getSecretsPublicKey`, seal the envelope locally, and submit it via `newt_storeEncryptedSecrets`. The operator validates the plaintext against your `secretsSchemaCid` and stores only the encrypted envelope.

## 3. Access secrets inside the oracle

Your WASM oracle reads secrets through the `newton:provider/secrets` host interface (declared in `newton-provider.wit`). Calling `get()` returns the decrypted secrets JSON as raw bytes — the operator has already decrypted them; you only decode and parse:

```js
import { fetch as httpFetch } from "newton:provider/http@0.2.0";
import { get as getHostSecrets } from "newton:provider/secrets@0.2.0";

let _secrets = {};

// Load host-provided secrets once, at the start of run().
function loadHostSecrets() {
  try {
    const r = getHostSecrets();
    const resp = r?.val ?? r;       // unwrap result<secret-response, string>
    const bytes = resp?.value;      // value: list<u8> — decrypted secrets JSON
    if (!bytes || bytes.length === 0) return;
    const text = new TextDecoder().decode(new Uint8Array(bytes));
    const parsed = JSON.parse(text);
    if (parsed && typeof parsed === "object") {
      _secrets = { ..._secrets, ...parsed };
    }
  } catch (_) {
    // Host secrets unavailable (e.g. local sim without uploaded secrets).
  }
}

function secret(name) {
  return _secrets[name];
}

function getJson(url) {
  const apiKey = secret("VAULTS_FYI_API_KEY");
  const r = httpFetch({
    url,
    method: "GET",
    headers: [["accept", "application/json"], ["x-api-key", apiKey]],
    body: null,
  });
  if (r.tag === "err") throw new Error(`http: ${r.val}`);
  const resp = r.val ?? r;
  if (resp.status < 200 || resp.status >= 300) {
    throw new Error(`http ${resp.status}`);
  }
  return JSON.parse(new TextDecoder().decode(new Uint8Array(resp.body)));
}

export function run(input) {
  try {
    loadHostSecrets();
    const args = JSON.parse(input);
    const data = getJson(`https://api.example.com/v2/${args.id}`);
    // wrap output under your pack id so it composes with other oracles
    return JSON.stringify({ my_oracle: { value: data.value } });
  } catch (e) {
    return JSON.stringify({ my_oracle: { error: String(e) } });
  }
}
```

Notes:

* `get()` returns a `result<secret-response, string>`; `secret-response.value` is `list<u8>` — the bytes of the decrypted secrets JSON object. The scope (which PolicyClient + PolicyData) is bound by the operator's execution context, so you do not pass any identifiers.
* The named keys you read (`VAULTS_FYI_API_KEY` above) must match the keys you uploaded and the properties in `secrets_schema.json`.
* Put credentials in request headers, never in the URL.
* Fail closed: if a fetch errors, return a namespaced `error` so your Rego can deny on it (see [Chaining Data Oracles](/developers/guides/chaining-data-oracles#writing-rego-that-consumes-two-oracles)).

This is the exact pattern the [Newton policy packs](/developers/guides/policy-packs) use. See the [Writing Data Oracles](/developers/guides/writing-data-oracles) guide for the full WIT interface and build steps.

## 4. Test that secrets resolve

Before submitting production tasks, verify the oracle reads its secrets correctly:

| Step | Method | Purpose |
|------|--------|---------|
| Iterate without uploading | `newt_simulatePolicyData` | Provide secrets inline; no ownership required |
| Verify stored secrets | `newt_simulatePolicyDataWithClient` | Runs the oracle with the secrets you uploaded (requires PolicyClient ownership) |

```bash
# Verify stored secrets work for a deployed PolicyData
curl -X POST https://gateway.testnet.newton.xyz/rpc \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $NEWTON_API_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "method": "newt_simulatePolicyDataWithClient",
    "params": {
      "policy_data_address": "0xYourPolicyData",
      "policy_client": "0xYourPolicyClient",
      "wasm_args": "0x7b226964223a2231323334227d"
    },
    "id": "7ca6621b-7aa4-4bb7-a896-1f2b58a18c78"
  }'
```

See [Testing Policies & Oracles](/developers/guides/testing-policies) for the full simulation workflow.

## Next Steps

<Cards>
  <Card icon="lock" to="/developers/advanced/encrypting-secrets" title="Encrypting Secrets">
    The HPKE wire protocol and security model
  </Card>

  <Card icon="link" to="/developers/guides/chaining-data-oracles" title="Chaining Data Oracles">
    Per-oracle secret scoping in multi-oracle policies
  </Card>

  <Card icon="cubes" to="/developers/guides/policy-packs" title="Policy Packs">
    Prebuilt oracles and the keys each one needs
  </Card>

  <Card icon="terminal" to="/developers/reference/command-line-tool" title="CLI Reference">
    newton-cli secrets commands and flags
  </Card>
</Cards>
