# S3 Minimum-Duration Exit Planner (`wheresprada/s3-minimum-duration-exit-planner`) Actor

Model remaining minimum-duration charges before planned exits from S3 Standard-IA, One Zone-IA and Glacier Instant Retrieval. Uses supplied rates and confirmed class-entry times; no AWS credentials, deletes or total-bill promises.

- **URL**: https://apify.com/wheresprada/s3-minimum-duration-exit-planner.md
- **Developed by:** [Mahruf Hossain](https://apify.com/wheresprada) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.32 / completed s3 minimum-duration plan

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## S3 Minimum-Duration Exit Planner

Estimate the remaining minimum-storage-duration component before a planned exit from S3 Standard-IA, One Zone-IA or Glacier Instant Retrieval. Supply confirmed storage-class entry times, object-version sizes and your applicable rates. The Actor returns per-object timing, modeled billable size, estimated remaining-duration exposure and an explicit list of records it cannot model.

This is an offline planning calculation, not an AWS bill, lifecycle simulator or deletion service. It needs no AWS credentials and never reads, changes or removes an object. Use it to prepare a review before an authorized cleanup or retention change, alongside AWS's own pricing and billing tools.

### Key features

- Model 30-day minimums for Standard-IA and One Zone-IA and 90 days for Glacier Instant Retrieval.
- Apply the 128 KiB minimum billable size for those supported classes.
- Calculate with integer bytes, seconds and microdollars rather than floating-point money.
- Show the earliest timestamp at which each supplied class-duration minimum is satisfied.
- Keep unconfirmed entry times and missing rates unresolved.
- Reject conflicting stable object-version records; count exact duplicate export records once.
- Review up to 5,000 supplied plans within a bounded input and runtime.

### Who should use it?

FinOps engineers, backup operators and platform teams planning repeated storage cleanup can use this Actor when they can establish storage-class transition history. It is especially useful for an approval workflow that needs machine-readable per-object estimates without giving an external process AWS account access.

Do not use it if the only timestamp you have is an unverified object creation or last-modified time. An object can enter a storage class later than it was created. The Actor deliberately requires confirmation rather than silently substituting a convenient date.

### Practical use cases

Prepare a bounded set of cleanup candidates for a FinOps review. Identify IA objects whose planned exits precede the class minimum. Compare two planned exit times using the same verified rates. Show how small-object billing floors affect an estimate. Separate supported records from archival classes requiring a different metadata model before anyone treats a partial estimate as a portfolio total.

### Quick start

Run the included invented example. A one-GiB object enters Standard-IA on October 1 and is planned to exit on October 11. With an illustrative rate of $0.03 per GiB-month and a 30-day model month, twenty days remain and the modeled amount is $0.020000.

The example rate is a calculation fixture, **not a current AWS regional quote**. Replace it with your applicable rate. Confirm the transition timestamp from authorized history, use stable pseudonymous object-version IDs and set a deliberate model month length. Review unresolved records before using the estimate.

### Input example

```json
{
  "billingMonthDays": 30,
  "ratesUsdPerGiBMonth": {"STANDARD_IA":"0.03"},
  "objects": [{
    "id":"VERSION-DEMO",
    "storageClass":"STANDARD_IA",
    "sizeBytes":"1073741824",
    "classEnteredAt":"2026-10-01T00:00:00Z",
    "classEntryConfirmed":true,
    "plannedExitAt":"2026-10-11T00:00:00Z"
  }]
}
```

#### Input parameters

| Field | Meaning |
|---|---|
| `billingMonthDays` | Explicit denominator, integer 28–31 |
| `ratesUsdPerGiBMonth` | Applicable rates for supported classes, decimal strings with at most six places |
| `objects` | 1–5,000 object-version plans, total input at most 2 MB |
| `id` | Stable pseudonymous version identifier; do not include a private object path |
| `storageClass` | `STANDARD_IA`, `ONEZONE_IA` or `GLACIER_IR` for modeled records |
| `sizeBytes` | Nonnegative integer string, maximum 5 TiB |
| `classEnteredAt` | Confirmed UTC timestamp, or null when unavailable |
| `classEntryConfirmed` | Explicit boolean; false preserves an unresolved record |
| `plannedExitAt` | UTC timestamp at or after class entry |

Timestamps must use exactly `YYYY-MM-DDTHH:mm:ssZ`. Rates are supplied by you and can be zero where genuinely applicable. Unknown fields are rejected. Unsupported storage classes can be listed, but remain unresolved; they are never assigned a guessed duration or rate. If no objects can be modeled, the run fails without a completed-plan event.

### Output and verified example

The packaged example produces this verified summary:

```json
{
  "uniqueObjects":1,
  "modeledObjects":1,
  "unresolvedObjects":0,
  "objectsBeforeMinimum":1,
  "ignoredExactDuplicates":0,
  "modeledRemainingDurationUsd":"0.020000"
}
```

Each modeled row includes size, modeled billable bytes, minimum days, remaining seconds, earliest minimum-satisfied timestamp, planned exit and the estimated amount. `unresolved` records the object reference and reason, such as unverified entry time or missing applicable rate. `coverageComplete` is false whenever unresolved records exist.

The default dataset contains one complete report, identical to the `OUTPUT` key-value record. Monetary outputs are six-decimal USD strings. `checkedAt` records processing time and does not imply AWS validated the proposed action.

### How the model works

For a supported record, the Actor subtracts elapsed seconds from the class minimum and floors the remainder at zero. It uses the larger of supplied size and 131,072 bytes, multiplies by the supplied USD/GiB-month rate and the remaining fraction of the chosen model month. Each result is rounded half up to one microdollar before summing.

This explicit model is not AWS invoice rounding or a universal calendar-month forecast. Changing `billingMonthDays` changes the estimate. Customer discounts, rate tiers, credits and actual billing timing can also change real charges. The output therefore labels amounts as modeled remaining duration, never guaranteed fees or savings.

Exact repeated records with the same stable version ID are ignored with a count. Conflicting values for that ID fail, avoiding accidental double counting or arbitrary selection. Different versions require different stable IDs even if they share the same object key upstream.

### Pricing

One completed bounded plan costs **$0.32**. A plan that clearly lists some unresolved objects is billable because it still contains a completed supported calculation. Invalid input or a plan with no modelable objects has no completed-plan event.

Ten completed reports cost $3.20 in Actor events, 100 cost $32 and 1,000 cost $320. There is no charge per flagged object. Apify platform usage and plan-specific conditions may apply separately. Inspect the run's current pricing and set a suitable spending limit. Repeated scenarios are separate reports and incur separate report events.

### API integration and scheduling

```javascript
import { ApifyClient } from 'apify-client';
import fs from 'node:fs';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const input = JSON.parse(fs.readFileSync('exit-plan.json', 'utf8'));
const run = await client.actor('wheresprada/s3-minimum-duration-exit-planner').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].summary);
```

Your authorized export process must supply fresh plans and transition evidence. An Apify schedule with static input does not discover new AWS objects or rates. Review results before any separate deletion workflow. No automatic action is taken on a finding.

### Limits and accuracy

The runtime budget is 128 MB and 30 seconds. Input is limited to 2 MB and 5,000 object-version records; oversized input fails rather than truncating. The calculation supports the three named classes only. Glacier Flexible Retrieval, Deep Archive, Intelligent-Tiering and their metadata or tier-specific models are excluded.

This does not estimate ordinary elapsed storage, request charges, retrieval costs, data transfer, taxes, discounts, total invoice changes or the operational value of retaining data. It does not interpret lifecycle filters, determine whether AWS would perform a transition, or establish whether an exit is permitted by retention or legal requirements. A satisfied minimum does not mean deletion is safe or that the total operation is free.

### Privacy and security

No AWS keys, bucket names or object contents are needed. Use authorized metadata and stable pseudonyms. The Actor makes no AWS requests. Raw input and results remain in Apify storage under your account's access and retention settings; they may still reveal operational scale or timing. Restrict sharing and remove old planning records when they are no longer needed.

### Troubleshooting and FAQ

**Why is LastModified insufficient?** It may not establish when the version entered its present storage class. Supply verified transition history or keep the record unresolved.

**Why is a tiny object modeled as 128 KiB?** The supported classes have a minimum billable object size. This differs from any separate lifecycle transition eligibility rule.

**Can I use this for Deep Archive?** No. Its archival metadata and duration model are intentionally unsupported rather than approximated silently.

**Why does the estimate differ from my bill?** Check rates, month denominator, version identity and omitted invoice components. The result is a narrow planning model.

**Does zero remaining duration mean zero deletion cost?** No. Other costs and obligations are outside this calculation.

**Can the Actor delete objects after review?** No. It has no AWS access and provides no destructive operation.

### Support

Open an Apify Store issue with the run ID, expected arithmetic and a minimal pseudonymized fixture. Include the assumed rate unit and model month length. Do not provide AWS credentials, signed URLs or private object paths. A reproducible calculation lets support distinguish a defect from an unsupported billing scenario.

# Actor input Schema

## `billingMonthDays` (type: `integer`):

Explicit 28-31-day month denominator. Provider invoice timing and rounding can differ.

## `ratesUsdPerGiBMonth` (type: `object`):

Map STANDARD_IA, ONEZONE_IA, GLACIER_IR to nonnegative decimal strings, up to 6 places. Supply applicable region/account rates yourself.

## `objects` (type: `array`):

1-5000 records, total input <=2 MB. id, storageClass, sizeBytes (integer string <=5 TiB), classEnteredAt (UTC or null), classEntryConfirmed boolean, plannedExitAt UTC. Never substitute unverified LastModified for class entry.

## Actor input object example

```json
{
  "billingMonthDays": 30,
  "ratesUsdPerGiBMonth": {
    "STANDARD_IA": "0.03"
  },
  "objects": [
    {
      "id": "VERSION-DEMO",
      "storageClass": "STANDARD_IA",
      "sizeBytes": "1073741824",
      "classEnteredAt": "2026-10-01T00:00:00Z",
      "classEntryConfirmed": true,
      "plannedExitAt": "2026-10-11T00:00:00Z"
    }
  ]
}
```

# Actor output Schema

## `report` (type: `string`):

No description

## `reports` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "billingMonthDays": 30,
    "ratesUsdPerGiBMonth": {
        "STANDARD_IA": "0.03"
    },
    "objects": [
        {
            "id": "VERSION-DEMO",
            "storageClass": "STANDARD_IA",
            "sizeBytes": "1073741824",
            "classEnteredAt": "2026-10-01T00:00:00Z",
            "classEntryConfirmed": true,
            "plannedExitAt": "2026-10-11T00:00:00Z"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wheresprada/s3-minimum-duration-exit-planner").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "billingMonthDays": 30,
    "ratesUsdPerGiBMonth": { "STANDARD_IA": "0.03" },
    "objects": [{
            "id": "VERSION-DEMO",
            "storageClass": "STANDARD_IA",
            "sizeBytes": "1073741824",
            "classEnteredAt": "2026-10-01T00:00:00Z",
            "classEntryConfirmed": True,
            "plannedExitAt": "2026-10-11T00:00:00Z",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("wheresprada/s3-minimum-duration-exit-planner").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "billingMonthDays": 30,
  "ratesUsdPerGiBMonth": {
    "STANDARD_IA": "0.03"
  },
  "objects": [
    {
      "id": "VERSION-DEMO",
      "storageClass": "STANDARD_IA",
      "sizeBytes": "1073741824",
      "classEnteredAt": "2026-10-01T00:00:00Z",
      "classEntryConfirmed": true,
      "plannedExitAt": "2026-10-11T00:00:00Z"
    }
  ]
}' |
apify call wheresprada/s3-minimum-duration-exit-planner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wheresprada/s3-minimum-duration-exit-planner"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ZwHd0E09LqQIdDo3z/builds/eJq315Dc0HimK8l4H/openapi.json
