MiniStack
Free, open-source local AWS emulator. Free forever.
60+ AWS services on a single port · Multi-account & multi-region · Terraform compatible · Real databases · MIT licensed
Ministack.org · Docker Hub · LinkedIn
Why MiniStack?
LocalStack recently moved its core services behind a paid plan. If you relied on LocalStack Community for local development and CI/CD pipelines, MiniStack is your free alternative.
- 60+ AWS services emulated on a single port (4566)
- Drop-in compatible — works with
boto3, AWS CLI, Terraform, CDK, Pulumi, any SDK - Multi-account & multi-region — a 12-digit access key becomes the account, the SigV4 region scopes the state; isolated tenants and regions on one endpoint, like real AWS
- Amazon Bedrock locally — Converse / InvokeModel with AWS-exact wire shapes; point
MINISTACK_BEDROCK_PROXY_URLat Ollama, llama.cpp, or vLLM and get real completions through the Bedrock API - Real infrastructure — RDS spins up actual Postgres/MySQL containers, ElastiCache spins up real Redis/Valkey, Athena runs real SQL via DuckDB (full image only), ECS runs real Docker containers
- Tiny footprint — ~270MB image, ~30MB RAM at idle vs LocalStack's ~1GB image and ~500MB RAM
- Fast startup — under 2 seconds, HTTP/2 (h2c) supported
- MIT licensed — use it, fork it, contribute to it
Quick Start
# Option 1: PyPI (simplest)
pip install ministack
ministack
Runs on http://localhost:4566 — use GATEWAY_PORT=XXXX to change
Option 2: Docker Hub
docker run -p 4566:4566 ministackorg/ministack
Option 2b: Docker Hub with real infrastructure (RDS, ECS, Lambda containers)
docker run -p 4566:4566 -v /var/run/docker.sock:/var/run/docker.sock ministackorg/ministack
Option 2c: Full image — Debian/glibc base with DuckDB (Athena), psycopg2, pymysql.
Larger (~360 MB vs ~110 MB) but enables Athena and native PostgreSQL/MySQL drivers
that don't ship musllinux wheels. Reports edition: full on /_ministack/health.
docker run -p 4566:4566 ministackorg/ministack:full
Option 3: Clone and build
git clone https://github.com/ministackorg/ministack
cd ministack
docker compose up -d
Verify (any option)
curl http://localhost:4566/_ministack/health
That's it. No account, no API key, no sign-up.
Internal API
MiniStack exposes internal endpoints for test automation:
# Health check — returns service status
curl http://localhost:4566/_ministack/health
Reset all state — wipe every service back to empty (useful between test runs)
curl -X POST http://localhost:4566/_ministack/reset
Reset and re-run init scripts (boot.d + ready.d)
curl -X POST http://localhost:4566/_ministack/reset?init=1
Runtime config — change service-level settings without restart
curl -X POST http://localhost:4566/_ministack/config \
-H "Content-Type: application/json" \
-d '{"lambda_svc.LAMBDA_EXECUTOR": "docker"}'
Inspect emails sent via SES — returns every message grouped by account
curl http://localhost:4566/_ministack/ses/messages
Filter by account (12-digit access-key ID used as the account ID)
curl "http://localhost:4566/_ministack/ses/messages?account=000000000000"
Inspect SQS messages — returns every queue's messages grouped by account
(includes Body, MessageId, ReceiveCount, VisibleAt, IsVisible, MessageAttributes, FIFO group/dedup)
curl http://localhost:4566/_ministack/sqs/messages
Filter by account and/or a specific queue
curl "http://localhost:4566/_ministack/sqs/messages?account=000000000000&QueueUrl=http://localhost:4566/000000000000/my-queue"
Inspect SMS sent via SNS Publish with a PhoneNumber — grouped by recipient
curl http://localhost:4566/_ministack/sns/sms-messages
Filter by recipient, account and/or region
curl "http://localhost:4566/_ministack/sns/sms-messages?phoneNumber=%2B15551234567"
The reset endpoint is especially useful in CI pipelines and test suites — call it in setUp/beforeEach to get a clean environment for every test without restarting the container. Add ?init=1 to re-run your init scripts after the reset, restoring any resources they create (VPCs, queues, seed data, etc.).
The config endpoint supports these keys:
| Key | Description |
|-----|-------------|
| lambda_svc.LAMBDA_EXECUTOR | Lambda execution mode (local or docker) |
| athena.ATHENA_ENGINE | Athena query engine (duckdb or mock) |
| athena.ATHENA_DATA_DIR | Directory for Athena DuckDB data files |
| stepfunctions._sfn_mock_config | SFN mock config (AWS SFN Local compatible) |
| stepfunctions._SFN_WAIT_SCALE | Scale factor for Wait state durations and retry sleeps (0 = skip all waits) |
To set region or account ID, use environment variables at startup:
docker run -p 4566:4566 \
-e MINISTACK_REGION=eu-west-1 \
-e MINISTACK_ACCOUNT_ID=123456789012 \
ministackorg/ministack
Or use the multi-tenancy feature — a 12-digit access key automatically becomes the account ID (see Multi-Tenancy below).
Also compatible with LocalStack's health endpoint:
curl http://localhost:4566/_localstack/health
curl http://localhost:4566/health
Multi-Tenancy
MiniStack supports lightweight multi-tenancy without any configuration. If the AWS_ACCESS_KEY_ID is a 12-digit number, it is used as the Account ID for all ARN generation. Non-numeric keys (like test) fall back to the MINISTACK_ACCOUNT_ID env var or 000000000000.
# Team A — gets account 111111111111
export AWS_ACCESS_KEY_ID=111111111111
export AWS_SECRET_ACCESS_KEY=test
aws --endpoint-url=http://localhost:4566 sts get-caller-identity
→ { "Account": "111111111111", ... }
Team B — gets account 222222222222
export AWS_ACCESS_KEY_ID=222222222222
export AWS_SECRET_ACCESS_KEY=test
aws --endpoint-url=http://localhost:4566 sts get-caller-identity
→ { "Account": "222222222222", ... }
All ARNs and resource state (SQS queues, Lambda functions, IAM roles, S3 buckets, DynamoDB tables, etc.) are fully isolated per account. Resources with the same name in different accounts never collide. This allows multiple developers or CI pipelines to share a single MiniStack endpoint with complete tenant isolation — no extra setup needed.
Since 1.4.0 state is additionally isolated per region — see Multi-Region below.
| Access Key | Account ID Used |
|---|---|
| 111111111111 | 111111111111 |
| 048408301323 | 048408301323 |
| test | 000000000000 (default) |
| AKIA_TEST_KEY_DO_NOT_USE | 000000000000 (default) |
Terraform — set access_key in your provider block:
provider "aws" {
access_key = "048408301323"
secret_key = "test"
region = "us-east-1"
endpoints { ... }
}
boto3 — pass aws_access_key_id:
boto3.client("s3",
endpoint_url="http://localhost:4566",
aws_access_key_id="048408301323",
aws_secret_access_key="test",
)
Multi-Region
Since 1.4.0, state is isolated per region as well as per account — no configuration needed. The region comes from the SigV4 credential scope (the region_name your SDK or --region your CLI is configured with), so two clients pointed at different regions see fully independent resources, exactly like real AWS:
# Same queue name, two regions — two independent queues
aws --endpoint-url=http://localhost:4566 --region us-east-1 sqs create-queue --queue-name jobs
aws --endpoint-url=http://localhost:4566 --region eu-west-1 sqs create-queue --queue-name jobs
aws --endpoint-url=http://localhost:4566 --region us-east-1 sqs list-queues
→ .../000000000000/jobs (the us-east-1 queue)
aws --endpoint-url=http://localhost:4566 --region eu-west-1 sqs list-queues
→ .../000000000000/jobs (a different queue — eu-west-1's)
use1 = boto3.client("dynamodb", endpoint_url="http://localhost:4566",
aws_access_key_id="test", aws_secret_access_key="test",
region_name="us-east-1")
euw1 = boto3.client("dynamodb", endpoint_url="http://localhost:4566",
aws_access_key_id="test", aws_secret_access_key="test",
region_name="eu-west-1")
use1.create_table(TableName="users",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST")
use1.list_tables()["TableNames"] # → ["users"]
euw1.list_tables()["TableNames"] # → [] — eu-west-1 is independent
Region-isolated services: as of 1.5.3, every regional service is region-isolated except S3 and Aurora DSQL (the isolation migrations rolled out service by service from 1.4.0 to 1.4.12 — see the CHANGELOG for the per-release history; services introduced later, like AWS Config and the Cloud Control API in 1.4.14, were born region-scoped).
Cross-resource references resolve in the referenced ARN's own account and region (SNS→SQS fanout, EventBridge targets, event source mappings), and cross-region references that real AWS rejects return the same errors AWS returns — e.g. invoking a eu-west-1 Lambda from a us-east-1 Step Functions task fails with Functions from 'eu-west-1' are not reachable in this region, exactly as on AWS.
Not yet region-isolated: S3 and Aurora DSQL — these share state across regions within an account (as all services did before 1.4.0); use unique resource names there if your tests exercise two regions, until their isolation lands in a subsequent release. IAM/STS, CloudFront, Route 53, and Organizations are global services on AWS, so their account-scoped shared state is correct, not a gap.
Upgrading with PERSIST_STATE=1: existing state files load and migrate automatically (on-disk format v2 with a version stamp — a newer-format file is refused instead of mis-parsed on downgrade). Each record's region is recovered from its stored ARNs; legacy records that carry no ARN migrate to the default region (MINISTACK_REGION).
Using with AWS CLI
# Option A — environment variables (no profile needed)
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
export AWS_DEFAULT_REGION=us-east-1
aws --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket
aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name my-queue
aws --endpoint-url=http://localhost:4566 dynamodb list-tables
aws --endpoint-url=http://localhost:4566 sts get-caller-identity
Option B — named profile (must pass --profile on every command)
aws configure --profile local
AWS Access Key ID: test
AWS Secret Access Key: test
Default region: us-east-1
Default output format: json
aws --profile local --endpoint-url=http://localhost:4566 s3 mb s3://my-bucket
aws --profile local --endpoint-url=http://localhost:4566 s3 cp ./file.txt s3://my-bucket/
aws --profile local --endpoint-url=http://localhost:4566 sqs create-queue --queue-name my-queue
aws --profile local --endpoint-url=http://localhost:4566 dynamodb list-tables
aws --profile local --endpoint-url=http://localhost:4566 sts get-caller-identity
awslocal wrapper
chmod +x bin/awslocal
./bin/awslocal s3 ls
./bin/awslocal dynamodb list-tables
Using with boto3
import boto3
All clients use the same endpoint
def client(service):
return boto3.client(
service,
endpoint_url="http://localhost:4566",
aws_access_key_id="test",
aws_secret_access_key="test",
region_name="us-east-1",
)
S3
s3 = client("s3")
s3.create_bucket(Bucket="my-bucket")
s3.put_object(Bucket="my-bucket", Key="hello.txt", Body=b"Hello, MiniStack!")
obj = s3.get_object(Bucket="my-bucket", Key="hello.txt")
print(obj["Body"].read()) # b'Hello, MiniStack!'
SQS
sqs = client("sqs")
q = sqs.create_queue(QueueName="my-queue")
sqs.send_message(QueueUrl=q["QueueUrl"], MessageBody="hello")
msgs = sqs.receive_message(QueueUrl=q["QueueUrl"])
print(msgs["Messages"][0]["Body"]) # hello
DynamoDB
ddb = client("dynamodb")
ddb.create_table(
TableName="Users",
KeySchema=[{"AttributeName": "userId", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "userId", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
ddb.put_item(TableName="Users", Item={"userId": {"S": "u1"}, "name": {"S": "Alice"}})
SSM Parameter Store
ssm = client("ssm")
ssm.put_parameter(Name="/app/db/host", Value="localhost", Type="String")
param = ssm.get_parameter(Name="/app/db/host")
print(param["Parameter"]["Value"]) # localhost
Secrets Manager
sm = client("secretsmanager")
sm.create_secret(Name="db-password", SecretString='{"password":"s3cr3t"}')
Kinesis
kin = client("kinesis")
kin.create_stream(StreamName="events", ShardCount=1)
kin.put_record(StreamName="events", Data=b'{"event":"click"}', PartitionKey="user1")
EventBridge
eb = client("events")
eb.put_events(Entries=[{
"Source": "myapp",
"DetailType": "UserSignup",
"Detail": '{"userId": "123"}',
"EventBusName": "default",
}])
Step Functions
sfn = client("stepfunctions")
sfn.create_state_machine(
name="my-workflow",
definition='{"StartAt":"Hello","States":{"Hello":{"Type":"Pass","End":true}}}',
roleArn="arn:aws:iam::000000000000:role/role",
)
Step Functions — TestState API (test a single state in isolation)
Note: inject_host_prefix=False prevents boto3 from prepending "sync-" to the hostname
from botocore.config import Config as BotoConfig
sfn_test = client("stepfunctions", config=BotoConfig(inject_host_prefix=False))
result = sfn_test.test_state(
definition='{"Type":"Pass","Result":{"greeting":"hello"},"End":true}',
input='{"name":"world"}',
)
print(result["status"]) # SUCCEEDED
print(result["output"]) # {"greeting": "hello"}
TestState with mock — test error handling without calling real services
result = sfn_test.test_state(
definition=json.dumps({
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:000000000000:function:my-fn",
"Catch": [{"ErrorEquals": ["States.ALL"], "Next": "Fallback"}],
"End": True
}),
input='{}',
inspectionLevel="DEBUG",
mock={"errorOutput": {"error": "ServiceError", "cause": "Timeout"}},
)
print(result["status"]) # CAUGHT_ERROR
print(result["nextState"]) # Fallback
EC2
ec2 = client("ec2")
reservation = ec2.run_instances(
ImageId="ami-00000001",
MinCount=1,
MaxCount=1,
InstanceType="t3.micro",
)
instance_id = reservation["Instances"][0]["InstanceId"]
print(instance_id) # i-xxxxxxxxxxxxxxxxx
Security Groups
sg = ec2.create_security_group(GroupName="my-sg", Description="My SG")
ec2.authorize_security_group_ingress(
GroupId=sg["GroupId"],
IpPermissions=[{"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80,
"IpRanges": [{"CidrIp": "0.0.0.0/0"}]}],
)
VPC / Subnet
vpc = ec2.create_vpc(CidrBlock="10.0.0.0/16")
subnet = ec2.create_subnet(
VpcId=vpc["Vpc"]["VpcId"],
CidrBlock="10.0.1.0/24",
AvailabilityZone="us-east-1a",
)
Supported Services
Core Services
| Service | Operations | Notes |
|---------|-----------|-------|
| S3 | CreateBucket, DeleteBucket, ListBuckets, HeadBucket, PutObject, GetObject, DeleteObject, HeadObject, CopyObject, ListObjects v1/v2, DeleteObjects, GetBucketVersioning, PutBucketVersioning, GetBucketEncryption, PutBucketEncryption, DeleteBucketEncryption, GetBucketLifecycleConfiguration, PutBucketLifecycleConfiguration, DeleteBucketLifecycle, GetBucketCors, PutBucketCors, DeleteBucketCors, GetBucketAcl, PutBucketAcl, GetBucketTagging, PutBucketTagging, DeleteBucketTagging, GetBucketPolicy, PutBucketPolicy, DeleteBucketPolicy, GetBucketNotificationConfiguration, PutBucketNotificationConfiguration, GetBucketLogging, PutBucketLogging, ListObjectVersions, CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload, PutObjectLockConfiguration, GetObjectLockConfiguration, PutObjectRetention, GetObjectRetention, PutObjectLegalHold, GetObjectLegalHold, PutBucketReplication, GetBucketReplication, DeleteBucketReplication, GetObjectTagging, PutObjectTagging, DeleteObjectTagging, PutObjectAnnotation, GetObjectAnnotation, ListObjectAnnotations, DeleteObjectAnnotation | Optional disk persistence via S3_PERSIST=1; Object Lock with retention & legal hold enforcement on delete, and on annotation writes under AUTH=true (governance mode yields to x-amz-bypass-governance-retention); object annotations are per version (up to 1,000, 1 byte–1 MiB each), carried by CopyObject and removed with their version; object tags are versioned (?tagging&versionId=… reads, writes, and deletes the per-version tag set); Multi-Region Access Points provision from CloudFormation (AWS::S3::MultiRegionAccessPoint) and serves the member buckets through the regular S3 path |
| SQS | CreateQueue, DeleteQueue, ListQueues, GetQueueUrl, GetQueueAttributes, SetQueueAttributes, PurgeQueue, SendMessage, ReceiveMessage, DeleteMessage, ChangeMessageVisibility, ChangeMessageVisibilityBatch, SendMessageBatch, DeleteMessageBatch, TagQueue, UntagQueue, ListQueueTags | Both Query API and JSON protocol; FIFO queues with deduplication; DLQ support; the batch actions apply AWS's request-level validation (empty batch, more than ten entries, repeated or malformed entry ids) before any entry is processed |
| SNS | CreateTopic, DeleteTopic, ListTopics, GetTopicAttributes, SetTopicAttributes, AddPermission, RemovePermission, Subscribe, Unsubscribe, ListSubscriptions, ListSubscriptionsByTopic, GetSubscriptionAttributes, SetSubscriptionAttributes, ConfirmSubscription, Publish, PublishBatch, TagResource, UntagResource, ListTagsForResource, CreatePlatformApplication, ListPlatformApplications, GetPlatformApplicationAttributes, SetPlatformApplicationAttributes, CreatePlatformEndpoint, GetEndpointAttributes, SetEndpointAttributes, DeleteEndpoint, DeletePlatformApplication | SNS→SQS fanout delivery; SNS→Lambda fanout (asynchronous delivery); FIFO topics with 5-minute deduplication, sequence numbers, content-based deduplication, and subscription validation; mobile-push endpoint lifecycle with device-token dedup and Publish to platform-endpoint TargetArn |
| DynamoDB | CreateTable, UpdateTable, DeleteTable, DescribeTable, ListTables, PutItem, GetItem, DeleteItem, UpdateItem, Query, Scan, BatchWriteItem, BatchGetItem, TransactWriteItems, TransactGetItems, DescribeTimeToLive, UpdateTimeToLive, DescribeContinuousBackups, UpdateContinuousBackups, DescribeEndpoints, TagResource, UntagResource, ListTagsOfResource, EnableKinesisStreamingDestination, DisableKinesisStreamingDestination, DescribeKinesisStreamingDestination, UpdateKinesisStreamingDestination | TTL enforced via thread-safe background reaper (60s cadence); DynamoDB Streams — StreamSpecification emits INSERT/MODIFY/REMOVE records on all write operations, respects StreamViewType; Kinesis streaming destinations (aws_dynamodb_kinesis_streaming_destination) fan item mutations out into any Kinesis stream by ARN while the destination is ACTIVE; global tables (version 2019.11.21) through UpdateTable ReplicaUpdates: a replica is a copy of the table in the other region, and writes replicate between them |
| DynamoDB Streams | ListStreams, DescribeStream, GetShardIterator, GetRecords | Reads records emitted by the main DynamoDB service via boto3.client("dynamodbstreams") — single synthetic shard per stream; TRIM_HORIZON/LATEST/AT_SEQUENCE_NUMBER/AFTER_SEQUENCE_NUMBER iterator types; NEW_AND_OLD_IMAGES, NEW_IMAGE, OLD_IMAGE, KEYS_ONLY view types; opaque base64 iterator tokens |
| Lambda | CreateFunction, DeleteFunction, GetFunction, GetFunctionConfiguration, ListFunctions, Invoke, UpdateFunctionCode, UpdateFunctionConfiguration, AddPermission, RemovePermission, GetPolicy, ListVersionsByFunction, PublishVersion, CreateAlias, GetAlias, UpdateAlias, DeleteAlias, ListAliases, TagResource, UntagResource, ListTags, CreateEventSourceMapping, DeleteEventSourceMapping, GetEventSourceMapping, ListEventSourceMappings, UpdateEventSourceMapping, CreateFunctionUrlConfig, GetFunctionUrlConfig, UpdateFunctionUrlConfig, DeleteFunctionUrlConfig, ListFunctionUrlConfigs, PutFunctionConcurrency, GetFunctionConcurrency, DeleteFunctionConcurrency, PutFunctionEventInvokeConfig, GetFunctionEventInvokeConfig, DeleteFunctionEventInvokeConfig, PublishLayerVersion, GetLayerVersion, GetLayerVersionByArn, ListLayerVersions, DeleteLayerVersion, ListLayers, AddLayerVersionPermission, RemoveLayerVersionPermission, GetLayerVersionPolicy, CheckpointDurableExecution, GetDurableExecution, GetDurableExecutionState, GetDurableExecutionHistory, ListDurableExecutionsByFunction, StopDurableExecution, SendDurableExecutionCallbackSuccess, SendDurableExecutionCallbackFailure, SendDurableExecutionCallbackHeartbeat | Python and Node.js runtimes execute with warm worker pool; provided.al2023/provided.al2 runtimes reuse local bootstrap processes via the Lambda Runtime API (Go, Rust, C++ support), or use Docker RIE when configured; Publish=True creates immutable numbered versions; Code via ZipFile, S3Bucket/S3Key (with optional S3ObjectVersion), or ImageUri (Docker image); PackageType: Image pulls and invokes user-provided Docker images via Lambda RIE; SQS, Kinesis, and DynamoDB Streams event source mappings; Function URL CRUD; Lambda Layers CRUD; Aliases; Concurrency; EventInvokeConfig; SnapStart — SnapStart.ApplyOn=PublishedVersions round-trips on CreateFunction/UpdateFunctionConfiguration (Java 11+/Python 3.12+/.NET 8+ and container images, per AWS's constraints); publishing a version initializes its execution environment then and there, so the version reports OptimizationStatus: On, walks Pending → Active, answers ResourceConflictException while Pending, fails the publish on a broken init, and runs snapshot-restore-py before-snapshot/after-restore hooks during init (Python); Durable Functions — CreateFunction accepts DurableConfig; checkpoint/state/history/list/stop ops at the preview API (2025-12-01); external SendCallback{Success,Failure,Heartbeat} resume the SDK across invocations; resume scheduler fires WAIT expiries, callback timeouts, and step-retry backoffs; verified against the official aws-durable-execution-sdk-python and aws-durable-execution-sdk-java; X-Ray active tracing — TracingConfig.Mode=Active injects _X_AMZN_TRACE_ID (Root=1-) into the runtime per invocation so the AWS X-Ray SDK runs without Missing AWS Lambda trace data; supported on the warm Python / Node executor, provided runtimes, and the local subprocess fallback (docker RIE upstream does not implement X-Ray and is logged but unsupported) |
| IAM | CreateUser, GetUser, ListUsers, DeleteUser, CreateRole, GetRole, ListRoles, DeleteRole, UpdateRoleDescription, PutRolePermissionsBoundary, DeleteRolePermissionsBoundary, CreatePolicy, GetPolicy, DeletePolicy, AttachRolePolicy, DetachRolePolicy, PutRolePolicy, GetRolePolicy, DeleteRolePolicy, ListRolePolicies, ListAttachedRolePolicies, CreateAccessKey, ListAccessKeys, DeleteAccessKey, UpdateAccessKey, GetAccessKeyLastUsed, CreateInstanceProfile, GetInstanceProfile, DeleteInstanceProfile, AddRoleToInstanceProfile, RemoveRoleFromInstanceProfile, ListInstanceProfiles, TagInstanceProfile, UntagInstanceProfile, ListInstanceProfileTags, CreateGroup, GetGroup, AddUserToGroup, RemoveUserFromGroup, AttachGroupPolicy, DetachGroupPolicy, ListAttachedGroupPolicies, PutGroupPolicy, GetGroupPolicy, DeleteGroupPolicy, ListGroupPolicies, CreateServiceLinkedRole, DeleteServiceLinkedRole, GetServiceLinkedRoleDeletionStatus, CreateOpenIDConnectProvider, ListOpenIDConnectProviders, CreateSAMLProvider, GetSAMLProvider, ListSAMLProviders, UpdateSAMLProvider, DeleteSAMLProvider, CreateLoginProfile, GetLoginProfile, UpdateLoginProfile, DeleteLoginProfile, CreateVirtualMFADevice, EnableMFADevice, DeactivateMFADevice, ResyncMFADevice, ListMFADevices, ListVirtualMFADevices, DeleteVirtualMFADevice, GetAccountAuthorizationDetails, GenerateServiceLastAccessedDetails, GetServiceLastAccessedDetails, TagRole, UntagRole, TagUser, UntagUser, TagPolicy, UntagPolicy, GenerateCredentialReport, GetCredentialReport, GetAccountSummary, GetAccountPasswordPolicy, UpdateAccountPasswordPolicy, DeleteAccountPasswordPolicy, ListAccountAliases, CreateAccountAlias, DeleteAccountAlias | Role descriptions and permissions boundaries round-trip through role reads; boundaries support put/delete updates. |
| STS | GetCallerIdentity, AssumeRole, GetSessionToken, AssumeRoleWithWebIdentity | |
| IMDS (EC2 Instance Metadata) | PUT /latest/api/token, GET /latest/meta-data/instance-id, GET /latest/meta-data/iam/security-credentials/, GET /latest/meta-data/iam/security-credentials/, GET /latest/meta-data/iam/info, GET /latest/meta-data/placement/{region,availability-zone,...}, GET /latest/dynamic/instance-identity/document | IMDSv1 + IMDSv2; default credential chain falls through to a ministack-instance-role document with ASIA* session creds. Point SDKs at ministack via AWS_EC2_METADATA_SERVICE_ENDPOINT=http://localhost:4566 (or ec2_metadata_service_endpoint in ~/.aws/config); set MINISTACK_IMDS_V2_REQUIRED=1 to require the token PUT |
| ECS Task Metadata V4 | GET /v4/, GET /v4/, GET /v4/, GET /v4/ | Per-container token injected as ECS_CONTAINER_METADATA_URI_V4 on every container started by RunTask. /task returns sibling containers in the same task. Containers reach the gateway via host.docker.internal (mapped through extra_hosts: host-gateway, so it works on user-defined Docker networks); networkMode: host containers use loopback. Volatile by design (stripped on persistence, cleared by /_ministack/reset) |
| ECS Container Credentials | GET /v2/credentials/ | The path real ECS exposes via AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/ (resolved by SDKs against 169.254.170.2). MiniStack serves the same path on the gateway and returns the AWS-strict 5-field credentials document (AccessKeyId, SecretAccessKey, Token, Expiration, RoleArn) — distinct from the IMDS shape served at /latest/meta-data/iam/security-credentials/. RunTask injects AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_CONTAINER_AUTHORIZATION_TOKEN (satisfies botocore's allow-list for non-loopback gateway hosts), and AWS_ENDPOINT_URL so unmodified SDKs inside the task fetch credentials and route service calls through MiniStack with no client config |
| SecretsManager | CreateSecret, GetSecretValue, ListSecrets, DeleteSecret, UpdateSecret, DescribeSecret, PutSecretValue, UpdateSecretVersionStage, RestoreSecret, RotateSecret, GetRandomPassword, ListSecretVersionIds, ReplicateSecretToRegions, TagResource, UntagResource, PutResourcePolicy, GetResourcePolicy, DeleteResourcePolicy, ValidateResourcePolicy | |
| CloudWatch Logs | CreateLogGroup, DeleteLogGroup, DescribeLogGroups, CreateLogStream, DeleteLogStream, DescribeLogStreams, PutLogEvents, GetLogEvents, FilterLogEvents, GetLogRecord, GetLogGroupFields, StartLiveTail, PutRetentionPolicy, DeleteRetentionPolicy, PutSubscriptionFilter, DeleteSubscriptionFilter, DescribeSubscriptionFilters, PutMetricFilter, DeleteMetricFilter, DescribeMetricFilters, TagLogGroup, UntagLogGroup, ListTagsLogGroup, TagResource, UntagResource, ListTagsForResource, StartQuery, GetQueryResults, StopQuery, PutDestination, DeleteDestination, DescribeDestinations, PutDestinationPolicy, PutDeliverySource, GetDeliverySource, DeleteDeliverySource, DescribeDeliverySources, PutDeliveryDestination, GetDeliveryDestination, DeleteDeliveryDestination, DescribeDeliveryDestinations, PutDeliveryDestinationPolicy, GetDeliveryDestinationPolicy, DeleteDeliveryDestinationPolicy, CreateDelivery, GetDelivery, DeleteDelivery, DescribeDeliveries, PutResourcePolicy, DescribeResourcePolicies, DeleteResourcePolicy | FilterLogEvents supports /? globs, multi-term AND, -term exclusion; GetLogRecord resolves Insights @ptr values issued at PutLogEvents; GetLogGroupFields scans recent stored events (optional time ±8m, else last 15m) and returns field names with rough presence percent, including system @ fields and flattened JSON message keys; Insights CWLI subset: fields, chained | filter (= / like /regex/), | sort @timestamp, post-filter | limit as min(query, API); StartLiveTail holds an AWS eventstream open until client disconnect and fans matching PutLogEvents into sessionUpdate frames (1s idle heartbeats; buffer of 10 updates, oldest dropped when full); use Config(inject_host_prefix=False) with a custom endpoint so boto3 does not prepend stream- to the hostname; DescribeLogGroups includes both arn (:*) and logGroupArn (no star) |
Extended Services
| Service | Operations | Notes |
|---------|-----------|-------|
| SSM Parameter Store | PutParameter, GetParameter, GetParameters, GetParametersByPath, DeleteParameter, DeleteParameters, DescribeParameters, GetParameterHistory, LabelParameterVersion, AddTagsToResource, RemoveTagsFromResource, ListTagsForResource | Supports String, SecureString, StringList; name:version and name:label selectors on GetParameter / GetParameters |
| EventBridge | CreateEventBus, UpdateEventBus, DeleteEventBus, ListEventBuses, DescribeEventBus, PutRule, DeleteRule, ListRules, DescribeRule, EnableRule, DisableRule, PutTargets, RemoveTargets, ListTargetsByRule, ListRuleNamesByTarget, PutEvents, TestEventPattern, TagResource, UntagResource, ListTagsForResource, CreateArchive, DeleteArchive, DescribeArchive, UpdateArchive, ListArchives, PutPermission, RemovePermission, CreateConnection, DescribeConnection, DeleteConnection, UpdateConnection, DeauthorizeConnection, ListConnections, CreateApiDestination, DescribeApiDestination, DeleteApiDestination, UpdateApiDestination, ListApiDestinations, StartReplay, DescribeReplay, ListReplays, CancelReplay, CreateEndpoint, DeleteEndpoint, DescribeEndpoint, ListEndpoints, UpdateEndpoint, ActivateEventSource, DeactivateEventSource, DescribeEventSource, CreatePartnerEventSource, DeletePartnerEventSource, DescribePartnerEventSource, ListPartnerEventSources, ListPartnerEventSourceAccounts, ListEventSources, PutPartnerEvents | Lambda target dispatch on PutEvents; S3 EventBridge notifications; archives capture matching events and StartReplay re-dispatches them to the destination bus in a background thread; API destination targets are invoked over HTTP on PutEvents — connection auth (Basic, API Key, OAuth client-credentials with token caching and 401/407 refresh), connection/target header/query/body parameter merging with connection precedence, path parameters, dynamic JSON-path parameter values resolved from the untransformed event, and the AWS default headers (User-Agent: Amazon/EventBridge/ApiDestinations, default Content-Type: application/json; charset=utf-8), which connection and target parameters cannot override; redirects are not followed (a 3xx is a non-retryable delivery failure), only http(s) invocation and OAuth authorization endpoints are accepted, HttpMethod and AuthorizationType are validated against their enums, and a connection that is not AUTHORIZED is not invoked (DeauthorizeConnection clears its stored credentials) — the 24h retry pipeline, rate limiting, and DLQs are not modeled; SaaS/partner APIs are otherwise control-plane stubs. Event patterns are validated — PutRule, TestEventPattern, CreateArchive and UpdateArchive reject a malformed pattern with InvalidEventPatternException carrying AWS's own reason text — and evaluate prefix/suffix (plain or case-insensitive via a nested equals-ignore-case), wildcard (AWS semantics — only is special, \ escapes — with catastrophic backtracking ruled out), equals-ignore-case (AWS's per-character case alternation), exactly, cidr (IPv4/IPv6, host bits floored, /32 and /128 refused as AWS refuses them), numeric (as the grammar it is: one comparison or a lower-then-upper bound), exists (leaf nodes only, so an object or an empty array does not exist), anything-but (a literal, a list, or a nested prefix/suffix/wildcard/equals-ignore-case, singly or as a list), and $or at any field position — expanded into flat alternatives at rule creation, so AWS's per-leaf last-write-wins collision rule and its 1000-combination cap both hold. All ten envelope fields are filterable, an unmodelled key is a path the event lacks rather than an error, resources is an intersection, array values are matched per element (with AWS's array-consistency rule), and values compare as JSON so a string never equals a number. An operand or key the matcher cannot evaluate never matches, rather than raising or matching everything. Not modeled: the 4096-character pattern limit, the wildcard-complexity refusal, and the 256-leaf rule-size cap |
| Kinesis | CreateStream, DeleteStream, DescribeStream, DescribeStreamSummary, ListStreams, ListShards, PutRecord, PutRecords, GetShardIterator, GetRecords, IncreaseStreamRetentionPeriod, DecreaseStreamRetentionPeriod, MergeShards, SplitShard, UpdateShardCount, StartStreamEncryption, StopStreamEncryption, EnableEnhancedMonitoring, DisableEnhancedMonitoring, RegisterStreamConsumer, DeregisterStreamConsumer, ListStreamConsumers, DescribeStreamConsumer, SubscribeToShard, AddTagsToStream, RemoveTagsFromStream, ListTagsForStream | Partition key → shard routing; AWS limits enforced (1 MB/record, 500 records/batch, 5 MB payload, 256-char partition key); SubscribeToShard pushes SubscribeToShardEvents over HTTP/1.1 or HTTP/2 (h2c) for up to 5 minutes, JSON or CBOR |
| CloudWatch Metrics | PutMetricData, GetMetricStatistics, GetMetricData, ListMetrics, PutMetricAlarm, PutCompositeAlarm, DescribeAlarms, DescribeAlarmsForMetric, DescribeAlarmHistory, DeleteAlarms, SetAlarmState, EnableAlarmActions, DisableAlarmActions, TagResource, UntagResource, ListTagsForResource, PutDashboard, GetDashboard, DeleteDashboards, ListDashboards | CBOR and JSON protocol |
| SES | SendEmail, SendRawEmail, SendTemplatedEmail, SendBulkTemplatedEmail, VerifyEmailIdentity, VerifyEmailAddress, VerifyDomainIdentity, VerifyDomainDkim, ListIdentities, ListVerifiedEmailAddresses, GetIdentityVerificationAttributes, GetIdentityDkimAttributes, DeleteIdentity, GetSendQuota, GetSendStatistics, SetIdentityNotificationTopic, SetIdentityFeedbackForwardingEnabled, CreateConfigurationSet, DeleteConfigurationSet, DescribeConfigurationSet, ListConfigurationSets, CreateTemplate, GetTemplate, UpdateTemplate, DeleteTemplate, ListTemplates | Emails stored in-memory, not sent |
| SES v2 | SendEmail, CreateEmailIdentity, GetEmailIdentity, DeleteEmailIdentity, ListEmailIdentities, CreateConfigurationSet, GetConfigurationSet, DeleteConfigurationSet, ListConfigurationSets, CreateEmailTemplate, GetEmailTemplate, UpdateEmailTemplate, DeleteEmailTemplate, ListEmailTemplates, GetAccount, PutAccountSuppressionAttributes, ListSuppressedDestinations, TagResource, UntagResource, ListTagsForResource | REST API (/v2/email/); identities auto-verified; emails stored in-memory, not sent; email templates share the v1 store, so SendEmail with Content.Template renders a template created through either API version |
| ACM | RequestCertificate, DescribeCertificate, ListCertificates, DeleteCertificate, GetCertificate, ImportCertificate, AddTagsToCertificate, RemoveTagsFromCertificate, ListTagsForCertificate, UpdateCertificateOptions, RenewCertificate, ResendValidationEmail | Certificates auto-issued; DNS validation records generated; supports SANs |
| Backup | CreateBackupVault, DescribeBackupVault, DeleteBackupVault, ListBackupVaults, CreateBackupPlan, GetBackupPlan, UpdateBackupPlan, DeleteBackupPlan, ListBackupPlans, ListBackupPlanVersions, CreateBackupSelection, GetBackupSelection, DeleteBackupSelection, ListBackupSelections, StartBackupJob, StopBackupJob, DescribeBackupJob, ListBackupJobs, TagResource, UntagResource, ListTags | In-memory; jobs complete immediately; vaults and plans participate in Resource Groups Tagging API |
| Bedrock | 66 operations — foundation-model catalog (real model IDs), inference profiles (system + application-managed), guardrails with versioning, custom models, imported models, provisioned model throughput, model customization / import / copy / batch-invocation job families, tagging | All 66 operations verified against botocore bedrock-2023-04-20; camelCase wire format per locationName; account- and region-scoped state |
| Bedrock Runtime | Converse, ConverseStream, InvokeModel, InvokeModelWithResponseStream, ApplyGuardrail, StartAsyncInvoke, GetAsyncInvoke, ListAsyncInvokes | Real eventstream wire format on streaming operations; guardrails enforce their deterministic policies — word policy, sensitive-information regexes, and the pattern-matchable PII types with BLOCK/ANONYMIZE per input/output stage — on ApplyGuardrail and on Converse/ConverseStream via guardrailConfig (GUARDRAIL_INTERVENED, blocked messaging, per-policy assessments, ResourceNotFoundException for an unknown id); the model-graded policies (topic, content, contextual grounding, PROFANITY) are not evaluated; deterministic family-aware mock responses selected by model ID prefix (anthropic., amazon.titan, amazon.nova, meta.llama, mistral., cohere., ai21.*); verified against botocore bedrock-runtime-2023-09-30 |
| Bedrock Agent | 72 operations — agents (versions, aliases, action groups, collaborators, agent knowledge bases, PrepareAgent), knowledge bases (data sources, ingestion jobs — StartIngestionJob reads the S3 data source and reports real statistics, FAILED with failureReasons when the source is unreadable — documents), flows (aliases, versions, ValidateFlowDefinition, PrepareFlow), prompts with versions, tagging | All 72 operations verified against botocore bedrock-agent-2023-06-05 |
| Bedrock Agent Runtime | InvokeAgent, InvokeInlineAgent, GetAgentMemory, DeleteAgentMemory, Retrieve, RetrieveAndGenerate, RetrieveAndGenerateStream, Rerank, CreateSession, GetSession, UpdateSession, DeleteSession, EndSession, ListSessions, CreateInvocation, ListInvocations, PutInvocationStep, GetInvocationStep, ListInvocationSteps, InvokeFlow, StartFlowExecution, StopFlowExecution, GetFlowExecution, ListFlowExecutions, ListFlowExecutionEvents, GetExecutionFlowSnapshot, OptimizePrompt | All 31 operations verified against botocore bedrock-agent-runtime-2023-07-26; eventstream responses on streaming operations; Retrieve searches the documents Knowledge Base ingestion stored (lexical scoring — no embeddings) and returns content, s3Location and score; InvokeAgent answers a canned reply without orchestration and emits no trace events — it never fabricates an ORCHESTRATE trace |
| Bedrock AgentCore | Control plane (bedrock-agentcore-control): CreateAgentRuntime, GetAgentRuntime, ListAgentRuntimes, UpdateAgentRuntime, DeleteAgentRuntime, ListAgentRuntimeVersions, CreateAgentRuntimeEndpoint, GetAgentRuntimeEndpoint, ListAgentRuntimeEndpoints, UpdateAgentRuntimeEndpoint, DeleteAgentRuntimeEndpoint, PutResourcePolicy, GetResourcePolicy, DeleteResourcePolicy. Data plane (bedrock-agentcore): InvokeAgentRuntime | Agent-runtime lifecycle with CREATING/UPDATING→READY transitions, versioned ARNs (agent/{uuid}:{version}, agentEndpoint/{uuid}); request/response shapes verified against botocore bedrock-agentcore-control / bedrock-agentcore; account- and region-scoped state; resource policies are evaluated for runtime and endpoint invocation when AUTH=true; InvokeAgentRuntime runs the runtime's containerUri image in Docker (full image), waits for /ping and forwards to /invocations on port 8080, with container errors as RuntimeClientError (424); without Docker (slim image) or for a code artifact it answers a deterministic echo |
| Transcribe | StartTranscriptionJob, GetTranscriptionJob, ListTranscriptionJobs, DeleteTranscriptionJob | Batch transcription jobs only; streaming is a separate endpoint (transcribestreaming). All four operations verified against botocore transcribe-2017-10-26. A job starts IN_PROGRESS with StartTime set, as on AWS, and reaches COMPLETED over two seconds; QUEUED, which AWS reaches only for a request that opted into job queueing (JobExecutionSettings.AllowDeferredExecution) while the account is at its concurrent job limit, is not modelled; the media object is read from MiniStack S3 via an s3://, path-style or virtual-hosted HTTPS URI, and an unreadable URI fails the job with a FailureReason rather than erroring at Start, as on AWS; the transcript is written back to S3 in the real result format (results.transcripts, word-level results.items, results.audio_segments, plus results.segments and results.speaker_labels when the matching Settings are set) at OutputBucketName/OutputKey, or to an auto-created ministack-transcribe-output bucket. IdentifyLanguage and IdentifyMultipleLanguages resolve a language on completion, ContentRedaction controls whether a redacted- copy replaces or accompanies the transcript, and Subtitles writes WebVTT and SubRip files, redacted alongside the transcript when redaction is on. Reaching a terminal state publishes a Transcribe Job State Change event, so EventBridge rules downstream of a job fire. There is no speech recognition, so the transcript is deterministic canned text keyed on a digest of the media URI |
| Translate | StartTextTranslationJob, DescribeTextTranslationJob, ListTextTranslationJobs, StopTextTranslationJob | Batch text translation jobs only; the synchronous TranslateText / TranslateDocument pair and the custom terminology / parallel data families are not implemented. All four operations verified against botocore translate-2017-07-01. Jobs walk SUBMITTED → IN_PROGRESS → COMPLETED over TRANSLATE_JOB_RUN_SECONDS (default 2, 0 completes immediately); every document under InputDataConfig.S3Uri is read from MiniStack S3 and written back to the rewritten output location AWS uses — OutputDataConfig.S3Uri is reported as s3:// — as , with a details/.auxiliary-translation-details.json summary per target language. text/plain is translated line by line, text/html keeps its markup and translates only text nodes, application/x-xliff+xml gets a populated in every plus target-language on every , and the three Office ZIP types are copied through. A document that can't be read as its content type is counted in JobDetails and the job ends COMPLETED_WITH_ERROR; an empty or unreadable input location fails the job with a Message rather than erroring at Start. ClientToken is idempotent, StopTextTranslationJob reaches a running worker, and reaching a terminal state publishes a Translate TextTranslationJob State Change event, so EventBridge rules downstream of a job fire. There is no machine translation, so the output is deterministic canned text derived from the source |
| MSK | CreateCluster, ListClusters, DescribeCluster, DeleteCluster, GetBootstrapBrokers, ListNodes, CreateConfiguration, ListConfigurations, DescribeConfiguration, ListConfigurationRevisions, DescribeConfigurationRevision, BatchAssociateScramSecret, BatchDisassociateScramSecret, ListScramSecrets, TagResource, UntagResource, ListTagsForResource | Kafka control plane; GetBootstrapBrokers honors MINISTACK_MSK_BOOTSTRAP so clients route to a real broker you bring (Redpanda, Kafka, KRaft) — the Kafka wire protocol itself is not emulated |
| WAF v2 | CreateWebACL, GetWebACL, UpdateWebACL, DeleteWebACL, ListWebACLs, AssociateWebACL, DisassociateWebACL, GetWebACLForResource, ListResourcesForWebACL, CreateIPSet, GetIPSet, UpdateIPSet, DeleteIPSet, ListIPSets, CreateRuleGroup, GetRuleGroup, UpdateRuleGroup, DeleteRuleGroup, ListRuleGroups, TagResource, UntagResource, ListTagsForResource, CheckCapacity, DescribeManagedRuleGroup | LockToken enforced on Update/Delete; resource associations tracked |
| Step Functions | CreateStateMachine, DeleteStateMachine, DescribeStateMachine, UpdateStateMachine, ListStateMachines, StartExecution, StartSyncExecution, StopExecution, DescribeExecution, DescribeStateMachineForExecution, ListExecutions, GetExecutionHistory, SendTaskSuccess, SendTaskFailure, SendTaskHeartbeat, CreateActivity, DeleteActivity, DescribeActivity, ListActivities, GetActivityTask, TestState, TagResource, UntagResource, ListTagsForResource | Full ASL interpreter; Retry/Catch; waitForTaskToken; Activities (worker pattern); Pass/Task/Choice/Wait/Succeed/Fail/Map/Parallel; TestState API with mock and inspectionLevel support; SFN_MOCK_CONFIG for AWS SFN Local compatible mock testing; intrinsic functions (States.StringToJson, States.JsonToString, States.JsonMerge, States.Format); nested startExecution.sync |
| API Gateway v2 | CreateApi, GetApi, GetApis, UpdateApi, DeleteApi, CreateRoute, GetRoute, GetRoutes, UpdateRoute, DeleteRoute, CreateIntegration, GetIntegration, GetIntegrations, UpdateIntegration, DeleteIntegration, CreateRouteResponse, GetRouteResponse, GetRouteResponses, UpdateRouteResponse, DeleteRouteResponse, CreateIntegrationResponse, GetIntegrationResponse, GetIntegrationResponses, UpdateIntegrationResponse, DeleteIntegrationResponse, CreateStage, GetStage, GetStages, UpdateStage, DeleteStage, CreateDeployment, GetDeployment, GetDeployments, DeleteDeployment, CreateAuthorizer, GetAuthorizer, GetAuthorizers, UpdateAuthorizer, DeleteAuthorizer, TagResource, UntagResource, GetTags, PostToConnection, GetConnection, DeleteConnection | HTTP API and WebSocket API (protocolType=WEBSOCKET); Lambda proxy (AWS_PROXY), HTTP proxy (HTTP_PROXY), and MOCK integrations; HTTP data plane via {apiId}.execute-api.localhost Host header or path-based /_aws/execute-api/{apiId}/{stage}/{path} (no DNS/Host override needed — works from browsers on macOS and strict clients); $default stage served from the URL root (no stage segment in the path); per-API corsConfiguration applied to preflights + dispatched responses; request parameter mapping for HTTP_PROXY (append/overwrite/remove for headers/querystring plus overwrite:path) with context variables including $context.authorizer.jwt.claims; JWT data-plane authorization for HTTP routes (issuer/audience/time/scope checks) and claim propagation to integrations; qualified-alias integration URIs (arn:...:function:) resolve to the alias's target version; WebSocket data plane on the same two URL forms, with $connect / $disconnect / $default / custom-action routing, $request.body.* RouteSelectionExpression, @connections management API (PostToConnection / GetConnection / DeleteConnection), per-connection outbox for server-side push; {param} / {proxy+} matching; JWT/Lambda authorizer CRUD; pin apiId across runs with the ms-custom-id tag |
| API Gateway v1 | CreateRestApi, GetRestApi, GetRestApis, UpdateRestApi, DeleteRestApi, CreateResource, GetResource, GetResources, UpdateResource, DeleteResource, PutMethod, GetMethod, DeleteMethod, UpdateMethod, PutMethodResponse, GetMethodResponse, DeleteMethodResponse, PutIntegration, GetIntegration, DeleteIntegration, UpdateIntegration, PutIntegrationResponse, GetIntegrationResponse, DeleteIntegrationResponse, CreateDeployment, GetDeployment, GetDeployments, UpdateDeployment, DeleteDeployment, CreateStage, GetStage, GetStages, UpdateStage, DeleteStage, CreateAuthorizer, GetAuthorizer, GetAuthorizers, UpdateAuthorizer, DeleteAuthorizer, CreateModel, GetModel, GetModels, DeleteModel, CreateDocumentationPart, GetDocumentationPart, GetDocumentationParts, UpdateDocumentationPart, DeleteDocumentationPart, CreateApiKey, GetApiKey, GetApiKeys, UpdateApiKey, DeleteApiKey, CreateUsagePlan, GetUsagePlan, GetUsagePlans, UpdateUsagePlan, DeleteUsagePlan, CreateUsagePlanKey, GetUsagePlanKeys, DeleteUsagePlanKey, Create
... (README truncated for length)