# Get secrets

`GET https://api.instacloud.com/projects/{projectId}/secrets`

**Token scope:** `account` · `org` · `project` — read-only tokens allowed.

The secret seam — decrypt and return the branch's credential bundle, or one compute service's slice of it with ?service=<type>/<name>. Both carry the branch's canonical provider credentials (the .env contract, #323); NEITHER reports what a container receives, which needs an explicit binding. A name several services define is listed in `collisions`, and omitted from the bundle when ?on_collision=withhold. Gated — secrets.read.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `branch` | query | `string` | no | Branch name (defaults to the project's default branch) |
| `service` | query | `string` | no | Answer with ONE compute service's slice of this seam, as "<type>/<name>": that service's user secrets, the unbound ones, its explicit provider bindings, and the branch's canonical provider credentials. This is the .env seam NARROWED BY SERVICE, not a container inspection — a container receives a provider credential only where bound, so this read is deliberately wider than the deployed env. Compute only; a managed service is a 400 naming /services/:serviceId/credentials. |
| `on_collision` | query | `"merge" \| "withhold"` | no | How to answer a name several services define. "merge" (the default, and today's behaviour) returns whichever row is newest; "withhold" omits it. Either way the name is listed in `collisions`. |
| `projectId` | path | `string` | yes |  |

## Response 200

Default Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `secrets` | `object` | no | env-var name → decrypted value (e.g. DATABASE_URL, AWS_ACCESS_KEY_ID, ...) |
| `collisions` | `object[]` | no |  |
| `collisions.name` | `string` | yes | env-var name more than one service defines, so a flat bundle cannot attribute a value to it |
| `collisions.services` | `string[]` | yes | "<type>/<name>" of every service that defines it; a since-removed holder appears as its service id |

## Response 202

Default Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | `"approval_required"` | no |  |
| `approvalId` | `string` | no |  |
| `action` | `string` | no | the gated action, or a comma-joined compound set — prefer `actions` |
| `actions` | `string[]` | no | every capability this approval covers |
| `message` | `string` | no |  |
| `url` | `string` | no | the console page where a project admin reviews this request |
| `nextActions` | `object[]` | no |  |
| `nextActions.op` | `string` | yes | Neutral logical action id, e.g. "service.add" — NOT an operationId or a CLI/MCP tool name; each client maps it to its own surface. |
| `nextActions.reason` | `string` | yes | Natural-language, human/LLM-facing "why do this now". |
| `nextActions.args` | `object` | no | Suggested, flat named arguments; "<placeholder>"s where a value is unknown. |
| `nextActions.gated` | `boolean` | no | True if the action passes a governance gate. |

## Response 400

Default Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | `string` | yes |  |

## Response 404

Default Response

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | `string` | yes |  |
