Skip to content

Using secret collections

Info

Learn the concepts and fundamentals of secret collections in the Explanation page.

A secret collection defines one or more secrets used by your optimization model during execution. You can attach secret collections to an instance configuration, or directly when starting a run.

This how-to guide explains how to interact with secret collections using the /v1/applications/{application_id}/secrets endpoints. Go to the reference section to see all the available parameters for each endpoint.

Secret values are write-only

Once a secret is set, its value cannot be retrieved. Every secret value is returned masked as ***************. The mask is literal and is always 15 * characters, regardless of the length or content of the secret value, so keep your own copy of any value you may need later.

Create a secrets collection

Use the POST /v1/applications/{application_id}/secrets endpoint to create a new secret collection for an application. Pass the secrets field in the request payload as a list of ApplicationSecret objects. Each secret has the following attributes:

  • type: Either env or file, which determines how the secret is injected into the runtime.
  • location: Where to place the secret.
    • env: the environment variable name. E.g.: BURROW_ENTRANCE.
    • file: the relative path from the execution directory. E.g.: licenses/burrow.entr.
  • value: The secret value as text (limited to 1 KB). Secret values are write-only: once set, they cannot be retrieved. A value equal to the masked placeholder *************** is rejected with 400 Bad Request.
{
  "type": "env",
  "location": "ACME_LICENSE_KEY",
  "value": "abc123"
}
{
  "type": "file",
  "location": "config/app.conf",
  "value": "server=prod\nport=8080"
}

Here is an example that uses both secrets defined above to create a secrets collection. Unlike applications, the endpoint requires an id and a name in the request payload, so generate them yourself if you don't want to choose custom values:

SECRETS_ID="secrets-$(openssl rand -hex 4)"

curl -s -X POST "https://api.cloud.nextmv.io/v1/applications/whispering-badger/secrets" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  -H "Content-Type: application/json" \
  -d "{
    \"id\": \"${SECRETS_ID}\",
    \"name\": \"${SECRETS_ID}\",
    \"secrets\": [
      {\"type\": \"env\", \"location\": \"ACME_LICENSE_KEY\", \"value\": \"abc123\"},
      {\"type\": \"file\", \"location\": \"config/app.conf\", \"value\": \"server=prod\\nport=8080\"}
    ]
  }" \
  | jq '.'
{
  "id": "secrets-ba404179",
  "application_id": "whispering-badger",
  "name": "secrets-ba404179",
  "description": "",
  "created_at": "2026-07-29T06:45:19Z",
  "updated_at": "2026-07-29T06:45:19Z"
}

The call above uses a randomly generated ID, and the same identifier for the collection's name. The name of the collection is used as a human-readable label. You can pass different values for the id and name fields in the payload to specify a custom ID and name for the collection. For example:

curl -s -X POST "https://api.cloud.nextmv.io/v1/applications/whispering-badger/secrets" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "fluffy-secrets",
    "name": "Secrets kept by a fluffy bunny",
    "secrets": [
      {"type": "env", "location": "ACME_LICENSE_KEY", "value": "abc123"},
      {"type": "file", "location": "config/app.conf", "value": "server=prod\nport=8080"}
    ]
  }' \
  | jq '.'
{
  "id": "fluffy-secrets",
  "application_id": "whispering-badger",
  "name": "Secrets kept by a fluffy bunny",
  "description": "",
  "created_at": "2026-07-29T06:45:23Z",
  "updated_at": "2026-07-29T06:45:23Z"
}

Run with a secrets collection

Once a secrets collection has been created you can use it when starting a run in two ways:

  • Use directly when starting a run with the secrets_collection_id field of the configuration object in the run payload, or
  • Attach it to an instance configuration with the secrets_collection_id field of the configuration object in the instance payload, and use that instance when starting a run.

Here is an example where the secrets collection is used directly when starting a run.

curl -s -X POST "https://api.cloud.nextmv.io/v1/applications/whispering-badger/runs?instance_id=latest" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {"name": "world", "radius": 6378, "distance": 147.6},
    "configuration": {"secrets_collection_id": "fluffy-secrets"}
  }' \
  | jq '.'
{
  "run_id": "latest-y9H4mQPvR"
}

Once the run finishes, fetch its result with the GET /v1/applications/{application_id}/runs/{run_id} endpoint.

curl -s -X GET "https://api.cloud.nextmv.io/v1/applications/whispering-badger/runs/latest-y9H4mQPvR" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  | jq 'del(.output.assets)'
{
  "id": "latest-y9H4mQPvR",
  "user_email": "sebastian@nextmv.io",
  "name": "",
  "description": "",
  "metadata": {
    "status": "succeeded",
    "status_v2": "succeeded",
    "created_at": "2026-07-29T06:45:29Z",
    "initiated_at": "2026-07-29T06:45:29.433345208Z",
    "duration": 6075,
    "execution_duration": 5025,
    "input_size": 52,
    "output_size": 25099,
    "error": "",
    "application_id": "whispering-badger",
    "application_instance_id": "latest",
    "application_version_id": "",
    "secrets_collection_id": "fluffy-secrets",
    "execution_class": "6c9500mb870s",
    "runtime": "python-3_11",
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "format": {
      "input": {
        "type": "json"
      },
      "output": {
        "type": "json"
      }
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "request_options": null,
      "options_summary": [
        {
          "name": "details",
          "value": "true",
          "source": "version"
        }
      ]
    },
    "queuing_priority": 6,
    "queuing_disabled": false,
    "metrics": {
      "message": "Hello, world",
      "value": 1.23
    }
  },
  "output": {
    "options": {
      "details": true
    },
    "solution": {
      "message": "Hello, world"
    },
    "metrics": {
      "value": 1.23,
      "message": "Hello, world"
    }
  }
}

.output.assets was removed from the response above for a cleaner display.

Let's say we have an instance identified as production. First, let's update it to use the secrets collection. The snippet below does this in three steps: it sends a GET request to fetch the current instance, uses jq to merge the configuration field into that response, and pipes the merged payload to a PUT /v1/applications/{application_id}/instances/{instance_id} request that updates the instance.

curl -s -X GET "https://api.cloud.nextmv.io/v1/applications/whispering-badger/instances/production" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  | jq '. + {"configuration": {"secrets_collection_id": "fluffy-secrets"}}' \
  | curl -s -X PUT "https://api.cloud.nextmv.io/v1/applications/whispering-badger/instances/production" \
      -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
      -H "Content-Type: application/json" \
      -d @- \
  | jq '.'
{
  "name": "The main production instance",
  "id": "production",
  "application_id": "whispering-badger",
  "version_id": "version-7vdhines",
  "description": "",
  "configuration": {
    "execution_class": "6c9500mb870s",
    "secrets_collection_id": "fluffy-secrets",
    "queuing": {
      "priority": 6,
      "disabled": false
    }
  },
  "locked": false,
  "created_at": "2026-07-29T06:45:08.853378506Z",
  "updated_at": "2026-07-29T06:45:58.509515505Z"
}

We can now start a run using the production instance, and the secrets collection will be used automatically.

curl -s -X POST "https://api.cloud.nextmv.io/v1/applications/whispering-badger/runs?instance_id=production" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"input": {"name": "world", "radius": 6378, "distance": 147.6}}' \
  | jq '.'
{
  "run_id": "production-L-vImwPDg"
}

After getting the run, we can see that the secrets_collection_id field is set to the secrets collection we attached to the instance.

curl -s -X GET "https://api.cloud.nextmv.io/v1/applications/whispering-badger/runs/production-L-vImwPDg" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  | jq 'del(.output.assets)'
{
  "id": "production-L-vImwPDg",
  "user_email": "sebastian@nextmv.io",
  "name": "",
  "description": "",
  "metadata": {
    "status": "succeeded",
    "status_v2": "succeeded",
    "created_at": "2026-07-29T06:46:02Z",
    "initiated_at": "2026-07-29T06:46:02.857989017Z",
    "duration": 7000,
    "execution_duration": 6107,
    "input_size": 52,
    "output_size": 25099,
    "error": "",
    "application_id": "whispering-badger",
    "application_instance_id": "production",
    "application_version_id": "version-7vdhines",
    "secrets_collection_id": "fluffy-secrets",
    "execution_class": "6c9500mb870s",
    "runtime": "python-3_11",
    "run_type": {
      "type": "standard",
      "definition_id": "",
      "reference_id": ""
    },
    "format": {
      "input": {
        "type": "json"
      },
      "output": {
        "type": "json"
      }
    },
    "options": {
      "active_options": {
        "details": "true"
      },
      "request_options": null,
      "options_summary": [
        {
          "name": "details",
          "value": "true",
          "source": "version"
        }
      ]
    },
    "queuing_priority": 6,
    "queuing_disabled": false,
    "metrics": {
      "message": "Hello, world",
      "value": 1.23
    }
  },
  "output": {
    "options": {
      "details": true
    },
    "solution": {
      "message": "Hello, world"
    },
    "metrics": {
      "value": 1.23,
      "message": "Hello, world"
    }
  }
}

Get a secrets collection

Use the GET /v1/applications/{application_id}/secrets/{collection_id} endpoint to retrieve an existing secrets collection for an application by its ID. The response is a SecretsCollection object.

Info

Secret values are write-only. Every secret's value is returned as the masked placeholder ***************, while its type and location stay visible.

curl -s -X GET "https://api.cloud.nextmv.io/v1/applications/whispering-badger/secrets/fluffy-secrets" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  | jq '.'
{
  "id": "fluffy-secrets",
  "application_id": "whispering-badger",
  "name": "Secrets kept by a fluffy bunny",
  "description": "",
  "created_at": "2026-07-29T06:45:23Z",
  "updated_at": "2026-07-29T06:45:23Z",
  "secrets": [
    {
      "type": "env",
      "location": "ACME_LICENSE_KEY",
      "value": "***************"
    },
    {
      "type": "file",
      "location": "config/app.conf",
      "value": "***************"
    }
  ]
}

You can list all collections in the application using the GET /v1/applications/{application_id}/secrets endpoint. The value of the secrets is omitted in the output, as this endpoint returns a list of SecretsCollectionSummary objects. This endpoint is paginated, so the snippet below always uses pagination: it passes pagereturn=true to receive a next_page_token in the response, and keeps requesting pages by passing that token back as pagetoken until no token is returned.

ITEMS="[]"
PAGE_TOKEN=""

while :; do
  RESPONSE=$(curl -s -G "https://api.cloud.nextmv.io/v1/applications/whispering-badger/secrets" \
    -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
    --data-urlencode "pagereturn=true" \
    --data-urlencode "pagetoken=${PAGE_TOKEN}")

  ITEMS=$(jq -n --argjson existing "$ITEMS" --argjson page "$(echo "$RESPONSE" | jq '.items')" '$existing + $page')
  PAGE_TOKEN=$(echo "$RESPONSE" | jq -r '.next_page_token // empty')

  [ -z "$PAGE_TOKEN" ] && break
done

echo "$ITEMS" | jq '.'
[
  {
    "id": "fluffy-secrets",
    "application_id": "whispering-badger",
    "name": "Secrets kept by a fluffy bunny",
    "description": "",
    "created_at": "2026-07-29T06:45:23Z",
    "updated_at": "2026-07-29T06:45:23Z"
  },
  {
    "id": "secrets-ba404179",
    "application_id": "whispering-badger",
    "name": "secrets-ba404179",
    "description": "",
    "created_at": "2026-07-29T06:45:19Z",
    "updated_at": "2026-07-29T06:45:19Z"
  }
]

Update a secrets collection

The PUT endpoint is deprecated

Do not use the PUT /v1/applications/{application_id}/secrets/{collection_id} endpoint. Use PATCH for all updates to a secrets collection.

Use the PATCH /v1/applications/{application_id}/secrets/{collection_id} endpoint to update a secrets collection. You only send the changes you want to make, so you never resend the values of secrets you are not changing. The request payload accepts the following optional fields:

  • name: The new name of the collection.
  • description: The new description of the collection.
  • operations: A list of SecretOperation objects. Each one identifies a secret by its type and location and has an op of create, update, or delete.

The request is applied completely or not at all. To change the location of a secret, delete it and create a new one with its value. You cannot update the ID of a secrets collection.

Here is an example that rotates the value of an existing secret, adds a new secret, and removes another one, all in a single request.

curl -s -X PATCH "https://api.cloud.nextmv.io/v1/applications/whispering-badger/secrets/fluffy-secrets" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "operations": [
      {"op": "update", "type": "env", "location": "ACME_LICENSE_KEY", "value": "xyz789"},
      {"op": "create", "type": "env", "location": "CARROT_KEY", "value": "delicious-vegetables"},
      {"op": "delete", "type": "file", "location": "config/app.conf"}
    ]
  }' \
  | jq '.'
{
  "id": "fluffy-secrets",
  "application_id": "whispering-badger",
  "name": "Secrets kept by a fluffy bunny",
  "description": "",
  "created_at": "2026-07-29T06:45:23Z",
  "updated_at": "2026-07-29T06:46:35Z",
  "secrets": [
    {
      "type": "env",
      "location": "ACME_LICENSE_KEY",
      "value": "***************"
    },
    {
      "type": "env",
      "location": "CARROT_KEY",
      "value": "***************"
    }
  ]
}

Each update to a secrets collection is recorded in its changelog, without any secret values.

Delete a secrets collection

Warning

Deleting a secrets collection is irreversible. All the secrets in the collection will be permanently deleted.

Delete a secrets collection using the DELETE /v1/applications/{application_id}/secrets/{collection_id} endpoint.

curl -s -X DELETE "https://api.cloud.nextmv.io/v1/applications/whispering-badger/secrets/fluffy-secrets" \
  -H "Authorization: Bearer ${NEXTMV_API_KEY}"