# Secrets

Generated from contract version `1.0.0` — the same document [`/v1/api/openapi.json`](/api/openapi/) serves.

## Prerequisites

- An API key in `x-api-key`, or a browser session cookie
- `X-Organization-Id` naming the workspace to act in, unless the session already has an active one
- The base URL for your deployment, `https://server.hlix.ai` by default

## Operations

| Method | Path | Operation |
| --- | --- | --- |
| `GET` | `/v1/api/secrets/project/{projectId}` | [List a project's secrets configs](#list-a-projects-secrets-configs) |
| `POST` | `/v1/api/secrets/project/{projectId}` | [Configure a secrets provider](#configure-a-secrets-provider) |
| `DELETE` | `/v1/api/secrets/project/{projectId}/{configId}` | [Delete a secrets config](#delete-a-secrets-config) |
| `PATCH` | `/v1/api/secrets/project/{projectId}/{configId}` | [Update a secrets config](#update-a-secrets-config) |
| `GET` | `/v1/api/secrets/project/{projectId}/entries` | [List the project's secrets, masked, with the file each belongs to](#list-the-projects-secrets-masked-with-the-file-each-belongs-to) |
| `PUT` | `/v1/api/secrets/project/{projectId}/entries` | [Create or replace secrets in the project store](#create-or-replace-secrets-in-the-project-store) |
| `DELETE` | `/v1/api/secrets/project/{projectId}/entries/{key}` | [Remove one secret from the project store](#remove-one-secret-from-the-project-store) |
| `POST` | `/v1/api/secrets/project/{projectId}/export` | [Export one source file's secrets, values in clear](#export-one-source-files-secrets-values-in-clear) |
| `PUT` | `/v1/api/secrets/project/{projectId}/files` | [Upload whole secret files and let the store parse them](#upload-whole-secret-files-and-let-the-store-parse-them) |
| `GET` | `/v1/api/secrets/project/{projectId}/sync-logs` | [List a project's secrets sync history](#list-a-projects-secrets-sync-history) |
| `POST` | `/v1/api/secrets/project/{projectId}/test` | [Resolve the project's secrets and report the key names](#resolve-the-projects-secrets-and-report-the-key-names) |

## List a project's secrets configs

`GET /v1/api/secrets/project/{projectId}` · operation ID `listProjectSecretsConfigs`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretsConfigSummary[] | Each config with a redacted `configSummary`. The `config` ciphertext is ABSENT from this body, not null. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | The resource does not exist, or the caller is a task-scoped credential and this endpoint is outside its allowlist. |

## Configure a secrets provider

`POST /v1/api/secrets/project/{projectId}` · operation ID `createProjectSecretsConfig`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Request body** — `application/json`, `CreateSecretsConfig` (required)

The contract does not pin this body's properties.

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `201` | SecretsConfig | The stored row. `config` is the encrypted credential blob — ciphertext, never plaintext. |
| `400` | ValidationError \| ApiError | The JSON body failed schema validation (`ValidationError`), or the request named no active organization (`ApiError`). |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | The resource does not exist, or the caller is a task-scoped credential and this endpoint is outside its allowlist. |
| `500` | ApiError | The config could not be written. |

## Delete a secrets config

`DELETE /v1/api/secrets/project/{projectId}/{configId}` · operation ID `deleteProjectSecretsConfig`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `configId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | DeletedAck | The config is gone. Deleting one that was never there answers the same 200 — the call is idempotent. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | The resource does not exist, or the caller is a task-scoped credential and this endpoint is outside its allowlist. |

## Update a secrets config

`PATCH /v1/api/secrets/project/{projectId}/{configId}` · operation ID `updateProjectSecretsConfig`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `configId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Request body** — `application/json`, `UpdateSecretsConfig` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `config` | object | no | For an Infisical config, the fields to change (clientId, clientSecret, projectId, environment, siteUrl, secretPath, recursive); a field left out keeps its stored value. For a manual config, the whole config. |
| `envFilter` | string,null | no |  |
| `isActive` | boolean | no |  |
| `label` | string | no | min length 1, max length 100 |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretsConfig | The updated row, with the re-encrypted `config` ciphertext. |
| `400` | ValidationError \| ApiError | The body failed validation, or the merged provider config is not valid — for Infisical, a missing field or a server address that is not a public https origin. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such config in this workspace. |

## List the project's secrets, masked, with the file each belongs to

`GET /v1/api/secrets/project/{projectId}/entries` · operation ID `listProjectSecretEntries`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretEntries | Every secret attached to the project. Values are masked to their last four characters and there is no option that unmasks them — `POST …/export` is the only route that returns plaintext. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such project in this workspace. |

## Create or replace secrets in the project store

`PUT /v1/api/secrets/project/{projectId}/entries` · operation ID `upsertProjectSecretEntries`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Request body** — `application/json`, `UpsertSecretEntries` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `entries` | object[] | **yes** |  |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretEntriesWritten | How many entries landed, and the keys refused because their `source` is not a path this platform will write inside a checkout. |
| `400` | ValidationError \| ApiError | The JSON body failed schema validation (`ValidationError`), or the request named no active organization (`ApiError`). |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such project in this workspace. |
| `409` | ApiError | The project is archived and accepts no further writes. |

## Remove one secret from the project store

`DELETE /v1/api/secrets/project/{projectId}/entries/{key}` · operation ID `deleteProjectSecretEntry`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `key` | path | **yes** | string |  |
| `source` | query | no | string | Remove the key only from this source file. Omitted, the key is removed from EVERY file that declares it. |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretEntryDeleted | How many `(key, source)` pairs were removed. Zero is a success, not a 404 — the call is idempotent. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such project in this workspace. |
| `409` | ApiError | The project is archived and accepts no further writes. |

## Export one source file's secrets, values in clear

`POST /v1/api/secrets/project/{projectId}/export` · operation ID `exportProjectSecrets`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Request body** — `application/json`, `ExportSecrets` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `format` | string | no | min length 1, max length 64 |
| `source` | string | no | min length 1, max length 512 |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | string (binary) | The rendered file, in the source's OWN shape unless `format` overrides it — dotenv, direnv, Terraform (HCL and JSON), Java properties, Spring YAML, .NET appsettings and user secrets, Azure Functions local settings, npm credentials, Rails database.yml, wrangler/fly TOML, Serverless provider environment, or a generic credentials JSON/YAML/INI. Sent as `text/plain` whatever the grammar, because the body IS the file and the caller saves it. VALUES ARE IN CLEAR: this is the one route in the product that returns them, and every call writes a `secrets.exported` audit row. Formats marked `shape` (see `GET …/entries`) are rebuilt normalized — every captured value is present, comments and un-captured neighbours are not. |
| `400` | ValidationError \| ApiError | The JSON body failed schema validation (`ValidationError`), or the request named no active organization (`ApiError`). |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such project in this workspace. |

## Upload whole secret files and let the store parse them

`PUT /v1/api/secrets/project/{projectId}/files` · operation ID `importProjectSecretFiles`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Request body** — `application/json`, `ImportSecretFiles` (required)

| Property | Type | Required | Notes |
| --- | --- | --- | --- |
| `files` | object[] | **yes** |  |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretFilesImported | What the store made of each file. A key/value document (dotenv, direnv, Terraform, Java properties, Spring YAML, .NET appsettings, Azure Functions local settings, npm credentials, Rails database.yml, wrangler/fly TOML, Serverless, generic credentials JSON/YAML/INI) is PARSED into entries under its own path, and that path's entries are REPLACED — the file is the truth. An opaque credential (private key, keystore, service-account JSON, Docker/AWS config) is stored byte-exact through the protected-files channel instead, so provisioning and `hlix pull` see it. An encrypted envelope this platform holds no key for (SOPS, Ansible Vault, git-crypt) and a `.env.example` are refused by name in `skipped`. No response field can carry a secret VALUE. |
| `400` | ValidationError \| ApiError | The JSON body failed schema validation (`ValidationError`), or the request named no active organization (`ApiError`). |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | No such project in this workspace. |
| `409` | ApiError | The project is archived and accepts no further writes. |

## List a project's secrets sync history

`GET /v1/api/secrets/project/{projectId}/sync-logs` · operation ID `listProjectSecretsSyncLogs`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretsSyncLog[] | Each push of secrets into a sandbox or into GitHub: how many moved, and whether it worked. |
| `400` | ApiError | The request named no active organization. |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | The resource does not exist, or the caller is a task-scoped credential and this endpoint is outside its allowlist. |

## Resolve the project's secrets and report the key names

`POST /v1/api/secrets/project/{projectId}/test` · operation ID `testProjectSecrets`

**Parameters**

| Name | In | Required | Type | Description |
| --- | --- | --- | --- | --- |
| `projectId` | path | **yes** | string |  |
| `X-Organization-Id` | header | no | string | The workspace (organization) to act in. Omitted, the session's active organization is used. |

**Responses**

| Status | Body | Description |
| --- | --- | --- |
| `200` | SecretsProbe | The keys every active config resolves to, merged. Names and a count only — the values are fetched to prove the credentials work and then discarded. |
| `400` | ApiError | A provider refused, and its own message is passed through as `error`. (Or the request named no active organization.) |
| `401` | ApiError | No valid session or API key. |
| `403` | ApiError | Authenticated, but not a member of the named organization — or a client collaborator without the required access on this project. |
| `404` | ApiError | The resource does not exist, or the caller is a task-scoped credential and this endpoint is outside its allowlist. |

## If a request fails

These apply to every operation on this page.

- `400` — the body failed schema validation, or the request named no active organization. A `ValidationError` body carries the failing fields; an `ApiError` body carries only `error`.
- `401` — no valid session or API key.
- `403` — authenticated, but not a member of the named organization, or a client collaborator without the required access.
- `404` — no such resource **in this workspace**. Absent and not-visible are deliberately the same answer; do not infer cross-tenant existence from it.
- `409` — the expected revision or state is stale, or another operation currently owns the transition.
- `500` — the operation failed server-side. Retry only where an idempotency key makes that safe.

## Next steps

[API reference](/api/reference/)
  [API & OpenAPI](/api/openapi/)
  [TypeScript SDK](/sdk/typescript/)
  [CLI reference](/reference/cli/)