# Credential Sources

Credential sources store the secret material that later gets projected into outbound auth flows.

Sources are created once per team and then referenced by `sourceRef` from sandbox or template network policy.

The API surface is:

| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/api/v1/credential-sources` | List sources |
| `POST` | `/api/v1/credential-sources` | Create a source |
| `GET` | `/api/v1/credential-sources/{name}` | Get source metadata |
| `PUT` | `/api/v1/credential-sources/{name}` | Replace a source |
| `DELETE` | `/api/v1/credential-sources/{name}` | Delete a source |

<Callout variant="info">
Source specs are write-only. Read APIs return metadata such as `name`, `resolverKind`, `currentVersion`, and timestamps, but not the raw secret values.
</Callout>

<Callout variant="info">
CLI examples assume you already ran `s0 auth login` and selected a current team with `s0 team use <team-id>`.
</Callout>

## Resolver Kinds

| Resolver kind | Key spec fields | Typical use |
|--------------|-----------------|-------------|
| `static_headers` | `spec.staticHeaders.values` | Bearer tokens and header fragments |
| `static_tls_client_certificate` | `spec.staticTLSClientCertificate.certificatePem`, `privateKeyPem`, optional `caPem` | mTLS client authentication |
| `static_username_password` | `spec.staticUsernamePassword.username`, `password` | Username/password based outbound auth |
| `static_ssh_private_key` | `spec.staticSSHPrivateKey.privateKeyPem`, optional `passphrase` | SSH transparent proxy upstream authentication |

## Create A Source

<Endpoint method="POST">
/api/v1/credential-sources
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `source, err := client.CreateCredentialSource(ctx, apispec.CredentialSourceWriteRequest{
    Name: "github-source",
    ResolverKind: apispec.CredentialSourceResolverKindStaticHeaders,
    Spec: apispec.CredentialSourceWriteSpec{
        StaticHeaders: apispec.NewOptStaticHeadersSourceSpec(apispec.StaticHeadersSourceSpec{
            Values: apispec.NewOptStaticHeadersSourceSpecValues(
                apispec.StaticHeadersSourceSpecValues{
                    "token": os.Getenv("GITHUB_TOKEN"),
                },
            ),
        }),
    },
})
if err != nil {
    log.Fatal(err)
}
fmt.Println(source.Name)`
    },
    {
      label: "Python",
      language: "python",
      code: `import os
from sandbox0.apispec.models.credential_source_write_request import CredentialSourceWriteRequest

source = client.create_credential_source(
    CredentialSourceWriteRequest.from_dict({
        "name": "github-source",
        "resolverKind": "static_headers",
        "spec": {
            "staticHeaders": {
                "values": {
                    "token": os.environ["GITHUB_TOKEN"],
                },
            },
        },
    })
)
print(source.name)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const source = await client.credentialSources.create({
    name: 'github-source',
    resolverKind: 'static_headers',
    spec: {
        staticHeaders: {
            values: {
                token: process.env.GITHUB_TOKEN!,
            },
        },
    },
});
console.log(source.name);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `cat <<'EOF' > github-source.yaml
name: github-source
resolverKind: static_headers
spec:
    staticHeaders:
        values:
            token: ghp_example
EOF

s0 credential create -f github-source.yaml`
    }
  ]}
/>

## List Sources

<Endpoint method="GET">
/api/v1/credential-sources
</Endpoint>

Read APIs return metadata only. Use list to discover source names, resolver kinds, status, and current versions.

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `sources, err := client.ListCredentialSources(ctx)
if err != nil {
    log.Fatal(err)
}
for _, source := range sources {
    fmt.Printf("- %s (%s) version=%d\\n", source.Name, source.ResolverKind, source.CurrentVersion.Or(0))
}`
    },
    {
      label: "Python",
      language: "python",
      code: `sources = client.list_credential_sources()
for source in sources:
    print(f"- {source.name} ({source.resolver_kind}) version={source.current_version}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const sources = await client.credentialSources.list();
for (const source of sources) {
    console.log('- ' + source.name + ' (' + source.resolverKind + ') version=' + source.currentVersion);
}`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 credential list`
    }
  ]}
/>

## Get Source Metadata

<Endpoint method="GET">
/api/v1/credential-sources/{'{name}'}
</Endpoint>

Use get when you know the source name and want the current metadata record without listing everything.

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `source, err := client.GetCredentialSource(ctx, "github-source")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("%s version=%d status=%s\\n", source.Name, source.CurrentVersion.Or(0), source.Status.Or(""))`
    },
    {
      label: "Python",
      language: "python",
      code: `source = client.get_credential_source("github-source")
print(source.name, source.current_version, source.status)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const source = await client.credentialSources.get('github-source');
console.log(source.name, source.currentVersion, source.status);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 credential get github-source`
    }
  ]}
/>

## Update Or Rotate A Source

Use `PUT /api/v1/credential-sources/{name}` to replace the source contents while keeping the same source name. Existing bindings continue to point at that source name.

Sources are reusable. Rotate the source once, then keep bindings and credential rules stable by continuing to reference the same `sourceRef`.

<Endpoint method="PUT">
/api/v1/credential-sources/{'{name}'}
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `source, err := client.UpdateCredentialSource(ctx, "github-source", apispec.CredentialSourceWriteRequest{
    ResolverKind: apispec.CredentialSourceResolverKindStaticHeaders,
    Spec: apispec.CredentialSourceWriteSpec{
        StaticHeaders: apispec.NewOptStaticHeadersSourceSpec(apispec.StaticHeadersSourceSpec{
            Values: apispec.NewOptStaticHeadersSourceSpecValues(
                apispec.StaticHeadersSourceSpecValues{
                    "token": os.Getenv("ROTATED_GITHUB_TOKEN"),
                },
            ),
        }),
    },
})
if err != nil {
    log.Fatal(err)
}
fmt.Println(source.Name, source.CurrentVersion.Or(0))`
    },
    {
      label: "Python",
      language: "python",
      code: `import os
from sandbox0.apispec.models.credential_source_write_request import CredentialSourceWriteRequest

source = client.update_credential_source(
    "github-source",
    CredentialSourceWriteRequest.from_dict({
        "name": "github-source",
        "resolverKind": "static_headers",
        "spec": {
            "staticHeaders": {
                "values": {
                    "token": os.environ["ROTATED_GITHUB_TOKEN"],
                },
            },
        },
    })
)
print(source.name, source.current_version)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const source = await client.credentialSources.update('github-source', {
    resolverKind: 'static_headers',
    spec: {
        staticHeaders: {
            values: {
                token: process.env.ROTATED_GITHUB_TOKEN!,
            },
        },
    },
});
console.log(source.name, source.currentVersion);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `cat <<'EOF' > github-source-rotated.yaml
name: github-source
resolverKind: static_headers
spec:
    staticHeaders:
        values:
            token: ghp_rotated_example
EOF

s0 credential update github-source -f github-source-rotated.yaml`
    }
  ]}
/>

## Delete A Source

<Endpoint method="DELETE">
/api/v1/credential-sources/{'{name}'}
</Endpoint>

Delete only succeeds after no sandbox or template credential binding still references the source.

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err := client.DeleteCredentialSource(ctx, "github-source")
if err != nil {
    log.Fatal(err)
}
fmt.Println("deleted github-source")`
    },
    {
      label: "Python",
      language: "python",
      code: `client.delete_credential_source("github-source")
print("deleted github-source")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await client.credentialSources.delete('github-source');
console.log('deleted github-source');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 credential delete github-source`
    }
  ]}
/>

## Next Steps

<CardGroup>
  <Card title="Egress Auth" href="/docs/credential/egress-auth" cta="Continue">
    Inject destination-scoped outbound credentials through network policy.
  </Card>

  <Card title="Overview" href="/docs/template" cta="Continue">
    Define reusable sandbox environments before creating or scaling sandboxes.
  </Card>
</CardGroup>
