🔑 Create S3 Keys
S3 credentials (an access key + secret key pair) authenticate every request to the S3 API. You create them yourself using your existing Lighthouse API key — one call, no dashboard round-trip. An account can hold multiple keys at once (up to 10 active), each labelled and independently revocable — one for production, one for staging, one per app.
Your Lighthouse API key is a bearer token; S3 clients instead sign every request with a secret that never travels over the wire (AWS Signature V4). That's what makes presigned URLs and tamper-proof requests possible. Your API key stays server-side; the S3 keypair is what you put into aws configure, CI, and apps.
Create a key
Send your Lighthouse API key as a Bearer token. The note label is optional but recommended once you have more than one key:
- curl
- Node.js
- Python
curl -X POST https://s3.lighthouse.storage/-/keys \
-H "Authorization: Bearer YOUR_LIGHTHOUSE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"note": "production"}'
const res = await fetch('https://s3.lighthouse.storage/-/keys', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LIGHTHOUSE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ note: 'production' }),
})
const keys = await res.json()
console.log(keys.accessKey, keys.secretKey) // store the secret NOW
import os, requests
res = requests.post(
"https://s3.lighthouse.storage/-/keys",
headers={"Authorization": f"Bearer {os.environ['LIGHTHOUSE_API_KEY']}"},
json={"note": "production"},
)
keys = res.json()
print(keys["accessKey"], keys["secretKey"]) # store the secret NOW
Response:
{
"accessKey": "LHAK7F3A9C21D48E55B0",
"secretKey": "3q9Zt...",
"note": "production",
"region": "auto",
"message": "save the secret key now — it cannot be shown again"
}
The secret key is returned only in this response and cannot be recovered afterwards — not by you, not by support. If it's lost, revoke that key and create a new one.
Up to 10 keys can be active at once; creating beyond the cap returns 409 until you revoke one. All of an account's keys see the same buckets — buckets belong to your account, not to an individual key.
Create keys with a session JWT (dApp frontends)
If your app authenticates users with a Lighthouse session JWT instead of an API key, pass the JWT as the Bearer token and the user's permanent API key in the body:
const res = await fetch('https://s3.lighthouse.storage/-/keys', {
method: 'POST',
headers: {
Authorization: `Bearer ${sessionJwt}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ apiKey: userApiKey, note: 'my-app' }),
})
Why both? The JWT proves who the user is, but whatever key is bound at creation becomes the account's upload/billing key — and JWTs expire, so binding one would silently break uploads later. The body apiKey must belong to the same account as the JWT (403 otherwise), so one user can never bind another account's key.
A POST with only a JWT returns 400 with this explanation. A plain API-key Bearer needs no apiKey in the body. Listing and revoking (below) work with a JWT alone.
List your keys
Works with an API key or a JWT. Returns your full key history, newest first — secrets are never included:
curl https://s3.lighthouse.storage/-/keys \
-H "Authorization: Bearer YOUR_API_KEY_OR_JWT"
{
"keys": [
{"accessKey": "LHAK7F3A9C21D48E55B0", "note": "production", "disabled": false, "created": "2026-07-16T09:12:03Z"},
{"accessKey": "LHAK52C1A0B93E7D66F2", "note": "staging", "disabled": false, "created": "2026-07-15T14:02:51Z"},
{"accessKey": "LHAKB339D445CE7BBB26", "note": "old-laptop", "disabled": true, "created": "2026-07-13T17:34:27Z"}
],
"region": "auto"
}
Revoke a key
Revoke a specific key by access key (recommended), or the newest active one (works with an API key or a JWT):
# a specific key
curl -X DELETE https://s3.lighthouse.storage/-/keys/LHAK52C1A0B93E7D66F2 \
-H "Authorization: Bearer YOUR_API_KEY_OR_JWT"
# the newest active key
curl -X DELETE https://s3.lighthouse.storage/-/keys \
-H "Authorization: Bearer YOUR_API_KEY_OR_JWT"
Revocation takes effect within about 30 seconds; requests signed with that key (including its presigned URLs) start failing with InvalidAccessKeyId. Your buckets and files are untouched — they belong to your account, and every other key (and any future key) still reaches them. Revoking also frees a slot under the 10-key cap.
To rotate a key: create the replacement first, roll it out to your apps, then revoke the old one — zero downtime.
Response codes
| Code | Meaning |
|---|---|
201 | Key created — the only time you'll see the secret |
400 | JWT auth without an apiKey in the body |
401 | Lighthouse rejected the token (or the body apiKey) |
403 | The body apiKey belongs to a different account than the JWT |
404 | (on DELETE /-/keys/{accessKey}) no such key on your account |
409 | Active key limit (10) reached — revoke an unused key first |
429 | Slow down — the endpoint is rate-limited |