> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pinework.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Secret

> A secret is an encrypted named value in the workspace Keychain. The API returns its details, and only a person can reveal its value.

A secret is a named value, such as an API key or a login, stored encrypted in your workspace. Its id is `sec_` plus 16 letters and digits. The dashboard lists secrets on the **Keychain** page in settings. For how runs use a secret, see [Secrets](/concepts/secrets).

A secret has no lifecycle states. It exists until its owner or a workspace admin deletes it.

## Fields

List returns the base fields. Get, create, update, replace and transfer return the base fields plus the detail fields. No response except a reveal carries a value.

<ResponseField name="object" type="string" required>
  Always `secret`.
</ResponseField>

<ResponseField name="id" type="string" required>
  The secret id, `sec_…`.
</ResponseField>

<ResponseField name="name" type="string" required>
  Unique in the workspace. 1 to 80 characters.
</ResponseField>

<ResponseField name="template" type="string" required>
  The type: `api_key`, `login`, `identity` or `custom`.
</ResponseField>

<ResponseField name="description" type="string">
  Up to 500 characters. Null when not set.
</ResponseField>

<ResponseField name="urls" type="string[]" required>
  The HTTPS hosts the secret is meant for. Each URL names one exact host, with no wildcard. Can be empty, except for `api_key`.
</ResponseField>

<ResponseField name="displayHint" type="string">
  Eight asterisks plus the last 4 characters of the first `password` field, or of the first field. Null when that value is shorter than 4 characters.
</ResponseField>

<ResponseField name="fieldSummary" type="object[]" required>
  One entry per field, with labels and types only.

  <Expandable title="properties">
    <ResponseField name="label" type="string">Field label, 1 to 100 characters.</ResponseField>
    <ResponseField name="type" type="string">`text`, `email`, `password`, `url` or `phone`.</ResponseField>
    <ResponseField name="secret" type="boolean">True when the type is `password`.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="createdBy" type="object" required>
  Who created the secret: `type` (`user` or `agent`), `id` and `label`.
</ResponseField>

<ResponseField name="owner" type="object">
  The owning person: `id` and `name`. Null when an agent created the secret.
</ResponseField>

<ResponseField name="visibility" type="string" required>
  `private`, `company` or `custom`. Defaults to `company`.
</ResponseField>

<ResponseField name="grantCount" type="integer" required>
  How many grants name this secret.
</ResponseField>

<ResponseField name="lastUsedAt" type="string">
  When a person last revealed the value. Null until the first reveal.
</ResponseField>

<ResponseField name="createdAt" type="string" required>
  ISO 8601 timestamp.
</ResponseField>

<ResponseField name="updatedAt" type="string" required>
  ISO 8601 timestamp.
</ResponseField>

<ResponseField name="fieldDefinitions" type="object[]">
  Detail only. Like `fieldSummary`, plus `required`.
</ResponseField>

<ResponseField name="lastRotatedAt" type="string">
  Detail only. When the fields were last replaced. Null until the first replace.
</ResponseField>

<ResponseField name="lastReadAt" type="string">
  Detail only. When the value was last revealed. Null until the first reveal.
</ResponseField>

<ResponseField name="grants" type="object">
  Only on get with `expand[]=grants`. A list of up to 25 grants, newest first, with `hasMore` and `totalCount`.
</ResponseField>

A reveal returns a different object:

<ResponseField name="object" type="string" required>
  Always `secret_value`.
</ResponseField>

<ResponseField name="secretId" type="string" required>
  The secret id, `sec_…`.
</ResponseField>

<ResponseField name="fields" type="object[]" required>
  One entry per field: `label`, `type` and the decrypted `value`.
</ResponseField>

<ResponseField name="readAt" type="string" required>
  When the reveal happened.
</ResponseField>

### Who can do what

| Action | Who |
| - | - |
| See a `company` secret | Any member or agent in the workspace |
| See a `custom` secret | The owner, workspace admins, and people or agents with a grant |
| See a `private` secret | The owner and workspace admins |
| Reveal the value | A person who can see the secret. An agent is refused. |
| Set or change `urls` | A person. An agent is refused. |
| Update or replace | The owner, workspace admins, or an edit grant |
| Make a secret `private` | A workspace admin. This removes every grant on the secret. |
| Delete or transfer | The owner or a workspace admin |

An agent can create a secret with no URLs. So an agent cannot create an `api_key` secret.

## CLI

| Command | Does |
| - | - |
| `pinework secret create` | Creates a secret. `--name` and `--template` are required. `--field`, `--url` and `--grant` repeat. |
| `pinework secret get <sec_…>` | Shows details, never values. `--expand grants` adds up to 25 grants. |
| `pinework secret list` | Lists secrets, newest first. 50 per page by default, 100 at most. |
| `pinework secret read <sec_…>` | Reveals the value. `--reason` is logged. Values print only with `--json`. |
| `pinework secret replace <sec_…>` | Replaces every field. Fields you leave out are dropped. |
| `pinework secret transfer-owner <sec_…>` | Gives the secret to another member. `--new-owner` is required. |
| `pinework grant create --target-type secret` | Gives a person or agent a role on a secret. |
| `pinework grant list --target-type secret` | Lists grants on secrets. |
| `pinework grant revoke <grnt_…>` | Revokes a grant. It stays readable for audit. |

A `--field` value has the form `label=type=value`. A `--grant` value has the form `<agt_…|@handle>=<VARIABLE_NAME>`. It gives that agent view access and names the variable its runs read.

```bash theme={null}
pinework secret create --name vercel-token --template api_key \
  --field "token=password=vcp_8f2kX1" --url https://api.vercel.com \
  --grant @deployer=VERCEL_TOKEN
pinework secret list --template api_key --limit 20
pinework secret get sec_4Fq9Lm2Rt7Xa1Bc3 --expand grants
pinework secret read sec_4Fq9Lm2Rt7Xa1Bc3 --reason "Rotating the Vercel token" --json
```

There is no CLI verb for update or delete. Use the API or the Keychain page.

## API

| Method | Path | Does |
| - | - | - |
| GET | `/api/v1/secrets` | Lists secrets. Filters: `template`, `visibility`, `url`, `q`, `sort`, `limit`, `cursor`. |
| POST | `/api/v1/secrets` | Creates a secret. Returns 201. |
| GET | `/api/v1/secrets/:id` | Gets one secret. `expand[]` takes `grants`, `createdBy` or `owner`. |
| PATCH | `/api/v1/secrets/:id` | Updates `name`, `template`, `description`, `urls` or `visibility`. |
| DELETE | `/api/v1/secrets/:id` | Deletes the secret and its grants. Returns 204. |
| POST | `/api/v1/secrets/:id/read` | Reveals the value. Body: optional `reason`. |
| POST | `/api/v1/secrets/:id/replace` | Replaces the full field set. Body: `fields`. |
| POST | `/api/v1/secrets/:id/transfer-owner` | Moves ownership. Body: `newOwnerId`. |
| POST | `/api/v1/grants` | Creates a grant. Needs an `Idempotency-Key` header. |
| GET | `/api/v1/grants` | Lists grants. Filter with `targetType=secret` and `targetId=sec_…`. |
| POST | `/api/v1/grants/:id/revoke` | Revokes a grant. Needs an `Idempotency-Key` header. |

The create body takes `name`, `template`, `fields`, and optional `description`, `urls`, `visibility` and `initialGrants`. Each initial grant names a `principal`, a `permission` (`view`, `edit` or `admin`) and an `envVarName`.

The list `cursor` is the `nextCursor` of the previous page. List drops secrets you cannot see after it reads a page. So a page can hold fewer items than `limit`, and `totalCount` counts only this page.

## Errors

| Code | HTTP | When | What to do |
| - | - | - | - |
| `secret.duplicate_name` | 409 | Another secret in the workspace has this name. | Pick another name. |
| `secret.read_forbidden` | 403 | You lack view access, or the value could not be read. | Ask the owner for a grant. |
| `secret.read_audit_failed` | 500 | Pinework could not log the reveal, so it returned no value. | Retry the reveal. |
| `secret.update_forbidden` | 403 | You lack edit access on a PATCH. | Ask the owner for an edit grant. |
| `secret.replace_forbidden` | 403 | You lack edit access on a replace. | Ask the owner for an edit grant. |
| `secret.delete_forbidden` | 403 | You are not the owner or a workspace admin. | Ask the owner or an admin. |
| `visibility.flip_forbidden` | 403 | You set `private` and are not a workspace admin. | Ask an admin. |
| `owner_transfer.forbidden` | 403 | You are not the owner or a workspace admin. | Ask the owner or an admin. |
| `owner_transfer.same_owner` | 409 | The new owner already owns the secret. | Nothing to do. |
| `owner_transfer.user_not_found` | 422 | The new owner is not a workspace member. | Use a member's user id. |
| `expand.not_allowed_on_list` | 400 | You sent `expand[]=grants` to list. | Expand on get instead. |
| `http_exception` | 403 | An agent tried to reveal a value or set `urls`. | A person must do it. |
| `http_exception` | 403 | You sent `expand[]=grants` and are not the owner or an admin. | Get the secret without the expand. |
| `validation_error` | 400 | An `api_key` has no URL, a URL is not one exact HTTPS host, or an agent got `admin`. | Fix the field named in `param`. |
| `not_authorized` | 403 | An agent tried to create, change or revoke a grant. | A person must manage grants. |

A secret you cannot see returns 404 `resource_not_found`, not 403. For shared codes, see [API conventions](/reference/api#errors).

## Limits

* Name: 1 to 80 characters.
* Description: 500 characters.
* Field label: 100 characters.
* Reveal reason: 200 characters.
* List search `q`: 100 characters.
* List page: 50 by default, 100 at most.
* Grants inlined on get: 25.
* Initial grant variable name: 64 characters, matching `[A-Z_][A-Z0-9_]*`.

<CardGroup cols={2}>
  <Card title="Secrets" href="/concepts/secrets">
    Why only a person can reveal a value, and how runs use a secret.
  </Card>

  <Card title="Add an MCP server" href="/guides/add-an-mcp-server">
    Point an MCP server at a secret by name.
  </Card>

  <Card title="Connection" href="/reference/connection">
    OAuth and API key connections to outside services.
  </Card>

  <Card title="Environment and device" href="/reference/environment-and-device">
    Where a run executes and what reaches it.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.