A self-destructing note API lets your script send a secret to SecureNotes over HTTPS and get back a one-time link instead of the secret itself. With the free SecureNotes REST API, you POST the text, a view limit, a time limit and an optional passcode, then deliver the returned link to the recipient.
Key takeaways
- The SecureNotes REST API is free and creates the same self-destructing notes as the website: text up to 10,000 characters, destroyed after 1 to 10 views or a time limit.
- Your code sends the secret once and gets back a link. Only the link travels through chat, email or tickets.
- Never pass the secret as a command-line argument or log the request body. Read it from an environment variable or stdin.
- Pair every link with a time limit and, for anything sensitive, a passcode delivered through a different channel.
- Handle HTTP 429 with backoff, and check the API docs for exact field names and current rate limits.
What does a self-destructing note API actually do?
A self-destructing note API turns a secret into a link that works once, or a few times, and then dies. Your script sends the text. The service encrypts and stores it and hands back a URL. When the recipient opens it, or when the time limit passes, the link stops working.
That matters because automation is where secrets leak most quietly. An onboarding script that emails a temporary password leaves it in two inboxes forever. A CI job that prints a token leaves it in build logs anyone on the team can scroll. Swap the secret for a one-time link and the thing sitting in those logs expires.
If you searched for a Privnote API, you probably found that there is no official, documented one. The scripts floating around mostly automate the web form, which breaks whenever the page changes. A one-time secret API that is published and documented is the safer thing to build on. For background on what these links can and cannot promise, see what a one-time secret link guarantees.
What you send to the SecureNotes API and what comes back
The SecureNotes API documentation is the source of truth for endpoint URLs, exact field names, accepted values and current rate limits. The concepts map directly onto what the website offers:
- Note text: plain text, up to 10,000 characters. No file attachments.
- View limit: destroy after 1, 3, 5 or 10 views.
- Time limit: destroy after 1 hour, 24 hours, 7 days or 30 days. Combine it with a view limit and whichever comes first wins.
- Passcode (optional): the recipient must enter it to open the note.
- Recipient email (optional): SecureNotes emails the link for you.
- Read notification (optional): SecureNotes emails the sender when the note is opened.
The response gives you the note's link. That link is the only thing your code should pass on. Each note is encrypted with AES-256 using its own random key before it is stored.
On rate limits, don't guess. Read the current numbers in the docs, and write your client so an HTTP 429 response triggers a pause and retry rather than a crash or a tight loop.
How to create a one-time link programmatically, step by step
- Get the secret into your process safely. Read it from an environment variable, a secrets manager or stdin. Never put it on the command line, where it lands in shell history and process listings.
- Paste the text into the request body. Keep it under 10,000 characters. If you are sending a whole config file, consider sending only the values that matter.
- Choose when it self-destructs. For a single human recipient, one view plus a time limit (24 hours or 7 days) is the sensible default.
- Optionally add a passcode. Generate it in the script or agree it in person. Use one for anything that grants access to production, money or personal data.
- Send the link. Post it to the ticket, chat or email, or let SecureNotes email it to the recipient.
- Send the passcode another way. SMS, a phone call, a different chat tool, or said out loud on day one. If link and passcode travel together, the passcode adds nothing.
- Discard the secret from memory and logs. Return only the link from your function, and make sure nothing upstream prints the payload.
Code examples: curl, Python and JavaScript
The examples below use placeholder field names (text, max_views, expires_in, passcode, url) and a SECURENOTES_CREATE_URL variable. Copy the real endpoint and field names from the API docs before you run them.
curl (with jq, so the secret never hits the command line)
# TEMP_PASSWORD comes from the environment, not an argument
jq -n --arg text "$TEMP_PASSWORD" \
'{text: $text, max_views: 1, expires_in: "24h"}' \
| curl -sS -X POST "$SECURENOTES_CREATE_URL" \
-H "Content-Type: application/json" \
--data-binary @-Building the body with jq also handles quotes and newlines in the secret, which hand-written JSON strings often break on.
Python (requests)
import os
import requests
CREATE_URL = os.environ["SECURENOTES_CREATE_URL"] # from the API docs
def create_one_time_link(secret, views=1, expiry="24h", passcode=None):
payload = {"text": secret, "max_views": views, "expires_in": expiry}
if passcode:
payload["passcode"] = passcode
resp = requests.post(CREATE_URL, json=payload, timeout=10)
if resp.status_code == 429:
raise RuntimeError("Rate limited: back off and retry later")
resp.raise_for_status()
return resp.json()["url"] # use the link field named in the docsJavaScript (Node 18+, built-in fetch)
const CREATE_URL = process.env.SECURENOTES_CREATE_URL;
async function createOneTimeLink(secret, { views = 1, expiry = "24h", passcode } = {}) {
const body = { text: secret, max_views: views, expires_in: expiry };
if (passcode) body.passcode = passcode;
const res = await fetch(CREATE_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (res.status === 429) throw new Error("Rate limited, retry later");
if (!res.ok) throw new Error(`Create failed: ${res.status}`);
const data = await res.json();
return data.url; // match the field name in the docs
}All three return only the link. That is deliberate: the function boundary is where the secret stops existing in your code.
Which settings fit which automation?
Most people stick with the strictest option. In SecureNotes data, 69% of notes are set to self-destruct after a single view, and 36% carry a time limit, alone or with a view limit (SecureNotes stats). For automated flows, add the time limit too: links created by a script are easy to forget.
| Use case | Views | Time limit | Passcode | Delivery |
|---|---|---|---|---|
| New-hire first-day password | 1 | 24 hours or 7 days | Yes, given in person | Emailed by SecureNotes to personal address |
| Helpdesk password reset | 1 | 1 hour | Optional, read out on the call | Posted as a ticket reply |
| CI-generated token for an engineer | 1 | 1 hour or 24 hours | Yes | Direct message, passcode by another tool |
| Shared staging credentials for a small group | 3 or 5 | 24 hours | Yes | Group email, passcode by phone |
Only 18% of notes in SecureNotes data use a passcode. For scripted credentials that open real systems, you should be in that minority.
Worked example: new-hire credentials from an onboarding script
Dana runs IT for a 40-person company. Every Monday at 7am, a script pulls that week's new starters from the HR system, creates their accounts with a random temporary password, and needs to get that password to each person before 9am.
The old version emailed the password to the new hire's personal address. Months later those emails were still sitting in inboxes nobody at the company controlled.
Now the script calls the API for each hire with these settings:
- Text: the username, the temporary password and the login URL.
- Views: 1. Only the new hire should ever see it.
- Time limit: 24 hours. If the hire doesn't open it on day one, it dies and IT resets the account.
- Passcode: a short code the script writes to a list for the hiring manager, who reads it out at the welcome meeting.
- Recipient email: the hire's personal address, so SecureNotes sends the link.
- Read notification: on, sent to the IT inbox, so Dana knows who has picked theirs up.
At 9:15, the new hire opens the email, clicks the link and is asked for a passcode. The manager tells them the code. They see their credentials once, log in, and the system forces a password change. The note is gone, the email holds only a dead link, and Dana gets a read notification. For what else belongs in that first-day bundle, see the first ten secrets a new hire needs.
What can still go wrong when you automate one-time links
Logging the request body
Debug logging, HTTP client tracing and CI verbose modes can all print the payload. Check that your logger never records the request, and mask the secret in CI. The OWASP Secrets Management Cheat Sheet covers this in more depth.
Passing the secret as an argument
python send.py --secret hunter2 puts the password in shell history and in the process list. Use environment variables or stdin instead.
Retries that create duplicate notes
If your retry logic fires after a timeout that actually succeeded, you may create two live notes for the same secret. Log which links were sent, not the secrets, and retry carefully.
Link previews
Some chat and email tools fetch links to build previews. Before wiring a flow into a new channel, test with a dummy note to confirm a preview doesn't use up a one-view link.
Forgetting the time limit
A link with only a view limit stays live until someone opens it. In a ticket that a dozen agents can read, that might be a long time. Always set both.
Sending the passcode in the same message
A passcode posted right under the link protects nothing. Use a separate channel every time.
When an API-generated link is the wrong tool
One-time links are for handoffs: one secret, one person, one moment. They are not storage.
- Secrets your app reads at runtime belong in a secrets manager or your platform's secret store, not a note.
- Credentials a team uses every day belong in a password manager with shared vaults. Here is when one-time links beat vaults and when they don't.
- Whole environment files often exceed what you want in a single note and change too often. See how to share an env file with your team.
- Files aren't supported. The API handles text only.
And if you only need to send one secret right now, you don't need code at all: you can create a self-destructing note in the browser with the same settings.
Frequently asked questions
Is the SecureNotes API free to use?
Yes. The SecureNotes REST API is free and public, and creating notes on SecureNotes doesn't require an account or sign-up. The API docs at securenotes.net/api-docs list the endpoint, request fields and current rate limits, so check them before building a high-volume integration like a nightly batch job.
Can I send files through a one-time secret API?
Not with SecureNotes. Notes are text only, up to 10,000 characters, and there are no file attachments. If you need to hand over a certificate or key file, consider whether the recipient can generate it themselves, or use a dedicated secrets manager or vault that supports files and access control.
How do I know when the recipient has opened the note?
Turn on the optional read notification when you create the note, and SecureNotes emails the sender once the note is read. In automated flows, point that notification at a shared IT or helpdesk inbox so the team can see which credentials have been collected and which links went unopened.
Can I use the API from GitHub Actions or another CI system?
Yes, any CI system that can make an HTTPS request can call it. Store the secret in the CI platform's secret store, read it as an environment variable, build the request body without echoing it, and only output the returned link. Mask the value in logs and avoid verbose HTTP tracing in that step.
What happens if a link expires before the recipient opens it?
The link simply stops working and the note is destroyed. Your script or helpdesk process should treat that as a normal outcome: generate a fresh credential, create a new note, and send a new link. Don't resend the old secret, especially if it was a temporary password that should be rotated anyway.