Skip to content

API

The JSON API is served under /api/v1. An interactive OpenAPI browser is available at /api, and the schema itself at /api/openapi.json; both can be turned off with VAULTR_DOCS_ENABLED=false.

Authentication

If VAULTR_API_TOKENS is set, every request needs a bearer token:

curl -H "Authorization: Bearer $VAULTR_TOKEN" localhost:8000/api/v1/projects

Without a valid token the API answers 401.

List projects

GET /api/v1/projects
curl -s localhost:8000/api/v1/projects
{
  "projects": [
    {"name": "prod-myproject", "description": "Production", "vault_id": null},
    {"name": "test-myproject", "description": "Staging", "vault_id": null}
  ]
}

Passphrases are never part of the response.

Encrypt a secret

POST /api/v1/encrypt
Field Type Required Description
project string yes Name of a configured project.
secret string yes The plaintext, encrypted verbatim.
variable_name string no Ansible variable name; adds yaml_snippet to the reply.
curl -s localhost:8000/api/v1/encrypt \
  -H 'Content-Type: application/json' \
  -d '{"project": "prod-myproject", "secret": "s3cr3t", "variable_name": "db_password"}'
{
  "project": "prod-myproject",
  "vault_id": null,
  "vault_text": "$ANSIBLE_VAULT;1.1;AES256\n6430...\n6161",
  "yaml_snippet": "db_password: !vault |\n          $ANSIBLE_VAULT;1.1;AES256\n          6430..."
}

yaml_snippet is null unless variable_name was given.

The plaintext is encrypted byte for byte

Unlike the web UI, the API adds and removes nothing. A trailing \n in secret ends up inside the encrypted value, which matters for things like SSH keys and API tokens. Strip it yourself if you do not want it.

Errors

Status Meaning
401 Missing or invalid bearer token.
404 Unknown project.
413 Secret larger than VAULTR_MAX_SECRET_LENGTH.
422 Malformed body, empty secret or invalid variable_name.

Errors carry a detail string:

{"detail": "unknown project 'nope'"}

Examples

vaultr_encrypt() {
  curl -sf "$VAULTR_URL/api/v1/encrypt" \
    -H "Authorization: Bearer $VAULTR_TOKEN" \
    -H 'Content-Type: application/json' \
    -d "$(jq -n --arg p "$1" --arg s "$2" --arg n "$3" \
          '{project: $p, secret: $s, variable_name: $n}')" \
    | jq -r .yaml_snippet
}

vaultr_encrypt prod-myproject "$(printf %s "$SECRET")" db_password
import httpx

response = httpx.post(
    f"{VAULTR_URL}/api/v1/encrypt",
    headers={"Authorization": f"Bearer {VAULTR_TOKEN}"},
    json={
        "project": "prod-myproject",
        "secret": "s3cr3t",
        "variable_name": "db_password",
    },
)
response.raise_for_status()
print(response.json()["yaml_snippet"])

Re-encrypt a secret

POST /api/v1/reencrypt

Moves an already encrypted secret from one project's passphrase to another's. Vaultr decrypts it with the source project's passphrase and immediately re-encrypts it with the target's; the plaintext is never returned.

Field Type Required Description
source_project string yes The project the secret is currently encrypted for.
target_project string yes The project to encrypt it for instead.
vault_text string yes The $ANSIBLE_VAULT string, or a whole key: !vault \| block.
variable_name string no Ansible variable name; adds yaml_snippet to the reply.
curl -s localhost:8000/api/v1/reencrypt \
  -H 'Content-Type: application/json' \
  -d @- <<'JSON'
{
  "source_project": "test-myproject",
  "target_project": "prod-myproject",
  "vault_text": "$ANSIBLE_VAULT;1.1;AES256\n64303339...",
  "variable_name": "db_password"
}
JSON
{
  "source_project": "test-myproject",
  "target_project": "prod-myproject",
  "vault_id": null,
  "vault_text": "$ANSIBLE_VAULT;1.1;AES256\n3861...",
  "yaml_snippet": "db_password: !vault |\n          $ANSIBLE_VAULT;1.1;AES256\n          3861..."
}

Paste the snippet as it is

vault_text accepts the whole key: !vault | block that Vaultr and ansible-vault encrypt_string produce. The ten space indentation, which Ansible itself rejects, is stripped for you, as are blank lines and \r\n line endings.

The content is re-encrypted byte for byte, so a value that is not valid UTF-8, such as a binary key, survives unchanged.

Errors

Status Meaning
401 Missing or invalid bearer token.
403 Re-encryption is disabled, or the configuration forbids this pair.
404 Unknown source or target project.
413 Input larger than VAULTR_MAX_SECRET_LENGTH.
422 Not a vault string, or it does not belong to source_project.

Re-encryption is a privileged operation

Anyone who can re-encrypt out of a project and knows the target project's passphrase can read that project's secrets. See Security before enabling it broadly.

No decrypt endpoint

There is none, by design. Vaultr exists so that people who must not know a vault passphrase can still produce encrypted values; giving them decryption back would undo exactly that. Decrypt with ansible-vault where the passphrase legitimately lives.

Re-encryption decrypts internally, but the plaintext never leaves the process: it exists only between the decrypt and the re-encrypt call.