API Referenceļ
Complete REST API reference for PutPlace server.
Base URLļ
http://localhost:8000
For production, use your serverās domain:
https://putplace.example.com
Authenticationļ
All endpoints except /, /health, /api/register, and /api/login require authentication.
PutPlace supports two authentication methods:
Method 1: JWT Bearer Token (Recommended)ļ
Login with username/password to get a JWT token:
# 1. Login to get token
curl -X POST http://localhost:8000/api/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "your-password"}'
# Returns: {"access_token": "eyJ0eXAi...", "token_type": "bearer"}
# 2. Use token in Authorization header
curl -H "Authorization: Bearer eyJ0eXAi..." http://localhost:8000/api/my_files
Method 2: API Key Header (Advanced)ļ
For backwards compatibility and advanced use cases:
X-API-Key: your-api-key-here
Example:
curl -H "X-API-Key: a1b2c3d4e5f6..." http://localhost:8000/api_keys
Interactive Documentationļ
PutPlace provides automatic interactive API documentation:
Swagger UI: http://localhost:8000/docs
ReDoc: http://localhost:8000/redoc
Endpointsļ
Health Endpointsļ
GET /ļ
Root endpoint - Returns basic API information.
Authentication: Not required
Response:
{
"message": "PutPlace API - File Metadata Storage",
"status": "running"
}
Example:
curl http://localhost:8000/
GET /healthļ
Health check endpoint with database connectivity check.
Authentication: Not required
Response (Healthy):
{
"status": "healthy",
"database": {
"status": "connected",
"type": "mongodb"
}
}
Response (Degraded):
{
"status": "degraded",
"database": {
"status": "disconnected",
"type": "mongodb"
}
}
Example:
curl http://localhost:8000/health
File Endpointsļ
POST /put_fileļ
Store file metadata in database and determine if file upload is needed.
Authentication: Required
Request Body:
{
"filepath": "/var/www/html/index.html",
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"sha256": "a1b2c3d4e5f6...",
"file_size": 1234,
"file_mode": 33188,
"file_uid": 33,
"file_gid": 33,
"file_mtime": 1609459200.0,
"file_atime": 1609459200.0,
"file_ctime": 1609459200.0,
"is_symlink": false,
"link_target": null
}
Response (Upload Required):
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k1",
"filepath": "/var/www/html/index.html",
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"sha256": "a1b2c3d4e5f6...",
"file_size": 1234,
"file_mode": 33188,
"file_uid": 33,
"file_gid": 33,
"file_mtime": 1609459200.0,
"file_atime": 1609459200.0,
"file_ctime": 1609459200.0,
"is_symlink": false,
"link_target": null,
"has_file_content": false,
"file_uploaded_at": null,
"upload_required": true,
"upload_url": "/upload_file/a1b2c3d4e5f6..."
}
Response (Upload Not Required):
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k1",
...
"upload_required": false,
"upload_url": null
}
Status Codes:
201 Created- Metadata stored successfully400 Bad Request- Invalid request body401 Unauthorized- Missing or invalid authentication500 Internal Server Error- Database error
Example:
curl -X POST http://localhost:8000/put_file \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"filepath": "/var/www/html/index.html",
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"sha256": "abc123...",
"file_size": 1234,
"file_mode": 33188,
"file_uid": 33,
"file_gid": 33,
"file_mtime": 1609459200.0,
"file_atime": 1609459200.0,
"file_ctime": 1609459200.0,
"is_symlink": false
}'
Deduplication Logic:
If server already has file content for this SHA256:
upload_required=falseIf this is a new file:
upload_required=true, client should call/upload_file/{sha256}
POST /upload_file/{sha256}ļ
Upload actual file content for previously registered metadata.
Authentication: Required
Path Parameters:
sha256(string, required): SHA256 hash of the file (64 characters)
Query Parameters:
hostname(string, required): Hostname where file is locatedfilepath(string, required): Full path to the file
Request Body:
Multipart form data with file upload
Field name:
file
Response:
{
"message": "File uploaded successfully",
"sha256": "a1b2c3d4e5f6...",
"file_size": 1234,
"hostname": "web-server-01",
"filepath": "/var/www/html/index.html"
}
Status Codes:
200 OK- File uploaded successfully400 Bad Request- Invalid SHA256 or hash mismatch401 Unauthorized- Missing or invalid authentication404 Not Found- No metadata found for this file500 Internal Server Error- Storage error
Example:
curl -X POST "http://localhost:8000/upload_file/abc123...?hostname=web-server-01&filepath=/var/www/html/index.html" \
-H "X-API-Key: your-api-key" \
-F "file=@/path/to/local/file.txt"
Validation:
Server calculates SHA256 of uploaded content
If calculated hash doesnāt match provided SHA256:
400 Bad Request
Storage:
File stored using configured backend (local or S3)
Database updated with
has_file_content=trueandfile_uploaded_attimestamp
GET /get_file/{sha256}ļ
Retrieve file metadata by SHA256 hash.
Authentication: Required
Path Parameters:
sha256(string, required): SHA256 hash of the file (64 characters)
Response:
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k1",
"filepath": "/var/www/html/index.html",
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"sha256": "a1b2c3d4e5f6...",
"file_size": 1234,
"file_mode": 33188,
"file_uid": 33,
"file_gid": 33,
"file_mtime": 1609459200.0,
"file_atime": 1609459200.0,
"file_ctime": 1609459200.0,
"is_symlink": false,
"link_target": null,
"has_file_content": true,
"file_uploaded_at": "2025-01-15T10:31:00Z"
}
Status Codes:
200 OK- File found400 Bad Request- Invalid SHA256 format401 Unauthorized- Missing or invalid authentication404 Not Found- File not found500 Internal Server Error- Database error
Example:
curl -H "X-API-Key: your-api-key" \
http://localhost:8000/get_file/abc123...
Note: This returns the FIRST matching file. If multiple files with the same SHA256 exist (duplicates on different hosts/paths), only one is returned. Use /api/clones/{sha256} to get all files with the same hash.
GET /api/clones/{sha256}ļ
Get all files with identical SHA256 hash across all users (clone detection).
Authentication: Required (JWT token)
Path Parameters:
sha256(string, required): SHA256 hash of the file (64 characters)
Response:
[
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k1",
"filepath": "/var/www/html/index.html",
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"sha256": "a1b2c3d4e5f6...",
"file_size": 1234,
"file_mode": 33188,
"file_uid": 1000,
"file_gid": 1000,
"file_mtime": 1705318200.0,
"file_atime": 1705320000.0,
"file_ctime": 1705316400.0,
"has_file_content": true,
"file_uploaded_at": "2025-01-15T10:31:00Z",
"created_at": "2025-01-15T10:30:00Z",
"uploaded_by_user_id": "user123"
},
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k2",
"filepath": "/backup/html/index.html",
"hostname": "backup-server",
"ip_address": "192.168.1.101",
"sha256": "a1b2c3d4e5f6...",
"file_size": 1234,
"file_mode": 33188,
"file_uid": 1001,
"file_gid": 1001,
"file_mtime": 1705318300.0,
"file_atime": 1705320100.0,
"file_ctime": 1705316500.0,
"has_file_content": false,
"file_uploaded_at": null,
"created_at": "2025-01-15T10:35:00Z",
"uploaded_by_user_id": "user456"
}
]
Status Codes:
200 OK- Success (returns empty array if no files found)400 Bad Request- Invalid SHA256 format401 Unauthorized- Missing or invalid JWT token500 Internal Server Error- Database error
Example:
curl -H "Authorization: Bearer your-jwt-token" \
http://localhost:8000/api/clones/abc123...
Sorting:
Files are sorted with āepoch fileā (first uploaded with content) first
Files with content are prioritized over metadata-only files
Among files with content, earliest upload time comes first
Use Cases:
Deduplication Discovery: Find all locations where identical file exists
Epoch File Identification: Identify the canonical copy of a file
Cross-User File Discovery: Find file content uploaded by other users
Storage Optimization: Identify duplicate files for cleanup
GET /api/my_filesļ
Get all files uploaded by the current user.
Authentication: Required (JWT token)
Query Parameters:
limit(integer, optional): Maximum number of files to return (default: 100)skip(integer, optional): Number of files to skip for pagination (default: 0)
Response:
[
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k1",
"filepath": "/var/www/html/index.html",
"hostname": "web-server-01",
"ip_address": "192.168.1.100",
"sha256": "a1b2c3d4e5f6...",
"file_size": 1234,
"file_mode": 33188,
"file_uid": 1000,
"file_gid": 1000,
"file_mtime": 1705318200.0,
"file_atime": 1705320000.0,
"file_ctime": 1705316400.0,
"has_file_content": true,
"file_uploaded_at": "2025-01-15T10:31:00Z",
"created_at": "2025-01-15T10:30:00Z",
"uploaded_by_user_id": "user123"
}
]
Status Codes:
200 OK- Success (returns empty array if no files found)401 Unauthorized- Missing or invalid JWT token500 Internal Server Error- Database error
Example:
# Get first 100 files
curl -H "Authorization: Bearer your-jwt-token" \
http://localhost:8000/api/my_files
# Get next 100 files (pagination)
curl -H "Authorization: Bearer your-jwt-token" \
"http://localhost:8000/api/my_files?limit=100&skip=100"
Sorting:
Files are sorted by creation time (most recent first)
Authentication Endpointsļ
POST /api_keysļ
Create a new API key.
Authentication: Required (existing API key needed)
Note: For creating the first API key, use the bootstrap script:
python -m putplace.scripts.create_api_key
Request Body:
{
"name": "web-server-02",
"description": "API key for web server 02"
}
Response:
{
"api_key": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6...",
"_id": "65a1b2c3d4e5f6g7h8i9j0k1",
"name": "web-server-02",
"description": "API key for web server 02",
"created_at": "2025-01-15T10:00:00Z",
"is_active": true
}
Status Codes:
201 Created- API key created successfully400 Bad Request- Invalid request body401 Unauthorized- Missing or invalid authentication500 Internal Server Error- Database error
Example:
curl -X POST http://localhost:8000/api_keys \
-H "X-API-Key: your-existing-api-key" \
-H "Content-Type: application/json" \
-d '{
"name": "web-server-02",
"description": "API key for production web server 02"
}'
Important: The api_key field in the response is the ONLY time the actual API key is shown. Save it immediately!
GET /api_keysļ
List all API keys (without showing actual keys).
Authentication: Required
Response:
[
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k1",
"name": "web-server-01",
"description": "API key for web server 01",
"created_at": "2025-01-15T09:00:00Z",
"last_used_at": "2025-01-15T11:30:00Z",
"is_active": true
},
{
"_id": "65a1b2c3d4e5f6g7h8i9j0k2",
"name": "web-server-02",
"description": "API key for web server 02",
"created_at": "2025-01-15T10:00:00Z",
"last_used_at": null,
"is_active": true
}
]
Status Codes:
200 OK- Success401 Unauthorized- Missing or invalid authentication500 Internal Server Error- Database error
Example:
curl -H "X-API-Key: your-api-key" \
http://localhost:8000/api_keys
PUT /api_keys/{key_id}/revokeļ
Revoke (deactivate) an API key without deleting it.
Authentication: Required
Path Parameters:
key_id(string, required): MongoDB ObjectId of the API key
Response:
{
"message": "API key 65a1b2c3d4e5f6g7h8i9j0k1 revoked successfully"
}
Status Codes:
200 OK- API key revoked401 Unauthorized- Missing or invalid authentication404 Not Found- API key not found500 Internal Server Error- Database error
Example:
curl -X PUT http://localhost:8000/api_keys/65a1b2c3d4e5f6g7h8i9j0k1/revoke \
-H "X-API-Key: your-api-key"
Effect: The key is marked as is_active=false and can no longer be used for authentication. Metadata is retained for audit purposes.
DELETE /api_keys/{key_id}ļ
Permanently delete an API key.
Authentication: Required
Path Parameters:
key_id(string, required): MongoDB ObjectId of the API key
Response:
{
"message": "API key 65a1b2c3d4e5f6g7h8i9j0k1 deleted successfully"
}
Status Codes:
200 OK- API key deleted401 Unauthorized- Missing or invalid authentication404 Not Found- API key not found500 Internal Server Error- Database error
Example:
curl -X DELETE http://localhost:8000/api_keys/65a1b2c3d4e5f6g7h8i9j0k1 \
-H "X-API-Key: your-api-key"
Warning: This permanently deletes the key. Consider using /api_keys/{key_id}/revoke instead to retain audit trail.
Data Modelsļ
FileMetadataļ
File metadata submitted by client.
{
"filepath": "string (required)",
"hostname": "string (required)",
"ip_address": "string (required)",
"sha256": "string (required, 64 chars)",
"file_size": "integer (required)",
"file_mode": "integer (required)",
"file_uid": "integer (required)",
"file_gid": "integer (required)",
"file_mtime": "float (required)",
"file_atime": "float (required)",
"file_ctime": "float (required)",
"is_symlink": "boolean (required)",
"link_target": "string or null (required)"
}
Field Descriptions:
filepath- Full path to the filehostname- Hostname where file is locatedip_address- IP address of the hostsha256- SHA256 hash of file content (64 hex characters)file_size- File size in bytesfile_mode- Unix file mode/permissions (integer, e.g., 33188 for rw-rārā)file_uid- File owner user ID (integer)file_gid- File group ID (integer)file_mtime- Last modification time (Unix timestamp)file_atime- Last access time (Unix timestamp)file_ctime- Creation/metadata change time (Unix timestamp)is_symlink- Whether file is a symbolic linklink_target- Target path if symlink, null otherwise
FileMetadataResponseļ
File metadata returned from server (includes server-generated fields).
{
"_id": "string (MongoDB ObjectId)",
"filepath": "string",
"hostname": "string",
"ip_address": "string",
"sha256": "string",
"file_size": "integer",
"file_mode": "integer",
"file_uid": "integer",
"file_gid": "integer",
"file_mtime": "float",
"file_atime": "float",
"file_ctime": "float",
"is_symlink": "boolean",
"link_target": "string or null",
"has_file_content": "boolean",
"file_uploaded_at": "datetime or null"
}
Additional Fields:
_id- MongoDB document IDhas_file_content- Whether server has the actual file contentfile_uploaded_at- Timestamp when file was uploaded (null if not uploaded)
FileMetadataUploadResponseļ
Response from POST /put_file (includes upload requirement info).
{
"_id": "string",
"filepath": "string",
"hostname": "string",
"ip_address": "string",
"sha256": "string",
"file_size": "integer",
"file_mode": "integer",
"file_uid": "integer",
"file_gid": "integer",
"file_mtime": "float",
"file_atime": "float",
"file_ctime": "float",
"is_symlink": "boolean",
"link_target": "string or null",
"has_file_content": "boolean",
"file_uploaded_at": "datetime or null",
"upload_required": "boolean",
"upload_url": "string or null"
}
Additional Fields:
upload_required- Whether client should upload file contentupload_url- URL to upload file to (null if upload not required)
APIKeyCreateļ
Request body for creating API key.
{
"name": "string (required, 1-100 chars)",
"description": "string or null (optional)"
}
APIKeyResponseļ
Response when creating API key (includes actual key).
{
"api_key": "string (64 hex chars)",
"_id": "string (MongoDB ObjectId)",
"name": "string",
"description": "string or null",
"created_at": "datetime",
"is_active": "boolean"
}
Warning: This is the ONLY time api_key is shown. Save it immediately!
APIKeyInfoļ
API key metadata (without actual key).
{
"_id": "string (MongoDB ObjectId)",
"name": "string",
"description": "string or null",
"created_at": "datetime",
"last_used_at": "datetime or null",
"is_active": "boolean"
}
Error Responsesļ
All errors follow a consistent format:
{
"detail": "Error message"
}
Common Error Codesļ
400 Bad Request:
Invalid request body
Invalid SHA256 format
SHA256 hash mismatch during upload
401 Unauthorized:
Missing X-API-Key header
Invalid API key
Revoked API key
404 Not Found:
File not found
API key not found
500 Internal Server Error:
Database connection failure
Storage backend error
Unexpected server error
Error Examplesļ
Missing API Key:
{
"detail": "Not authenticated"
}
Invalid API Key:
{
"detail": "Invalid API key"
}
File Not Found:
{
"detail": "File with SHA256 abc123... not found"
}
SHA256 Mismatch:
{
"detail": "File content SHA256 (def456...) does not match provided hash (abc123...)"
}
Rate Limitingļ
PutPlace does not currently implement rate limiting. Consider implementing this at the reverse proxy level (nginx, traefik) or using a service like Cloudflare.
Example nginx rate limiting:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location / {
limit_req zone=api burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
}
Best Practicesļ
1. Always Check upload_requiredļ
After POST /put_file, check the upload_required field:
response = post("/put_file", json=metadata)
if response["upload_required"]:
upload_url = response["upload_url"]
upload_file(upload_url, file_path)
else:
print("File already exists, skipping upload")
2. Verify SHA256 Before Uploadļ
Calculate SHA256 client-side before uploading:
import hashlib
def calculate_sha256(filepath):
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
sha256.update(chunk)
return sha256.hexdigest()
3. Handle Authentication Errorsļ
Retry with exponential backoff on 401 errors:
import time
max_retries = 3
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=data)
if response.status_code == 401:
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
break
4. Use Separate API Keysļ
Create separate API keys for each client/environment:
# Development
curl -X POST .../api_keys -d '{"name": "dev-server-01"}'
# Staging
curl -X POST .../api_keys -d '{"name": "staging-server-01"}'
# Production
curl -X POST .../api_keys -d '{"name": "prod-server-01"}'
5. Monitor last_used_atļ
Regularly check API key usage:
curl -H "X-API-Key: admin-key" http://localhost:8000/api_keys
Revoke unused keys:
curl -X PUT http://localhost:8000/api_keys/{key_id}/revoke \
-H "X-API-Key: admin-key"
Python Client Exampleļ
import hashlib
import httpx
class PutPlaceClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.headers = {"X-API-Key": api_key}
def calculate_sha256(self, filepath: str) -> str:
"""Calculate SHA256 hash of file."""
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
sha256.update(chunk)
return sha256.hexdigest()
def put_file(self, metadata: dict) -> dict:
"""Send file metadata to server."""
url = f"{self.base_url}/put_file"
response = httpx.post(url, json=metadata, headers=self.headers)
response.raise_for_status()
return response.json()
def upload_file(self, sha256: str, hostname: str, filepath: str, file_path: str):
"""Upload file content to server."""
url = f"{self.base_url}/upload_file/{sha256}"
params = {"hostname": hostname, "filepath": filepath}
with open(file_path, "rb") as f:
files = {"file": f}
response = httpx.post(url, params=params, files=files, headers=self.headers)
response.raise_for_status()
return response.json()
def scan_and_upload(self, filepath: str, hostname: str):
"""Scan file and upload if needed."""
import os
from datetime import datetime
# Get file stats
stat = os.stat(filepath)
sha256 = self.calculate_sha256(filepath)
# Prepare metadata
metadata = {
"filepath": filepath,
"hostname": hostname,
"ip_address": "127.0.0.1",
"sha256": sha256,
"file_size": stat.st_size,
"file_mode": stat.st_mode,
"file_uid": stat.st_uid,
"file_gid": stat.st_gid,
"file_mtime": stat.st_mtime,
"file_atime": stat.st_atime,
"file_ctime": stat.st_ctime,
"is_symlink": os.path.islink(filepath),
"link_target": os.readlink(filepath) if os.path.islink(filepath) else None,
}
# Send metadata
response = self.put_file(metadata)
# Upload if required
if response["upload_required"]:
print(f"Uploading {filepath}...")
self.upload_file(sha256, hostname, filepath, filepath)
else:
print(f"File {filepath} already exists, skipping upload")
return response
# Usage
client = PutPlaceClient("http://localhost:8000", "your-api-key")
result = client.scan_and_upload("/path/to/file.txt", "my-laptop")
Next Stepsļ
Client Guide - Using the command-line client
Authentication Guide - JWT tokens and API keys
File Upload Workflow - Understanding the upload process
Configuration - Server and client configuration