| name | beamdrop |
| description | Interact with a Beamdrop file storage server โ upload, download, and manage files via the S3-compatible API. Use when the user wants to store files, create buckets, generate presigned/shareable URLs, manage API keys, or integrate Beamdrop into their project. Covers Go SDK, HTTP API, presigned URL strategies, and error handling. |
Beamdrop File Storage Skill
When to Use This Skill
Use this skill when the user asks to:
- Upload, download, or manage files on a Beamdrop server
- Create, list, or delete storage buckets
- Generate presigned or shareable download URLs
- Manage Beamdrop API keys (create, list, delete, scope permissions)
- Store AI-generated artifacts (code, images, documents, build outputs) on Beamdrop
- Set up Beamdrop integration in their Go, Python, JavaScript, PHP, or any project
- Compare presigned URL types or decide on a file-sharing strategy
- Debug Beamdrop API errors (401, 404, 409, 429)
- Configure Beamdrop for production deployment
Prerequisites
The user needs a running Beamdrop instance with API authentication enabled:
beamdrop -dir /path/to/share -api-auth
They need these environment variables or config values:
BEAMDROP_BASE_URL โ The server URL (e.g., http://localhost:7777)
BEAMDROP_ACCESS_KEY_ID โ API access key (format: BDK_ + 16 hex chars)
BEAMDROP_SECRET_KEY โ API secret key (format: sk_ + 40 hex chars)
If the user doesn't have API keys yet, they can create them:
curl -X POST http://localhost:7777/api/v1/keys
Authentication Details
All S3 API requests at /api/v1/ use HMAC-SHA256 signing:
StringToSign = METHOD + "\n" + PATH + "\n" + RFC3339_TIMESTAMP
Signature = Base64(HMAC-SHA256(StringToSign, SecretKey))
- PATH is the URL path only โ no query string. Example:
/api/v1/buckets/my-bucket
- TIMESTAMP is RFC3339 UTC (e.g.,
2024-01-15T10:30:00Z). Must be within ยฑ15 minutes of server time.
- Base64 is standard encoding (not URL-safe)
Headers required on every request:
Authorization: Bearer BDK_xxxx:SIGNATURE
X-Beamdrop-Date: 2024-01-15T10:30:00Z
API key properties:
permissions: "read", "write", or "read,write"
bucketScope: Optional โ restricts key to a single bucket
expiresAt: Optional โ key auto-expires after this time
disabled: Can be set to temporarily disable without deleting
Go Client SDK
When generating Go code, always use the official client SDK at github.com/ekilie/beamdrop/pkg/client:
import "github.com/ekilie/beamdrop/pkg/client"
c, err := client.New(client.Config{
BaseURL: os.Getenv("BEAMDROP_BASE_URL"),
AccessKeyID: os.Getenv("BEAMDROP_ACCESS_KEY_ID"),
SecretKey: os.Getenv("BEAMDROP_SECRET_KEY"),
})
if err != nil {
log.Fatal(err)
}
Bucket Operations
buckets, err := c.ListBuckets(ctx)
created, err := c.CreateBucket(ctx, "my-bucket")
created, err := c.CreateBucketIfNotExists(ctx, "my-bucket")
exists, err := c.BucketExists(ctx, "my-bucket")
err = c.DeleteBucket(ctx, "my-bucket")
Bucket name rules: 3-63 chars, regex ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$, no IP-like names.
Object Operations
uploaded, err := c.PutObject(ctx, "bucket", "path/to/file.txt", []byte("content"))
f, _ := os.Open("large-file.bin")
defer f.Close()
uploaded, err = c.PutObjectReader(ctx, "bucket", "large-file.bin", f)
obj, err := c.GetObject(ctx, "bucket", "path/to/file.txt")
fmt.Println(string(obj.Body))
fmt.Println(obj.ContentType)
fmt.Println(obj.ETag)
meta, err := c.HeadObject(ctx, "bucket", "path/to/file.txt")
fmt.Printf("Size: %d, Type: %s\n", meta.ContentLength, meta.ContentType)
exists, err := c.ObjectExists(ctx, "bucket", "key")
err = c.DeleteObject(ctx, "bucket", "path/to/file.txt")
list, err := c.ListObjects(ctx, "bucket", client.ListObjectsOptions{
Prefix: "folder/",
Delimiter: "/",
MaxKeys: 100,
})
Object key rules: max 1024 bytes, no .., no leading /. Forward slashes create virtual directory hierarchies.
Max upload size: 5GB. Writes are atomic (crash-safe). ETag = MD5 hex hash of content.
Presigned URLs โ Choosing the Right Type
Beamdrop has two presigned URL mechanisms. Always choose deliberately โ they have different trade-offs:
Client-side HMAC presigned URLs
Generated locally using your secret key. No server API call needed. Self-contained URL.
url, err := c.PresignObjectURL("GET", "bucket", "file.txt", time.Now().Add(24*time.Hour))
Use when:
- You need zero server overhead (URL computed locally)
- Generating many links in a batch
- Embedding in automated emails or notifications
- Temporary access with no tracking needed
Limitations:
- Cannot be revoked (valid until expiry)
- No download counting or limits
- Key rotation invalidates ALL outstanding client-side URLs
- URL is long (contains bucket/key path + query params)
Server-side presigned URLs (RECOMMENDED for most use cases)
Created via API call, stored in database with a short 32-char hex token. Clean /dl/{token} URLs.
presigned, err := c.CreatePresignedURL(ctx, client.CreatePresignedURLRequest{
Bucket: "bucket",
Key: "file.txt",
Method: "GET",
ExpiresIn: int64Ptr(3600),
MaxDownloads: intPtr(10),
})
urls, err := c.ListPresignedURLs(ctx)
details, err := c.GetPresignedURL(ctx, "token")
fmt.Printf("Downloaded %d/%d times\n", details.DownloadCount, *details.MaxDownloads)
err = c.DeletePresignedURL(ctx, "token")
Use when:
- You need to revoke access after sharing
- You want download counting or limits
- You need an audit trail (createdBy, createdAt tracked)
- Link must survive API key rotation
- You want short, clean URLs for users
- Sharing sensitive files
Decision matrix:
| Scenario | Client-side | Server-side |
|---|
| Quick temporary link, no tracking | โ
| |
| Need to revoke after sharing | | โ
|
| Limit number of downloads | | โ
|
| Track download count | | โ
|
| Batch-generate 1000 links | โ
| |
| Share in download portal | | โ
|
| Must survive key rotation | | โ
|
| Sensitive files with audit trail | | โ
|
| Embed in automated emails | โ
| |
HTTP API Quick Reference
When generating code in other languages, use the HTTP API directly with HMAC signing:
Buckets
GET /api/v1/buckets โ {"buckets":[{name, createdAt}], "count":N}
PUT /api/v1/buckets/{name} โ 201 {bucket, created, location} | 409 BUCKET_EXISTS
PUT /api/v1/buckets/{name}?createIfNotExists=true โ 201 (new) or 200 {exists:true} (existed)
HEAD /api/v1/buckets/{name} โ 200 or 404
DELETE /api/v1/buckets/{name} โ 204 | 409 BUCKET_NOT_EMPTY
Objects
PUT /api/v1/buckets/{bucket}/{key} (body = raw bytes) โ 200 {bucket, key, etag, size, url}
GET /api/v1/buckets/{bucket}/{key} โ raw file (headers: Content-Type, Content-Length, ETag, Last-Modified). Supports Range header
HEAD /api/v1/buckets/{bucket}/{key} โ headers only, no body
DELETE /api/v1/buckets/{bucket}/{key} โ 204
GET /api/v1/buckets/{bucket}?list=true&prefix=X&delimiter=/&max-keys=N โ {contents, commonPrefixes, isTruncated}
Presigned URLs
POST /api/v1/presign (JSON: {bucket, key, method, expires_in, max_downloads}) โ 201 {token, url, ...}
GET /api/v1/presign โ {urls, count}
GET /api/v1/presign/{token} โ presigned URL details with current downloadCount
DELETE /api/v1/presign/{token} โ 200 (immediate revocation)
GET /dl/{token} โ public download (no auth needed). 404 if expired/revoked/max-reached
API Keys
POST /api/v1/keys (JSON: {name, permissions, bucket_scope}) โ 201 {accessKeyId, secretKey, ...} โ secret shown ONCE
GET /api/v1/keys โ {keys, count} โ no secrets
DELETE /api/v1/keys?accessKeyId=BDK_xxxx โ 204
Error Handling
All errors return consistent JSON: {"error":{"code":"CODE","category":"CATEGORY","message":"...","details":{}}}
Handle these errors in generated code:
| HTTP | Code | What to Do |
|---|
| 400 | INVALID_BUCKET_NAME | Fix bucket name: 3-63 chars, lowercase a-z0-9 + hyphens/dots, start/end with letter/digit |
| 400 | INVALID_OBJECT_KEY | Fix key: no .., no leading /, max 1024 bytes |
| 401 | UNAUTHORIZED | Check: (1) API key exists and not disabled, (2) timestamp within ยฑ15 min, (3) signing path matches request path |
| 403 | PERMISSION_DENIED | API key lacks required permission or bucket scope doesn't match |
| 404 | BUCKET_NOT_FOUND | Create bucket first with CreateBucketIfNotExists |
| 404 | OBJECT_NOT_FOUND | Object doesn't exist โ check key spelling, verify bucket |
| 409 | BUCKET_EXISTS | Use ?createIfNotExists=true to avoid this |
| 409 | BUCKET_NOT_EMPTY | Delete all objects before deleting bucket |
| 413 | FILE_TOO_LARGE | File exceeds 5GB limit โ split or compress |
| 423 | OBJECT_LOCKED | Another operation holds the lock โ retry after brief delay (lock timeout is 30s) |
| 429 | RATE_LIMIT_EXCEEDED | Retry after Retry-After header seconds. Response has X-Retryable: true. General: 100/min, upload: 10/min |
| 507 | STORAGE_FULL | Server reached -max-storage limit โ cannot upload |
Go client error handling pattern:
result, err := c.GetObject(ctx, "bucket", "key")
if err != nil {
var apiErr *client.APIError
if errors.As(err, &apiErr) {
switch apiErr.Code {
case "OBJECT_NOT_FOUND":
case "RATE_LIMIT_EXCEEDED":
time.Sleep(time.Duration(apiErr.RetryAfter) * time.Second)
}
}
}
Common Workflows
Store AI-generated artifacts
c.CreateBucketIfNotExists(ctx, "ai-artifacts")
key := fmt.Sprintf("generations/%s/%s", sessionID, "output.json")
c.PutObject(ctx, "ai-artifacts", key, resultBytes)
presigned, _ := c.CreatePresignedURL(ctx, client.CreatePresignedURLRequest{
Bucket: "ai-artifacts",
Key: key,
Method: "GET",
ExpiresIn: int64Ptr(86400),
})
fmt.Println("Download:", presigned.URL)
Upload and share with download limits
c.CreateBucketIfNotExists(ctx, "shared")
c.PutObject(ctx, "shared", "report.pdf", pdfBytes)
presigned, _ := c.CreatePresignedURL(ctx, client.CreatePresignedURLRequest{
Bucket: "shared",
Key: "report.pdf",
Method: "GET",
ExpiresIn: int64Ptr(7 * 24 * 3600),
MaxDownloads: intPtr(5),
})
List and organize files
list, _ := c.ListObjects(ctx, "bucket", client.ListObjectsOptions{
Delimiter: "/",
})
list, _ = c.ListObjects(ctx, "bucket", client.ListObjectsOptions{
Prefix: "folder1/",
Delimiter: "/",
})
Deduplicate with ETags
meta, _ := c.HeadObject(ctx, "bucket", "file.txt")
if meta.ETag == computeMD5Hex(newContent) {
fmt.Println("Content unchanged, skipping upload")
} else {
c.PutObject(ctx, "bucket", "file.txt", newContent)
}
Scoped API keys for limited access
curl -X POST http://localhost:7777/api/v1/keys \
-H "Content-Type: application/json" \
-d '{"name":"readonly-reports","permissions":"read","bucket_scope":"reports"}'
Set up webhooks for event notifications
curl -X POST http://localhost:7777/api/v1/webhooks \
-H "Authorization: Bearer BDK_xxx:signature" \
-H "X-Beamdrop-Date: 2024-01-15T10:30:00Z" \
-H "Content-Type: application/json" \
-d '{"name":"my-hook","url":"https://example.com/webhook","event_types":["beamdrop.object.*"]}'
Use the built-in MCP server
The MCP server is built into Beamdrop at /mcp:
GET /mcp โ Public discovery (no auth)
POST /mcp โ JSON-RPC 2.0 requests (requires API key auth)
{
"mcpServers": {
"beamdrop": {
"url": "https://your-server.com/mcp",
"headers": {
"Authorization": "Bearer BDK_your_key:signature",
"X-Beamdrop-Date": "ISO-8601-timestamp"
}
}
}
}
16 tools: list_buckets, create_bucket, delete_bucket, bucket_exists, list_objects, put_object, get_object, head_object, delete_object, create_presigned_url, list_presigned_urls, get_presigned_url, delete_presigned_url, list_api_keys, create_api_key, delete_api_key
Webhooks Reference
Beamdrop supports real-time event notifications via HMAC-SHA256 signed webhooks.
API: POST /api/v1/webhooks (create), GET /api/v1/webhooks (list), PATCH /api/v1/webhooks/{id} (update), DELETE /api/v1/webhooks/{id} (delete), POST /api/v1/webhooks/{id}/test (test), GET /api/v1/webhooks/{id}/deliveries (history)
Event types: beamdrop.object.created, .updated, .deleted, beamdrop.bucket.created, .deleted, beamdrop.share.created, .deleted, beamdrop.presign.created, .deleted. Wildcards: beamdrop.object.*, beamdrop.bucket.*, etc.
Signing: v1= + hex(HMAC-SHA256(timestamp + "\n" + delivery_id + "\n" + body, secret)). Headers: X-Beamdrop-Signature, X-Beamdrop-Webhook-Id, X-Beamdrop-Event, X-Beamdrop-Delivery-Id, X-Beamdrop-Timestamp
Delivery: Max 8 retries, exponential backoff (2s-15min), 10s timeout, retryable codes: 408/425/429/5xx
Validation Rules Summary
| Rule | Constraint |
|---|
| Bucket name length | 3-63 characters |
| Bucket name regex | ^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$ |
| Bucket name chars | Lowercase a-z, digits 0-9, hyphens, dots |
| Bucket name start/end | Must be letter or digit |
| IP-like bucket names | Rejected (e.g., 192.168.1.1) |
| Object key max length | 1024 bytes |
| Object key no-go patterns | Empty, contains .., starts with / |
| Max upload size | 5GB (5,242,880,000 bytes) |
| HMAC clock skew tolerance | ยฑ15 minutes |
| Object lock timeout | 30 seconds |
| Rate limit โ general | 100 req/min per IP |
| Rate limit โ upload | 10 req/min per IP |
| Rate limit โ auth | 5 req/min per IP |