Skip to content

Use the API

Create secrets, write and read versions, and destroy versions with the Secrets API.

Updated View as Markdown

All paths are under https://secrets.ezghcloud.com/v1/organizations/{organizationId}/projects/{projectId}. {secretId} is the secret’s ID (a UUID), and {version} is a number or latest. The examples use $BASE for that prefix and an API key. Lists are paged as described in Requests.

Task Operation
Create a secret CreateSecret
List secrets, or find one by name ListSecrets
Read a secret’s metadata GetSecret
Change description and tags UpdateSecret
Delete a secret DeleteSecret
Write a version CreateSecretVersion
List versions ListSecretVersions
Read a version’s metadata GetSecretVersion
Read a version’s values AccessSecretVersion
Read one field AccessSecretField
Destroy a version DestroySecretVersion
Manage grants CreateSecretGrant, ListSecretGrants, GetSecretGrant, DeleteSecretGrant

Create a secret

curl -X POST "$BASE/secrets" \
  -H "Authorization: Bearer $EZGH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "prod/db",
    "description": "Production database",
    "tags": { "team": "data" },
    "fields": { "DB_USER": "app", "DB_PASSWORD": "s3cret", "CA_CERT": { "base64": "LS0tLS1CRUdJTi..." } }
  }'

201 returns the secret: id, slug, name, resourceName, latestVersion (a number, or null for a secret created without fields), versionCount, fieldCount (the fields in enabled versions, which is what you’re billed for), grantCount and the timestamps. A secret is never returned with a value. With fields, version 1 is created with the secret, which needs secrets.versions.create as well as secrets.secrets.create. A name that’s taken is 409 secret_name_taken. The create accepts an Idempotency-Key.

Write a version

A version is either the complete set of fields (this one becomes version 2):

curl -X POST "$BASE/secrets/$SECRET_ID/versions" \
  -H "Authorization: Bearer $EZGH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fields": { "DB_USER": "app", "DB_PASSWORD": "s3cret", "OLD_KEY": "x" } }'

or changes to an existing version, which keeps every field you don’t mention. This one builds on latest and becomes version 3:

curl -X POST "$BASE/secrets/$SECRET_ID/versions" \
  -H "Authorization: Bearer $EZGH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "base": "latest", "set": { "DB_PASSWORD": "n3w-s3cret" }, "remove": ["OLD_KEY"] }'

Send exactly one of fields or base. base is a version number or "latest". Removing a field the base doesn’t have is 400. 201 returns the new version’s metadata:

{
  "number": 3,
  "state": "enabled",
  "fieldNames": ["DB_PASSWORD", "DB_USER"],
  "fieldCount": 2,
  "createdBy": { "type": "Bot", "userName": "deployer" },
  "createdAt": "2026-10-02T09:58:02.114Z",
  "destroyedAt": null
}

(createdBy is the full Trails userIdentity.) Add If-Match with the secret’s ETag to write only if the secret hasn’t changed (412 precondition_failed otherwise). A write that races another for the same version number is 409 conflict: run it again. At the enabled versions quota, a write is 409 quota_exceeded: destroy versions you no longer need.

Read values

All the fields of a version:

curl "$BASE/secrets/$SECRET_ID/versions/latest/value" \
  -H "Authorization: Bearer $EZGH_API_KEY"
{ "version": 3, "fields": { "DB_USER": "app", "DB_PASSWORD": "n3w-s3cret" } }

One field, at .../versions/{version}/fields/{field}, returns { "version": 3, "name": "DB_PASSWORD", "value": "n3w-s3cret" }. A text value is a string and a binary value is { "base64": "..." }.

Each of these calls is one billed read and is recorded in Trails. Read all the fields of a version in one call rather than one call per field. A secret without an enabled version, an unknown field or a destroyed version is 404; for a destroyed version the error has error.details.reason: "destroyed".

Destroy a version

curl -X POST "$BASE/secrets/$SECRET_ID/versions/2/destroy" \
  -H "Authorization: Bearer $EZGH_API_KEY"

returns 200 with the version, state destroyed. Destroying a version that’s already destroyed returns 200 with the version and changes nothing.

Errors

Errors use the standard error body. In addition to 401, 403 and 404:

Status code When
400 invalid_request A bad name, field name, value encoding or fields/base combination. The issues name the path, such as fields.DB_PASSWORD, never the value. A value over 8 KiB is a 400 at its path.
404 not_found Also a base that’s already destroyed, with error.details.reason: "destroyed"
409 secret_name_taken A secret with that name exists in the project
409 conflict A concurrent write took the version number, or the base was destroyed meanwhile. Run the write again.
409 organization_deletion_pending The organization is being deleted
409 quota_exceeded A quota
409 grant_exists, grants_unavailable See Grants
412 precondition_failed If-Match doesn’t match
413 payload_too_large The body is over 16 MiB for creating a secret or a version, or over 1 MiB for any other request
429 too_many_requests A rate limit; wait for Retry-After
503 unavailable An upstream service is unavailable; try again
Navigation

Type to search…

↑↓ navigate↵ selectEsc close