Back to home

AWS CLI and SDK Basics

Aug 3, 2026

AWS CLI and SDK Basics: How You Talk to AWS, How AWS Knows It's You, and What Happens When You Push Too Hard

When you're working with AWS, you're really doing one of two things all the time: sending a request to AWS, or an AWS resource sending a request about itself. That sounds simple, but under that simple idea sits a bunch of small mechanics that most developers never learn until something breaks. Why did my script suddenly get a ThrottlingException? Why can't I read the IAM policy from inside my own EC2 instance? Why does the CLI ask me for an MFA code differently than the console does?

This post walks through those mechanics one at a time. We'll cover the two main ways you talk to AWS (the CLI and the SDK), how every request you send gets signed so AWS knows it's really you, how to add MFA to a CLI session, how an EC2 instance can look up info about itself, and what happens when you hit AWS's rate limits or quotas. None of these are hard concepts on their own. They just don't get explained together very often, so let's put them in one place.

You don't need deep AWS knowledge to follow along, but you should have the AWS CLI installed on your machine and configured with a working IAM user or role, since most of the examples below are things you'd run straight from a terminal.

Two Ways to Talk to AWS: CLI and SDK

There are two main ways to make AWS do something: the CLI, or an SDK.

The CLI is what you use from a terminal. You type a command, AWS runs it, you get a response back. It's great for one-off tasks, quick checks, and scripts.

The SDK is what you use when you want your application code to talk to AWS directly, without shelling out to a terminal command. So if you're writing a Node.js app that needs to read from DynamoDB, you wouldn't call the CLI from inside your app, you'd use the AWS SDK for Node.js instead.

AWS officially supports SDKs for most major languages: Java, .NET, Node.js, PHP, Python (called boto3), Go, Ruby, and C++. Here's a fun fact that surprises a lot of people: the AWS CLI itself is actually built on top of boto3, the Python SDK. So every time you run a CLI command, you're technically already going through an SDK, just one AWS built for you.

How AWS Knows It's Really You: Signing Requests

Here's something that happens on every single AWS API call, whether you notice it or not: the request gets signed. Signing means AWS attaches proof, using your access key and secret key, that the request really came from you and hasn't been tampered with along the way.

If you're using the CLI or an SDK, you never have to think about this. Signing happens automatically behind the scenes. It only becomes something you deal with directly if you're building a raw HTTP request to AWS yourself, without going through the CLI or SDK.

The signing method AWS uses is called Signature Version 4, or SigV4 for short. There are two common ways to attach a SigV4 signature to a request.

Option one: put the signature in the HTTP header. This is the most common approach, and it looks like this:

GET https://iam.amazonaws.com/?Action=ListUsers&Version=2010-05-08 HTTP/1.1
Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request,
SignedHeaders=content-type;host;x-amz-date,
Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
content-type: application/x-www-form-urlencoded; charset=utf-8
host: iam.amazonaws.com
x-amz-date: 20150830T123600Z

Option two: put the signature in the query string. You'll see this most with S3 pre-signed URLs, where the signature sits right in the URL as X-Amz-Signature:

GET https://iam.amazonaws.com?Action=ListUsers&Version=2010-05-08&
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=AKIDEXAMPLE%2F20150830%2Fus-east-1%2Fiam%2Faws4_request&
X-Amz-Date=20150830T123600Z&X-Amz-Expires=60&X-Amz-SignedHeaders=content-type%3Bhost&
X-Amz-Signature=37ac2f4fde00b0ac9bd9eadeb459b1bbee224158d66e7ae5fcadb70b2d181d02 HTTP/1.1
content-type: application/x-www-form-urlencoded; charset=utf-8
host: iam.amazonaws.com

One thing worth knowing: not every S3 request needs a signature at all. A public S3 object can be fetched with a plain, unsigned GET request.

Adding MFA to Your CLI Sessions

MFA in the console is easy, you just get a prompt asking for your code when you log in. The CLI doesn't work that way, since there's no login screen to plug an MFA code into. Instead, you create a short-lived, MFA-verified session up front, and use that session for the rest of your work.

You create that session by calling the STS GetSessionToken API:

aws sts get-session-token \
  --serial-number arn-of-the-mfa-device \
  --token-code code-from-token \
  --duration-seconds 3600

Here, --serial-number is the ARN of your MFA device, and --token-code is whatever code is currently showing on your authenticator app or hardware token. --duration-seconds controls how long the session stays valid before it expires.

AWS sends back a full set of temporary credentials:

{
    "Credentials": {
        "SecretAccessKey": "secret-access-key",
        "SessionToken": "temporary-session-token",
        "Expiration": "expiration-date-time",
        "AccessKeyId": "access-key-id"
    }
}

You'd then export these as environment variables, or set up a named CLI profile with them, so every command you run afterward uses this temporary, MFA-backed session instead of your regular long-term credentials.

EC2 Instances Learning About Themselves: The Instance Metadata Service (IMDS)

Here's a feature a lot of developers don't know exists: an EC2 instance can look up information about itself without needing an IAM role just for that purpose. This is done through something called the Instance Metadata Service, or IMDS.

From inside the instance, you can query a special address, http://169.254.169.254/latest/meta-data, to get details like the instance ID, the attached IAM role name, and networking info.

Two terms get mixed up a lot here, so let's be clear about them. Metadata is information about the instance, things like its ID or role. Userdata is completely different, it's the script that ran when the instance first booted up. If you're debugging why an instance didn't set itself up correctly at launch, userdata is what you want to check, not metadata.

One limit worth knowing: you can pull the name of the IAM role attached to an instance from its metadata, but you can't pull the actual IAM policy behind that role. That's intentional, since the full policy would expose more about your permissions setup than you'd want available at that address.

IMDSv1 vs IMDSv2

There are two versions of IMDS, and the difference is really about security.

IMDSv1 is the simple, older version. You just hit the metadata URL directly with a GET request, no extra steps.

IMDSv2 adds a security step in front of that. First, you request a short-lived session token:

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`

Then you pass that token along in the header of every metadata request you make:

curl http://169.254.169.254/latest/meta-data/profile \
  -H "X-aws-ec2-metadata-token: $TOKEN"

This extra step protects against a class of attack (like SSRF) where someone tricks a server into making a request to the metadata endpoint on their behalf. If you're launching new instances today, use IMDSv2 by default rather than leaving it as an afterthought.

Staying Within AWS Limits: Rate Limits, Quotas, and Backoff

AWS puts two different kinds of caps on what you can do, and it's easy to mix them up until you actually hit one.

API rate limits cap how fast you can call a specific API. For example, EC2's DescribeInstances API allows up to 100 calls per second, while S3's GetObject allows up to 5,500 GET requests per second per prefix.

Service quotas (also called service limits) are different, they cap how much of something you're allowed to run at once, not how fast you can call an API. A common one: the default quota for running On-Demand Standard EC2 instances is 1,152 vCPUs total, unless you ask AWS to raise it.

What you do about it depends on which kind of limit you hit:

  • Hitting a rate limit occasionally? Use exponential backoff and retry the call.
  • Hitting a rate limit consistently? Backoff won't fix it, request a throttling limit increase instead.
  • Hitting a quota? Open a support ticket, or use the Service Quotas API to request an increase.

Exponential Backoff

If you're seeing ThrottlingException errors here and there, exponential backoff is the standard fix. You retry the failed call, but you wait a little longer before each retry instead of hammering AWS again right away. The good part is that if you're using an AWS SDK, this retry logic is already built in for you. You only need to write it yourself if you're calling the raw API directly, or hit one of the specific cases the SDK's default retry logic doesn't cover.

Best Practices

  • Use IMDSv2 instead of IMDSv1 on new EC2 instances, it closes off a real class of SSRF-based attacks.
  • Don't hardcode long-term access keys in app code. Use IAM roles for EC2 and Lambda, and temporary STS credentials (like the MFA session above) when working from the CLI.
  • Let the SDK handle retries and backoff for you instead of writing your own retry loop, unless you have a real reason to.
  • Treat rate limits and service quotas as two separate problems, since they fail differently and need different fixes.
  • Use S3 pre-signed URLs when you need to give someone temporary access to a private object without handing over credentials.

Common Pitfalls

  • Mixing up metadata and userdata, they answer completely different questions ("what am I" vs. "what should I run at boot").
  • Assuming you can read the full IAM policy from instance metadata. You only get the role name, not the policy.
  • Treating a consistent throttling error like an occasional one. Backoff won't help if you're structurally over the limit, you need an increase instead.
  • Forgetting that get-session-token sessions expire, and not checking the Expiration field, which leads to a script suddenly failing auth partway through.
  • Writing your own retry logic that just duplicates what the SDK already does for you.

Summary

None of these five things, request signing, MFA on the CLI, choosing SDK vs CLI, instance metadata, or rate limits and quotas, are complicated on their own. But they're the kind of plumbing that quietly sits underneath almost everything else you do on AWS. Once you know how IMDSv2's token flow works, how to spin up an MFA session for the CLI, when your code actually needs an SDK instead of shell commands, and how to tell a throttling error apart from a quota error, a lot of the "why is this suddenly broken" moments on AWS stop being mysterious.