API Reference
Module: Tuner Component: REST API Version: 1.0.0-RELEASE Last Updated: October 26, 2025 Document Type: API Documentation (Developer Reference)
Table of Contents
- API Overview
- Authentication
- Recommendation APIs
- Account Management APIs
- Scheduler APIs
- Error Handling
- Rate Limiting
API Overview
Base URL
Production: https://api.cloudkeeper.com/tuner/v1
Staging: https://api-staging.cloudkeeper.com/tuner/v1
Common Headers
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
Accept: application/json
Response Format
All API responses follow this standard format:
{
"success": true,
"data": { ... },
"error": null,
"timestamp": "2025-10-26T12:00:00Z"
}
Error Response:
{
"success": false,
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "Account ID is required",
"details": { "field": "accountId" }
},
"timestamp": "2025-10-26T12:00:00Z"
}
Authentication
JWT Token
Include JWT token in Authorization header:
GET /api/recommendations?accountId=123456789012
Host: api.cloudkeeper.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Obtaining Token
POST /api/auth/login
Content-Type: application/json
{
"email": "user@company.com",
"password": "********"
}
Response:
{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
"refreshToken": "eyJhbGciOiJSUzI1NiIs...",
"expiresIn": 86400,
"tokenType": "Bearer"
}
}
Recommendation APIs
GET /api/recommendations
Get all recommendations for an account.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
accountId | string | Yes | AWS Account ID (12 digits) |
region | string | No | Filter by AWS region |
category | string | No | Filter by category: Cleaner, OverProvisioned, Modernization, PotentialBenefits |
status | string | No | Filter by status: GENERATED, SNOOZED, IMPLEMENTED, DISMISSED |
minSavings | number | No | Minimum monthly savings ($) |
page | number | No | Page number (default: 1) |
limit | number | No | Items per page (default: 50, max: 200) |
Example Request:
GET /api/recommendations?accountId=123456789012&category=Cleaner&minSavings=100
Authorization: Bearer <token>
Response:
{
"success": true,
"data": {
"recommendations": [
{
"id": "rec_abcd1234",
"accountId": "123456789012",
"resourceId": "snap-0abcd1234",
"resourceName": "backup-2024-01-15",
"service": "EC2",
"category": "Cleaner",
"description": "EBS snapshot is 180 days old and the volume has been deleted",
"action": "Delete EBS snapshot snap-0abcd1234",
"region": "us-east-1",
"currentCost": 15.36,
"recommendedCost": 0,
"monthlySavings": 15.36,
"annualSavings": 184.32,
"status": "GENERATED",
"riskLevel": "LOW",
"metadata": {
"size": 100,
"created": "2024-01-15T10:30:00Z",
"volumeId": "vol-deleted"
},
"createdAt": "2025-10-26T10:00:00Z",
"updatedAt": "2025-10-26T10:00:00Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 5,
"totalItems": 234,
"itemsPerPage": 50
},
"summary": {
"totalRecommendations": 234,
"totalMonthlySavings": 48645.00,
"totalAnnualSavings": 583740.00,
"byCategory": {
"Cleaner": { "count": 102, "savings": 48645.00 },
"OverProvisioned": { "count": 87, "savings": 28087.00 },
"Modernization": { "count": 32, "savings": 21687.00 },
"PotentialBenefits": { "count": 13, "savings": 45280.00 }
}
}
}
}
GET /api/recommendations/{id}
Get a single recommendation by ID.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Recommendation ID |
Example Request:
GET /api/recommendations/rec_abcd1234
Authorization: Bearer <token>
Response: (Same structure as single recommendation above)
POST /api/recommendations/{id}/implement
Mark recommendation as implemented.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Recommendation ID |
Request Body:
{
"implementedBy": "john.doe@company.com",
"implementedAt": "2025-10-26T14:30:00Z",
"notes": "Deleted snapshot via AWS Console"
}
Response:
{
"success": true,
"data": {
"id": "rec_abcd1234",
"status": "IMPLEMENTED",
"implementedBy": "john.doe@company.com",
"implementedAt": "2025-10-26T14:30:00Z"
}
}
POST /api/recommendations/{id}/snooze
Snooze recommendation for a specified duration.
Request Body:
{
"snoozeDuration": 30,
"reason": "Planned for next quarter"
}
Response:
{
"success": true,
"data": {
"id": "rec_abcd1234",
"status": "SNOOZED",
"snoozedUntil": "2025-11-25T12:00:00Z"
}
}
POST /api/recommendations/{id}/dismiss
Permanently dismiss recommendation.
Request Body:
{
"reason": "Resource required for backup purposes"
}
Response:
{
"success": true,
"data": {
"id": "rec_abcd1234",
"status": "DISMISSED"
}
}
POST /api/recommendations/refresh
Trigger recommendation regeneration for an account/region.
Request Body:
{
"accountId": "123456789012",
"region": "us-east-1",
"factorKeys": ["OVER_PROVISIONED_EC2", "SNAPSHOT_CLEANUP"]
}
Response:
{
"success": true,
"data": {
"jobId": "job_xyz789",
"status": "QUEUED",
"estimatedCompletionTime": "2025-10-26T15:00:00Z"
}
}
Account Management APIs
GET /api/accounts
Get all linked AWS accounts.
Response:
{
"success": true,
"data": {
"accounts": [
{
"accountId": "123456789012",
"accountName": "Production",
"roleArn": "arn:aws:iam::123456789012:role/CloudKeeperTunerRole",
"externalId": "uuid-12345-abcde",
"status": "ACTIVE",
"regions": ["us-east-1", "us-west-2", "eu-west-1"],
"lastSyncAt": "2025-10-26T10:00:00Z",
"linkedAt": "2025-01-15T09:00:00Z"
}
]
}
}
POST /api/accounts
Link a new AWS account.
Request Body:
{
"accountId": "987654321098",
"accountName": "Development",
"roleArn": "arn:aws:iam::987654321098:role/CloudKeeperTunerRole",
"externalId": "uuid-67890-fghij",
"regions": ["us-east-1"]
}
Response:
{
"success": true,
"data": {
"accountId": "987654321098",
"status": "VERIFYING",
"verificationJobId": "verify_abc123"
}
}
POST /api/accounts/{accountId}/verify
Verify AWS account access.
Response:
{
"success": true,
"data": {
"accountId": "123456789012",
"verified": true,
"permissions": {
"ec2:DescribeInstances": true,
"rds:DescribeDBInstances": true,
"cloudwatch:GetMetricStatistics": true
},
"missingPermissions": []
}
}
DELETE /api/accounts/{accountId}
Unlink AWS account.
Response:
{
"success": true,
"data": {
"accountId": "123456789012",
"status": "DELETED"
}
}
Scheduler APIs
GET /api/scheduler/schedules
Get all schedules.
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
accountId | string | No | Filter by account |
status | string | No | Filter by status: ACTIVE, PAUSED |
Response:
{
"success": true,
"data": {
"schedules": [
{
"id": "sched_abc123",
"name": "Dev Environment Schedule",
"type": "TAG_BASED",
"accountId": "123456789012",
"region": "us-east-1",
"tagRules": {
"Environment": "Dev",
"Scheduler": "Enabled"
},
"schedule": {
"startTime": "07:00",
"stopTime": "19:00",
"timezone": "America/New_York",
"weekdays": [1, 2, 3, 4, 5]
},
"resourceTypes": ["EC2", "RDS"],
"status": "ACTIVE",
"lastExecutionAt": "2025-10-26T07:00:00Z",
"projectedMonthlySavings": 3452.00,
"createdAt": "2025-01-10T10:00:00Z"
}
]
}
}
POST /api/scheduler/schedules
Create a new schedule.
Request Body:
{
"name": "Staging Environment Schedule",
"type": "TAG_BASED",
"accountId": "123456789012",
"region": "us-east-1",
"tagRules": {
"Environment": "Staging"
},
"schedule": {
"startTime": "08:00",
"stopTime": "18:00",
"timezone": "America/New_York",
"weekdays": [1, 2, 3, 4, 5]
},
"resourceTypes": ["EC2", "RDS"]
}
Response:
{
"success": true,
"data": {
"id": "sched_xyz789",
"status": "ACTIVE",
"matchedResources": 23,
"projectedMonthlySavings": 1840.00
}
}
PUT /api/scheduler/schedules/{id}
Update existing schedule.
Request Body: (Same as POST)
DELETE /api/scheduler/schedules/{id}
Delete schedule.
Response:
{
"success": true,
"data": {
"id": "sched_xyz789",
"status": "DELETED"
}
}
POST /api/scheduler/schedules/{id}/pause
Pause schedule execution.
Response:
{
"success": true,
"data": {
"id": "sched_xyz789",
"status": "PAUSED"
}
}
POST /api/scheduler/schedules/{id}/resume
Resume schedule execution.
Response:
{
"success": true,
"data": {
"id": "sched_xyz789",
"status": "ACTIVE"
}
}
Error Handling
Error Codes
| Code | HTTP Status | Description |
|---|---|---|
INVALID_REQUEST | 400 | Invalid request parameters |
UNAUTHORIZED | 401 | Missing or invalid authentication token |
FORBIDDEN | 403 | Insufficient permissions |
NOT_FOUND | 404 | Resource not found |
CONFLICT | 409 | Resource already exists or state conflict |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |
SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable |
Error Response Structure
{
"success": false,
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "Account ID must be 12 digits",
"details": {
"field": "accountId",
"value": "12345",
"expected": "12-digit string"
},
"requestId": "req_abc123def456",
"timestamp": "2025-10-26T12:00:00Z"
}
}
Rate Limiting
Limits
| Endpoint Type | Limit | Window |
|---|---|---|
| Read Operations (GET) | 100 requests | per minute |
| Write Operations (POST/PUT/DELETE) | 30 requests | per minute |
| Auth Endpoints | 5 requests | per minute |
| Bulk Operations | 10 requests | per minute |
Rate Limit Headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1698336060
Rate Limit Exceeded Response
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1698336060
Retry-After: 45
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Please retry after 45 seconds."
}
}
API Versioning
APIs are versioned via URL path:
/api/v1/recommendations (Current)
/api/v2/recommendations (Future)
Deprecation Policy:
- 6 months notice before deprecation
- Old version supported for 12 months after new version release
Next Steps
- Data Architecture - Database schemas and models