API documentation

A REST API for creating, viewing and managing secrets. All requests and responses use JSON, and all encryption and decryption happens on your side, so the plaintext never reaches our servers.

Overview

The base URL for all API endpoints is:

URL
https://password.link/api

You can use the API to handle both encryption and decryption of the secret outside of our service, without needing to load any assets from us. It also allows you to use any kind of delivery method for the public password part (half of the password which is used to derive the encryption key for the secret from) and basically create any kind of custom configuration for viewing the secret.

For working end-to-end code, see the API examples.

Authentication

Every request must be authenticated with an API key, sent in the Authorization header:

HTTP
Authorization: ApiKey public_key_abcd...

There are two kinds of API keys:

Key type Description
Public API key Meant for creating a custom self-hosted view secret page. Can be used in public scripts. Only allows viewing and acknowledging secrets.
Private API key Required for all other API actions. Must always be kept private.

Both API keys contain the type of the key in the key itself so they can be easily distinguished.

Each endpoint below shows which key type it requires.

Errors

When a request fails, the response contains an error object:

JSON
{
  "data": null,
  "metadata": null,
  "error": {
    "message": "Ciphertext can't be blank",
    "field": "ciphertext"
  }
}
Field Description
message The full error message.
field The field the error is about, when the error is a validation error.

The HTTP response status code also indicates the type of the error:

Status code Description
403 Forbidden The request was not allowed, for example because of an invalid API key.
404 Not Found The requested resource was not found.
422 Unprocessable Content The request was understood but failed validation, for example because of a missing or invalid parameter.

Encrypting secrets

Secrets are encrypted client-side before they are sent to the API. This section describes the password and ciphertext formats the service expects.

The password parts

The password which is used to encrypt the secret in AES must consist of two 18 character long strings. The actual encryption key will be the concatenation of these two strings ("private part" + "public part") and is derived from the password using PBKDF2.

Example
Password: fkgjbnvlakwiejgutnFIGKEOTIRUBNAKQJRL
  => Private part: fkgjbnvlakwiejgutn
  => Public part:  FIGKEOTIRUBNAKQJRL

The private part is sent to the API (Base64 encoded) and stored with the secret. The public part is never sent to the API, and is usually delivered in the link itself, so we can never decrypt the secret ourselves.

If you use the view secret page provided by us, you must also Base64 encode the public part. If you use a self-hosted view secret page, you can use the same method or create your own. Please see the API examples for reference implementations.

Ciphertext format

The ciphertext is a JSON object, encoded in Base64:

JSON
{
  "cipher": "<Base64 encoded AES-GCM ciphertext>",
  "iv": "<Base64 encoded 12-byte initialization vector>",
  "salt": "<Base64 encoded 16-byte PBKDF2 salt>",
  "iter": 10000
}

You must use the following settings for AES when encrypting the secret:

Setting Value
Mode AES-GCM with a 128-bit authentication tag appended to the ciphertext (the Web Crypto default)
Key size 256 bits
Key derivation PBKDF2 with SHA-256
PBKDF2 iterations The value of the iter field. Allowed range 1000-1000000, we recommend 10000.
Initialization vector 12 random bytes
Salt 16 random bytes

Legacy SJCL format (deprecated)

Ciphertexts created with the Stanford Javascript Crypto Library (SJCL) are still accepted: an SJCL compatible ciphertext JSON string encoded in Base64, using AES-GCM ("mode:gcm"), a 256 bit key ("ks:256") and 10000 PBKDF2 iterations ("iter:10000"). This format is deprecated and support for it will be removed in the future.

Decrypting secrets

The View Secret API returns the ciphertext exactly as it was stored. Secrets created in the web app use the Web Crypto format above. Secrets created by older API clients, the Chrome extension, or still-unopened links may use the legacy SJCL format.

A self-hosted view page must handle both. After Base64-decoding the ciphertext to JSON, check for a ct key: if it is present, decrypt with SJCL; otherwise decrypt with Web Crypto. See the View Secret example.

API clients that only create secrets do not need to change: SJCL ciphertexts are still accepted on create. Clients that decrypt secrets fetched from the API must add Web Crypto support, or they will fail on secrets created in the web app.

Create Secret

POST /api/secrets Private API key

Creates an encrypted secret and returns its ID. The ciphertext must be created as described in Encrypting secrets. Returns 201 Created on success.

Request parameters

Parameter Type Description
ciphertextrequired string A Web Crypto compatible ciphertext JSON string of the secret, encoded in Base64. Legacy SJCL compatible ciphertexts are also accepted (deprecated).
password_part_privaterequired string The private password part which was used to encrypt the secret, encoded in Base64.
password_part_public string The public password part, encoded in Base64. Required only when using email_to so the API can email the hosted secret link.
description string A description for the secret. Cannot be seen when viewing the secret.
message string A message for the secret. Will be shown along the secret.
expiration integer An expiration time for the secret, in hours. Possible values: 1-500.
view_button boolean Show a view secret button instead of showing the secret immediately after opening the link.
captcha boolean Show a simple CAPTCHA before showing the secret, mainly for blocking automated scanners.
password string A password for the secret.
max_views integer How many times the secret can be viewed. Possible values: 1-100.
email_to string Comma-separated email recipients that should receive the created secret link. Requires password_part_public.
otp_email_recipient string Email address that receives a one-time password. When set, the recipient must enter the emailed code before the secret can be viewed.
otp_recipient string Accepted as an alias for otp_email_recipient. Prefer otp_email_recipient for new integrations.
otp_sms_recipient string Phone number that receives a one-time password by SMS, in international format starting with + and no spaces.
allowed_ips string Comma-separated IP addresses or CIDR ranges allowed to view the secret.
allowed_locations array Array of ISO 3166-1 alpha-2 country codes allowed to view the secret.
available_after datetime Date and time before which the secret cannot be viewed.
secret_folder_id string ID of an existing secret folder owned by the API key user.
reply_enabled boolean Enable a one-time reply from the recipient after the secret has been viewed.
ack_required boolean Require recipient acknowledgement before the secret can be viewed.
ack_mode string Acknowledgement mode. Use checkbox or phrase.
ack_phrase string Phrase the recipient must type. Required when ack_required is true and ack_mode is phrase.
ack_disclosure_text string Disclosure text shown to the recipient, up to 500 characters.
ack_collect_name boolean collect the recipient's name with the acknowledgement.
ack_name_required boolean Require the recipient's name. This also enables ack_collect_name.
ack_collect_org boolean collect the recipient's organization with the acknowledgement.
attachment object Metadata for one encrypted file attachment, see Attachments. Requires an active plan.
attachment.file_name string The original file name shown to the recipient. Required when attachment is provided.
attachment.file_type string The original MIME type, for example text/plain or application/pdf. Required when attachment is provided.
attachment.file_size integer The original unencrypted file size in bytes. Required when attachment is provided.

Team Secret Settings can disable, require, or enforce defaults for supported secret options. The API applies those team rules when creating a secret.

Example request

cURL
curl -H "Authorization: ApiKey private_key_abcd" \
     -H "Content-Type: application/json" \
     -X POST https://password.link/api/secrets \
     -d '{
       "ciphertext": "eyJjaXBoZXIiOiJRc3hxsN3...aXRlciI6MTAwMDB9",
       "password_part_private": "ZmtnamJudmxha3dpZWpndXRu",
       "description": "Database credentials",
       "expiration": 24,
       "view_button": true,
       "max_views": 1
     }'

Example response

JSON
{
  "data": {
    "id": "dT5g"
  },
  "metadata": {
    "secrets_total": 70,
    "secrets_usage": 25,
    "secrets_allowance": 100,
    "secret_requests_total": 10,
    "secret_requests_allowance": 100
  }
}

The link to the secret is formed by combining the hosted view page URL, the secret ID and the Base64 encoded public password part:

URL
https://password.link/<id>/#<public password part, Base64>

Attachments

Uploading an attachment is a two-step process: first create the secret with attachment metadata (see Create Secret), then encrypt and upload the encrypted attachment payload to the returned upload URL.

The secret contents and attachment contents are encrypted separately. Both use Web Crypto compatible AES-GCM data, Base64 encoded, but the payload fields are encoded differently: the secret payload stores Base64 encoded fields and an iter field, while the attachment payload stores raw binary string fields.

Encrypting the attachment

Before encryption, represent the file as a data URL:

URL
data:<mime-type>;base64,<base64 file bytes>

The attachment encryption password is the raw private password part concatenated with the raw public password part (no Base64 encoding).

Encrypt that data URL string with AES-GCM using PBKDF2-SHA256, 10000 iterations, a 256-bit derived key, a 16-byte random salt and a 12-byte random IV. Store the encrypted attachment payload as a JSON object, then Base64 encode the JSON string:

JSON
{
  "cipher": "<binary string>",
  "iv": "<binary string>",
  "salt": "<binary string>"
}

Uploading the encrypted file

When an attachment is included, the create secret response contains an upload target in data.attachment.upload:

JSON
{
  "data": {
    "id": "dT5g",
    "attachment": {
      "file_name": "example.txt",
      "upload": {
        "url": "/api/secrets/dT5g/attachment",
        "fields": {},
        "metadata": {}
      }
    }
  },
  "metadata": {}
}

Upload the Base64 encoded encrypted JSON as text/plain using multipart/form-data to the returned upload.url. Add any returned upload.metadata or upload.fields key/value pairs to the form data before adding the encrypted file. The encrypted file field must be named file.

For Password.link upload URLs, include the same private API key in the Authorization: ApiKey <key> header. Direct storage upload URLs, such as pre-signed S3 URLs, should only receive the returned form fields and the encrypted file field.

Password.link-hosted upload URLs include an empty fields object. Direct storage upload URLs, such as pre-signed S3 URLs, return signing fields in metadata instead.

Attachment size limits depend on the account or team allowance, and the encrypted upload is larger than the original file because the file is converted to Base64 and encrypted.

View Secret

GET /api/secrets/<id> Public API key

Fetches the ciphertext, private password part and other details of a secret. This endpoint can be used to create a custom self-hosted view secret page which does not load any scripts or other assets from our service. Returns 200 OK on success.

Successfully fetching a secret automatically marks it as viewed and so deletes the ciphertext and private password part from our database, making it impossible to view the secret again.

The ciphertext may be the Web Crypto format or the legacy SJCL format. Detect SJCL payloads by a ct key in the decoded JSON. See Decrypting secrets.

Example request

cURL
curl -H "Authorization: ApiKey public_key_abcd" \
     https://password.link/api/secrets/dT5g

Example response

JSON
{
  "data": {
    "id": "dT5g",
    "ciphertext": "eyJjaXBoZXIiOiJRc3hxsN3...aXRlciI6MTAwMDB9",
    "password_part_private": "ZmtnamJudmxha3dpZWpndXRu",
    "message": "Here are the credentials we talked about"
  },
  "metadata": null
}

Acknowledge Secret

POST /api/secrets/<id>/acknowledge Public API key

Submits recipient acknowledgement for a secret that was created with ack_required, and returns the same secret data as View Secret when the acknowledgement is valid. Returns 200 OK on success.

If acknowledgement is required, GET /api/secrets/<id> returns 403 Forbidden with secret_status set to ack_required until this endpoint is used.

Invalid or incomplete acknowledgement payloads also return 403 Forbidden. They are not returned as validation errors.

Request parameters

Parameter Type Description
ack_confirmed boolean Set to true for checkbox acknowledgement.
ack_phrase string Required for phrase acknowledgement, and must match the phrase configured for the secret.
ack_signer_name string Recipient name, required when name collection is enabled and marked as required.
ack_signer_org string Recipient organization, stored when organization collection is enabled.

Example request

cURL
curl -H "Authorization: ApiKey public_key_abcd" \
     -H "Content-Type: application/json" \
     -X POST https://password.link/api/secrets/dT5g/acknowledge \
     -d '{"ack_confirmed": true, "ack_signer_name": "Jane Doe"}'

List Secrets

GET /api/secrets Private API key

Fetches a list of secrets. Does not contain the ciphertexts or private password parts, only IDs, descriptions and the like. Returns 200 OK on success.

The maximum amount of returned results for each query is limited to 50. You can control the offset with the optional offset query parameter, for example: /api/secrets?offset=50

Example request

cURL
curl -H "Authorization: ApiKey private_key_abcd" \
     https://password.link/api/secrets

Example response

JSON
{
  "data": [
    {
      "id": "dT5g",
      "created_at": "2026-07-02T10:00:00.000Z",
      "message": "Here are the credentials we talked about",
      "description": "Database credentials",
      "view_button": true,
      "captcha": false,
      "password": false,
      "expiration": 24,
      "expired": false,
      "view_times": 1,
      "max_views": 1,
      "secret_folder_id": "5e976e6e-d205-4f58-8df9-5ac0048bc702",
      "views": [
        {
          "viewed_at": "2026-07-02T12:34:56.000Z",
          "viewed_by_ip": "192.0.2.10",
          "viewed_by_user_agent": "Mozilla/5.0 ..."
        }
      ]
    }
  ],
  "metadata": {
    "secrets_total": 70,
    "secrets_usage": 25,
    "secrets_allowance": 100,
    "secret_requests_total": 10,
    "secret_requests_allowance": 100
  }
}

Update Secret

PATCH /api/secrets/<id> Private API key

Updates supported settings and metadata for an existing secret. Returns 204 No Content on success.

PATCH does not replace the encrypted secret content, re-send delivery emails, or create attachment upload metadata. Use Create Secret for ciphertext, password parts, link delivery, and attachments.

The following parameters can be updated, with the same meaning as in Create Secret:

description, message, expiration, max_views, view_button, captcha, password, otp_email_recipient, otp_recipient, otp_sms_recipient, allowed_ips, allowed_locations, available_after, secret_folder_id, reply_enabled, ack_required, ack_mode, ack_phrase, ack_disclosure_text, ack_collect_name, ack_name_required, ack_collect_org

ciphertext, password_part_private, password_part_public, email_to and attachment metadata are create-only. They are ignored by PATCH.

Example request

cURL
curl -H "Authorization: ApiKey private_key_abcd" \
     -H "Content-Type: application/json" \
     -X PATCH https://password.link/api/secrets/dT5g \
     -d '{"description": "Updated description", "otp_email_recipient": "recipient@example.com"}'

Delete Secret

DELETE /api/secrets/<id> Private API key

Deletes a secret. Returns 200 OK on success.

Example request

cURL
curl -H "Authorization: ApiKey private_key_abcd" \
     -X DELETE https://password.link/api/secrets/dT5g

Example response

JSON
{
  "data": null,
  "metadata": {
    "secrets_total": 69,
    "secrets_usage": 24,
    "secrets_allowance": 100,
    "secret_requests_total": 10,
    "secret_requests_allowance": 100
  }
}

Create Secret Folder

POST /api/secret_folders Private API key

Creates a secret folder. Returns 201 Created on success.

Request parameters

Parameter Type Description
namerequired string Folder name.
description string Folder description.

Example response

JSON
{
  "data": {
    "id": "5e976e6e-d205-4f58-8df9-5ac0048bc702"
  },
  "metadata": {
    "secrets_total": 70,
    "secrets_usage": 25,
    "secrets_allowance": 100,
    "secret_requests_total": 10,
    "secret_requests_allowance": 100
  }
}

List Secret Folders

GET /api/secret_folders Private API key

Fetches a list of secret folders. Returns 200 OK on success.

Example response

JSON
{
  "data": [
    {
      "id": "5e976e6e-d205-4f58-8df9-5ac0048bc702",
      "name": "Clients",
      "description": "Client credentials",
      "created_at": "2026-07-02T10:00:00.000Z",
      "updated_at": "2026-07-02T10:00:00.000Z"
    }
  ],
  "metadata": {
    "secrets_total": 70,
    "secrets_usage": 25,
    "secrets_allowance": 100,
    "secret_requests_total": 10,
    "secret_requests_allowance": 100
  }
}

Update Secret Folder

PATCH /api/secret_folders/<id> Private API key

Updates the name and description of a secret folder. Accepts the same parameters as Create Secret Folder and returns 204 No Content on success.

Delete Secret Folder

DELETE /api/secret_folders/<id> Private API key

Deletes a secret folder. Returns 200 OK on success.

Create Secret Request

POST /api/secret_requests Private API key

Creates a new Secret Request. Returns 201 Created on success.

Request parameters

Parameter Type Description
description string Description for the Secret Request.
message string Message for the Secret Request viewer.
expiration integer Expiration time for the Secret Request, in hours.
limit integer Usage limit for the Secret Request.
send_request_to_email string Send the created Secret Request link to the given email address.
send_request_to_sms string Send the created Secret Request link to the given phone number.
send_to_email string Send the Secret link created using the Secret Request to the given email address.
secret_description string Description for the Secret created using the Secret Request.
secret_message string Message for the Secret created using the Secret Request.
secret_expiration integer Expiration time for the Secret created using the Secret Request, in hours.
secret_password string Password for the Secret created using the Secret Request.
secret_max_views integer View limit for the Secret created using the Secret Request.
template_id string ID of the request template to use.
secret_allowed_emails string Comma-separated email addresses or domains allowed to open Secrets created with this Secret Request.
secret_allowed_ips string Comma-separated IP addresses or CIDR ranges allowed to open Secrets created with this Secret Request.

Example request

cURL
curl -H "Authorization: ApiKey private_key_abcd" \
     -H "Content-Type: application/json" \
     -X POST https://password.link/api/secret_requests \
     -d '{"description": "Request for VPN credentials", "expiration": 48}'

Example response

JSON
{
  "data": {
    "id": "5e976e6e-d205-4f58-8df9-5ac0048bc702"
  },
  "metadata": {
    "secrets_total": 70,
    "secrets_usage": 25,
    "secrets_allowance": 100,
    "secret_requests_total": 10,
    "secret_requests_allowance": 100
  }
}

List Secret Requests

GET /api/secret_requests Private API key

Fetches a list of Secret Requests. Returns 200 OK on success.

The maximum amount of returned results for each query is limited to 50. You can control the offset with the optional offset query parameter, for example: /api/secret_requests?offset=50

The list response does not include secret_allowed_emails or secret_allowed_ips.

Example request

cURL
curl -H "Authorization: ApiKey private_key_abcd" \
     https://password.link/api/secret_requests

Example response

JSON
{
  "data": [
    {
      "id": "5e976e6e-d205-4f58-8df9-5ac0048bc702",
      "description": "Request for VPN credentials",
      "message": "Please send me the VPN credentials",
      "expiration": 48,
      "limit": 1,
      "send_request_to_email": "request-recipient@example.com",
      "send_request_to_sms": "+358401234567",
      "send_to_email": "example@example.com",
      "secret_description": "VPN credentials",
      "secret_message": null,
      "secret_expiration": 24,
      "secret_max_views": 1,
      "secret_password": false,
      "template_id": null
    }
  ],
  "metadata": {
    "secrets_total": 70,
    "secrets_usage": 25,
    "secrets_allowance": 100,
    "secret_requests_total": 10,
    "secret_requests_allowance": 100
  }
}

Delete Secret Request

DELETE /api/secret_requests/<id> Private API key

Deletes a Secret Request. Returns 200 OK on success.

Example request

cURL
curl -H "Authorization: ApiKey private_key_abcd" \
     -X DELETE https://password.link/api/secret_requests/5e976e6e-d205-4f58-8df9-5ac0048bc702