# How to manage secrets (https://developer.godaddy.com/en/docs/api-users/hosting/manage-secrets)

---
title: How to manage secrets
description: 'Add, update, and delete per-variant environment secrets for a hosting app.'
keywords: >-
  PATCH /apps/{appId}/secrets, GET /apps/{appId}/secrets, secret:write scope,
  per-variant secrets, batch secret operations, preview publish secrets,
  environment variables hosting
agentNotes:
  scopes:
    - 'hosting.secret:read'
    - 'hosting.secret:write'
  idempotent: false
  destructive: true
  failureRecovery: >-
    The secrets write endpoint is not idempotent. Duplicate additions return an
    error. Deletions are irreversible — confirm secret names before submitting.
related:
  tutorials:
    - title: Deploy your Node.js app
      href: /docs/api-users/hosting/workflows/deploy-nodejs-app
  guides:
    - title: Manage apps
      href: /docs/api-users/hosting/manage-apps
  concepts:
    - title: Hosting concepts
      href: /docs/api-users/hosting/concepts
  apis:
    - title: Secrets reference
      href: /docs/references/rest/hosting/secrets
---

## Overview

This page covers managing environment secrets for a hosted app. You configure secrets per variant (`PREVIEW` or `PUBLISH`). A single call can add, update, or delete them. The API never returns secret values.

Go to [Deploy your Node.js app](https://developer.godaddy.com/docs/api-users/hosting/workflows/deploy-nodejs-app) if you haven't created and deployed an app yet. Go to [Hosting concepts](https://developer.godaddy.com/docs/api-users/hosting/concepts) for background on the variant model.

## Prerequisites

The following prerequisites are required before you can manage secrets for your app:

* a [Personal Access Token](https://developer.godaddy.com/personal-access-token) with the `hosting.secret:read` scope (list operations) or `hosting.secret:write` scope (write operations)

## List secrets

`GET /apps/{appId}/secrets` returns a deduplicated flat list of secret metadata for the app. Values are never included.

The following procedure lists secrets for an app.

* List secrets:

  ```bash tab="curl"
  curl -s "$BASE_URL/v1/hosting/apps/$APP_ID/secrets?variant=PREVIEW" \
    -H "Authorization: Bearer $GODADDY_PAT" | jq .
  ```

  ```js tab="Node"
  const res = await fetch(
    `${process.env.BASE_URL}/v1/hosting/apps/${appId}/secrets?variant=PREVIEW`,
    { headers: { Authorization: `Bearer ${process.env.GODADDY_PAT}` } },
  );
  const secrets = await res.json();
  ```

  ```python tab="Python"
  import requests, os

  resp = requests.get(
      f"{os.environ['BASE_URL']}/v1/hosting/apps/{app_id}/secrets?variant=PREVIEW",
      headers={"Authorization": f"Bearer {os.environ['GODADDY_PAT']}"},
  )
  secrets = resp.json()
  ```

  ```go tab="Go"
  req, _ := http.NewRequest("GET",
      os.Getenv("BASE_URL")+"/v1/hosting/apps/"+appID+"/secrets?variant=PREVIEW", nil)
  req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT"))
  resp, _ := http.DefaultClient.Do(req)
  defer resp.Body.Close()
  ```

The response includes secret names and last-updated timestamps. Values are omitted.

## Add, update, or delete secrets

`PATCH /apps/{appId}/secrets` applies a [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/html/rfc6902) array atomically. Pass the environment as the `variant` query parameter (`PREVIEW` or `PUBLISH`). Each call accepts up to 50 operations total. Supported ops are `add`, `replace`, and `remove`. Each path is `/{name}`.

The following procedure adds a new preview secret, updates an existing preview secret, and deletes another preview secret in one call.

* Submit the patch:

  ```bash tab="curl"
  curl -s -X PATCH "$BASE_URL/v1/hosting/apps/$APP_ID/secrets?variant=PREVIEW" \
    -H "Authorization: Bearer $GODADDY_PAT" \
    -H "Content-Type: application/json-patch+json" \
    -d '[
      { "op": "add",     "path": "/STRIPE_KEY", "value": "sk_test_abc123" },
      { "op": "replace", "path": "/DB_URL",     "value": "postgres://host/newdb" },
      { "op": "remove",  "path": "/OLD_FLAG" }
    ]' | jq .
  ```

  ```js tab="Node"
  const res = await fetch(`${process.env.BASE_URL}/v1/hosting/apps/${appId}/secrets?variant=PREVIEW`, {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${process.env.GODADDY_PAT}`,
      'Content-Type': 'application/json-patch+json',
    },
    body: JSON.stringify([
      { op: 'add',     path: '/STRIPE_KEY', value: 'sk_test_abc123' },
      { op: 'replace', path: '/DB_URL',     value: 'postgres://host/newdb' },
      { op: 'remove',  path: '/OLD_FLAG' },
    ]),
  });
  const result = await res.json();
  ```

  ```python tab="Python"
  import json, requests, os

  resp = requests.patch(
      f"{os.environ['BASE_URL']}/v1/hosting/apps/{app_id}/secrets?variant=PREVIEW",
      headers={
          "Authorization": f"Bearer {os.environ['GODADDY_PAT']}",
          "Content-Type": "application/json-patch+json",
      },
      data=json.dumps([
          {"op": "add",     "path": "/STRIPE_KEY", "value": "sk_test_abc123"},
          {"op": "replace", "path": "/DB_URL",     "value": "postgres://host/newdb"},
          {"op": "remove",  "path": "/OLD_FLAG"},
      ]),
  )
  ```

  ```go tab="Go"
  body := `[
    {"op": "add",     "path": "/STRIPE_KEY", "value": "sk_test_abc123"},
    {"op": "replace", "path": "/DB_URL",     "value": "postgres://host/newdb"},
    {"op": "remove",  "path": "/OLD_FLAG"}
  ]`
  req, _ := http.NewRequest("PATCH",
      os.Getenv("BASE_URL")+"/v1/hosting/apps/"+appID+"/secrets?variant=PREVIEW",
      strings.NewReader(body))
  req.Header.Set("Authorization", "Bearer "+os.Getenv("GODADDY_PAT"))
  req.Header.Set("Content-Type", "application/json-patch+json")
  resp, _ := http.DefaultClient.Do(req)
  defer resp.Body.Close()
  ```

The response is secret metadata. Values are never included.

## Common errors

The following table lists common errors and recommended actions:

| Status | Cause                                    | Action                                                                                        |
| ------ | ---------------------------------------- | --------------------------------------------------------------------------------------------- |
| `401`  | Expired or revoked PAT, or missing scope | Confirm the token includes `hosting.secret:read` for list or `hosting.secret:write` for patch |
| `404`  | App id not found                         | Confirm the id from `GET /apps`                                                               |
| `409`  | Secret already exists (on `add`)         | Use `replace` instead of `add` for existing secrets                                           |
| `422`  | Validation error                         | Check the patch array structure, op values (`add`/`replace`/`remove`), and path format        |
| `429`  | Rate limit exceeded                      | Back off and retry. Go to [Rate limits](https://developer.godaddy.com/docs/api-users/rate-limits) for handling guidance    |
