Authenticate the public API
Create, scope, replace and revoke the product public key used for Mail sending.
Use a product public key to authenticate Mail’s public send and read routes. The platform matches the key against the selected product’s active keys before the request reaches Mail. A key from another product cannot authorize that request.
Create and manage the key under your product’s settings. Its scope selects routes, including public routes of other toolkits; it does not select sending domains. See public access for the platform-wide header, error and lifecycle contract.
Before you start
You need | Why |
|---|---|
A product Owner or Admin | Only those two roles can open Settings → Public Access, where keys are created, rescoped and revoked |
A secret store your backend reads from | The plaintext key is shown once and never again |
A key authorizes the route; the from address still has to be at a verified domain |
The lpk_ prefix
New keys start with lpk_ followed by 32 hexadecimal characters, representing 128 bits of randomness. The prefix identifies the credential as a product public key.
The platform keeps a SHA-256 hash and the first eight characters of the hexadecimal body. The remaining plaintext secret is not stored. The workspace lists a key’s prefix followed by dots; it does not reveal the secret again.
Imported Mail keys keep their existing
lmk_prefix and value. See keys imported from Mail.
Create a key
-
Open Settings → Public Access. You must be a product Owner or Admin.
-
Create a named key. Choose Selected toolkits and select Mail to cover its public send and read routes. The workspace also offers All toolkits. For an individual route such as
POST /emails, use the management API instead; see public access. The creation response shows the plaintext value once. -
Save it to your backend’s secret store. If you lose it, revoke it and create another; neither the workspace nor support can recover a secret from its hash.
Check the result: Settings → Public Access shows the key’s name, toolkit scope and active status. A route-scoped key carries a note that it is limited to specific routes.
What a key covers
The workspace selects All toolkits or Selected toolkits. The management API additionally supports individual routes:
Scope | What it covers |
|---|---|
Every public route of the product | Every public route of every toolkit in the product, including ones added later. |
Every public route of one toolkit | For Mail, both keyed routes below and nothing outside Mail. |
Exactly one route | One toolkit, one method, one path — for example POST /emails. |
A route scope identifies a toolkit, method and path. Edit scope in the workspace operates at toolkit level: saving it for a route-scoped key widens that key’s grant. Use the management API to preserve or change an individual-route scope. Any key covering POST /emails can send from every verified domain of the product. Give each integration its own key and scope it to the route it needs. This limits the routes an exposed key can reach and lets you revoke that integration’s credential independently.
Key changes take about thirty seconds
Key creation, scope changes and revocation take effect after the platform refreshes its snapshot, about 30 seconds by default.
A new key can briefly return 401, and a revoked key can continue working until that refresh. Allow one propagation window before checking a change. If your onboarding gives people keys, explain this delay there too.
Replace a key
Replace a key by creating and deploying another, then revoking the original. There is no rotation endpoint or button.
-
Create a replacement with the old key’s scope. Use the management API for an individual-route scope; creating or editing at toolkit level in the workspace would widen it.
-
Allow one propagation window, about 30 seconds, then test a route the new key covers.
For a scope covering
GET /emails/{id}, read a known message. Product-wide, Mail-wide and that single-route scope permit this check. It sends no mail:curl -i https://public.lepsto.com/$PRODUCT_ID/mail/emails/$A_KNOWN_EMAIL_ID \ -H "Authorization: Bearer $NEW_LEPSTO_PUBLIC_KEY"For
POST /emailsalone, test with a deliberate send to a mailbox you control, following send a message. A read check correctly returns403for that scope. The send delivers real email. Keep the scope unchanged and use your own mailbox rather than broadening access for the test.Check the result: the read returns
200, or the send returns201with a message ID. -
Deploy the replacement secret wherever the old key is used. Confirm callers have stopped using the old key.
-
Revoke the original in Settings → Public Access.
Both keys remain valid during deployment, so your application can switch without losing its working credential.
Test the new credential before revoking the old one. Confirming that callers stopped using the old key does not establish that the replacement works.
If the replacement check fails, inspect its response before treating the problem as propagation:
What you get | What it establishes | What to do |
|---|---|---|
401 public_key_required | No key reached the edge at all | Check you sent the Authorization header, and that it carries the Bearer scheme. |
401 public_key_invalid | The key matches no active key of this product. That covers a mistyped or truncated value, a key belonging to another product, a revoked key — and a key created seconds ago that the snapshot has not picked up yet | Check the value and the product first, since those stay broken however long you wait. Only once both are right is waiting out the window and retrying the right response. |
403 public_key_scope | The route you just called is outside this key’s scope. It says nothing about the old key, and nothing about which of the two is wider | Compare the route you called against the scope you gave the key. If the key is send-only and you used the read, that is the expected answer — use the send check above. If the intended scope includes this route, correct it through the management API for route-level access, or Edit scope for toolkit-level access, then allow another propagation window. |
503 public_config_unavailable | No snapshot is loaded yet. Nothing about your key is established either way | Retry. |
Revocation is permanent. Replace a revoked key with a new one if needed. Wait one propagation window before treating the revoked credential as unusable.
Use a key on a request
Send the key as a bearer token in the Authorization header:
Authorization: Bearer lpk_…The platform checks headers in this order: Authorization: Bearer, X-Public-Key, then X-Api-Key. The last is a legacy alias, so existing integrations and older SDK releases using X-Api-Key can keep their imported lmk_ keys. All three accept minted and imported keys. Use Authorization in new code.
Other Authorization schemes are ignored. Query parameters cannot carry the key. Before forwarding the request to Mail, the platform removes all three key headers; Mail does not receive or log them.
The base URL is https://public.lepsto.com/{product_id}/mail. Both routes below require a key:
Route | What it does |
|---|---|
POST /emails | Sends an email. |
GET /emails/{id} | Returns an email and its latest event. |
For example, send from mail.example.com with this request. Replace the example sender, recipient, product ID and key with your own:
curl https://public.lepsto.com/$PRODUCT_ID/mail/emails \
-H "Authorization: Bearer $LEPSTO_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "you@mail.example.com",
"to": "customer@example.com",
"subject": "Welcome",
"html": "<p>Hello</p>"
}'Recipients’ mail clients open tracking and unsubscribe links without a public key. Those URLs use signed tokens instead, so opening the pixel, click link or one-click unsubscribe does not require one of your sending credentials.
When a request is refused for its key
These four responses come from the platform before a request reaches Mail:
Response | Meaning |
|---|---|
401 public_key_required | No key was presented on a route that needs one. |
401 public_key_invalid | The key matches no active key of this product. It was mistyped, it belongs to another product, or it has been revoked and the snapshot has caught up. |
403 public_key_scope | The key is valid, but its scope does not cover this route. |
503 public_config_unavailable | No snapshot has been loaded yet, so the key has not been checked; retry. |
Mail errors cover other causes: message validation, 404 for a missing email, 413 for a request body over the cap and 429 for send rate limits. See send a message.
Store a key
Treat a key covering POST /emails as a sending secret. Anyone with it can send mail signed by any verified domain of your product. An exposed key could therefore send a forged password-reset link from the address your customers recognize.
- Store keys on your backend and load them from a secret store or environment at runtime.
- Keep keys out of browser, mobile and desktop applications: secrets shipped to a device can be extracted. Let those clients call your backend, which holds the key.
- Keep keys out of repositories, issues, chats and support tickets.
- Name a separate key for each system and scope it to the required route.
- If exposure is suspected, revoke the key in Settings → Public Access immediately. It can continue working until the next snapshot refresh.
Keys imported from Mail
Your imported lmk_ key continues to work. Active keys from Mail’s former key store were copied into the product’s public-key list. They now authenticate the public endpoint like newly created lpk_ keys.
- Secret: imported keys retain their original value and
lmk_prefix, so existing integrations need no new configuration or secret deployment. - Headers: existing
Authorization: Bearer,X-Public-KeyandX-Api-Keyintegrations remain compatible, including older SDKs using the legacy alias. Move custom-header integrations to an accepted header; useAuthorization: Bearerfor new code. - Listing: Settings → Public Access marks the key as imported and preserves its name and creation date.
- Initial scope: an imported key covers Mail’s
POST /emailsandGET /emails/{id}, without routes from another toolkit. - Management: an Owner or Admin can change an imported key’s scope or revoke it in Settings → Public Access, with the same propagation delay as minted keys. The secret is never displayed again.
Imported keys no longer have a per-domain restriction. A key formerly limited to one domain can now send from every verified domain of the product. Review keys you gave to contractors, agencies and third-party services: their sending access has widened.
Creating a replacement cannot restore a domain restriction. The narrowest sending scope is POST /emails, and it still permits every verified domain of this product. Scope changes restrict routes only. Reissue a key if you need to control who holds its secret, but do not treat that as a way to restrict its sender domains.
To keep a domain outside a third party’s access, keep it verified in a different product. A domain registered there is invisible to this one. Deleting a domain also stops all of this product’s keys from sending as that domain.
Mail → API keys only reports status
Mail → API keys reports whether a public key is configured and where to manage it. It shows no key list or prefixes and has no create or revoke actions. If the platform cannot answer, the screen points to the management location without claiming a key is present or absent.
Use Settings → Public Access to create, list, edit toolkit scope and revoke keys. Use the management API for route-level scope changes. This applies to imported lmk_ keys and minted lpk_ keys alike.
Revoke in Public Access, and only there
Deleting a key from Mail’s own store does not revoke public access. Imported keys have a separate record in the product’s public-key list, and the platform authenticates against that record. Removing Mail’s copy leaves the public credential working for
POST /emailsandGET /emails/{id}.
The tools mail_apikey_delete and mail_apikey_create still exist, but neither manages the credential checked on public requests. Deleting through mail_apikey_delete leaves the product’s copy active. A key created through mail_apikey_create is absent from the product’s public-key list and cannot authenticate the public endpoint. Update scripts, agent instructions and runbooks that use these tools to grant or remove public sending access.
Revoke public sending access in Settings → Public Access, then allow the propagation window. This changes the record the platform actually checks.
Next steps
- Send your first email: Use the key in a complete first-send workflow.
- Set up a sending domain: Prepare a verified sender domain.
- Send a message: Create sends, batches and scheduled messages.
- Send from TypeScript: Set the header with the TypeScript client.
- Public access: Read the platform-wide key contract.