How AWS Bills SDK Calls
An SDK call is an API request, and API requests can cost money.
Search across all documentation pages
An SDK call is an API request, and API requests can cost money.
This page explains the three ways AWS turns your calls into charges, and how ordinary code patterns like loops and polling create surprise bills.
AWS pricing is per-service, but almost every charge falls into three buckets: request count, resource consumption, and data transfer.
The SDK call itself is frequently free or fractions of a cent. The cost usually comes from what the call does - storing an object, running a query, invoking a function.
Some services do bill per API request directly. S3, for example, charges per thousand GET, PUT, and LIST requests, so a tight loop of small calls adds up.
Data transfer out of AWS to the internet is billed per gigabyte. Reading large objects repeatedly can cost more in egress than in requests.
The dangerous patterns are automated: loops over many items, polling in a while loop, and pagination that walks far more pages than you expected.
Poll with backoff instead of a tight loop, and prefer waiters over hand-rolled polling.
# --- Python (boto3) ---
import boto3
# A waiter polls with sensible intervals instead of a tight while-loop.
s3 = boto3.client("s3")
waiter = s3.get_waiter("object_exists")
waiter.wait(
Bucket="my-bucket",
Key="report.pdf",
WaiterConfig={"Delay": 5, "MaxAttempts": 20},
)// --- TypeScript (AWS SDK v3) ---
import { S3Client, waitUntilObjectExists } from "@aws-sdk/client-s3";
// waitUntilObjectExists polls with backoff and a max wait, not a busy loop.
const s3 = new S3Client({});
await waitUntilObjectExists(
{ client: s3, maxWaitTime: 100 },
{ Bucket: "my-bucket", Key: "report.pdf" },
);When to reach for this:
while True: check() and hammer the API.This walks a bucket safely: it filters with a prefix, paginates deliberately, and stops early once it has enough.
# --- Python (boto3) ---
import boto3
s3 = boto3.client("s3")
paginator = s3.get_paginator("list_objects_v2")
found = 0
LIMIT = 100
for page in paginator.paginate(Bucket="my-bucket", Prefix="logs/2026/"):
for obj in page.get("Contents", []):
print(obj["Key"], obj["Size"])
found += 1
if found >= LIMIT:
break
if found >= LIMIT:
break # stop paging: each page is a billed LIST request// --- TypeScript (AWS SDK v3) ---
import { S3Client, paginateListObjectsV2 } from "@aws-sdk/client-s3";
const s3 = new S3Client({});
const pages = paginateListObjectsV2(
{ client: s3 },
{ Bucket: "my-bucket", Prefix: "logs/2026/" },
);
let found = 0;
const LIMIT = 100;
outer: for await (const page of pages) {
for (const obj of page.Contents ?? []) {
console.log(obj.Key, obj.Size);
if (++found >= LIMIT) break outer; // stop paging once we have enough
}
}What this demonstrates:
Prefix narrows the scan so you list fewer objects and issue fewer requests.ListObjectsV2 request, so stopping early saves money.| Cost axis | Example | Driven by |
|---|---|---|
| Per-request | S3 GET/PUT/LIST, SQS SendMessage | Number of API calls |
| Resource use | Lambda invoke, DynamoDB read/write | Compute or capacity consumed |
| Data transfer | Downloading objects to the internet | Gigabytes egressed |
ThrottlingException or HTTP 503) which the SDK may retry with backoff.while True that calls the API with no delay issues thousands of requests a minute. Fix: use waiters or poll with backoff and a max attempt count.GetItem once per row is both slow and request-heavy. Fix: use batch operations like BatchGetItem or BatchWriteItem.| Alternative | Use When | Don't Use When |
|---|---|---|
| Batch operations | You act on many items at once | The API has no batch variant for your operation |
| Caching layer | Data is read-heavy and changes slowly | Data must be strongly consistent per read |
| Event-driven (SNS/SQS/EventBridge) | You would otherwise poll for changes | You need an immediate synchronous result |
| Waiters | You are waiting on a resource state | You need custom polling logic waiters do not support |
Not directly. Many calls are free or fractions of a cent; the cost usually comes from what the call does (storage, compute, egress) or from request-priced services like S3.
A loop turns one logical action into thousands of billed requests. Request-priced services and free-tier quotas are consumed per call, so unbounded loops add up quickly.
Yes. The SDK's automatic retries each send a real request, so a call that retries several times counts as several requests against pricing and rate limits.
When you exceed a service's allowed request rate, AWS rejects calls with a throttling error. The SDK may retry with backoff, but sustained throttling means your call rate is too high.
Waiters poll with sensible delays and a maximum attempt count instead of a busy loop, so you wait for a state change with a bounded, predictable number of requests.
It can be. Each page is a separate billed request. Filtering with a prefix or query and breaking early once you have enough keeps the request count down.
Only up to a monthly quota measured across your whole account. Once you cross it, usage silently switches to paid rates, and most data egress has only a small free allowance.
Use batch operations like BatchGetItem or BatchWriteItem in DynamoDB, which move many items in fewer requests than one call per item.
Transfer into AWS is generally free. The charge is on transfer out to the internet, billed per gigabyte, which is why redundant downloads are costly.
Use an event-driven pattern. SNS, SQS, and EventBridge let a change notify your code instead of you repeatedly asking whether it happened yet.
Set AWS Budgets and billing alarms, cache read-heavy data, batch writes, paginate deliberately, and review Cost Explorer for the services driving request volume.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 23, 2026