Every other page in this section holds one rule: the SDK provisions the database, and your app queries it with a driver. The Aurora Data API is the deliberate exception. It runs your SQL over HTTPS through the AWS SDK, with no driver, no open connection, and no pool to manage.
That makes it a natural fit for environments where connections are the enemy - Lambda functions that scale to hundreds of concurrent invocations, each of which would otherwise open its own database connection and exhaust the limit. The trade is higher per-call latency than a warm driver connection.
Enable the Data API on a cluster, then insert with named parameters, read the rows back, run a batch insert over many parameter sets, and wrap two statements in a transaction.
# --- Python (boto3) ---import boto3rds = boto3.client("rds", region_name="us-east-1")data = boto3.client("rds-data", region_name="us-east-1")CLUSTER = "arn:aws:rds:us-east-1:111122223333:cluster:app-aurora"SECRET = "arn:aws:secretsmanager:us-east-1:111122223333:secret:app-db-abc"DB = "appdb"# 0. Turn on the Data API for the cluster (one-time, control plane).rds.modify_db_cluster(DBClusterIdentifier="app-aurora", EnableHttpEndpoint=True, ApplyImmediately=True)# 1. Insert one row with named parameters (no string interpolation - safe from injection).data.execute_statement( resourceArn=CLUSTER, secretArn=SECRET, database=DB, sql="INSERT INTO users (id, email) VALUES (:id, :email)", parameters=[ {"name": "id", "value": {"longValue": 1}}, {"name": "email", "value": {"stringValue": "ada@x.io"}}, ],)# 2. Read it back.resp = data.execute_statement( resourceArn=CLUSTER, secretArn=SECRET, database=DB, sql="SELECT id, email FROM users WHERE id = :id", parameters=[{"name": "id", "value": {"longValue": 1}}],)print(resp["records"])# 3. Batch insert: one SQL statement, many parameter sets.data.batch_execute_statement( resourceArn=CLUSTER, secretArn=SECRET, database=DB, sql="INSERT INTO users (id, email) VALUES (:id, :email)", parameterSets=[ [{"name": "id", "value": {"longValue": 2}}, {"name": "email", "value": {"stringValue": "b@x.io"}}], [{"name": "id", "value": {"longValue": 3}}, {"name": "email", "value": {"stringValue": "c@x.io"}}], ],)# 4. Multi-statement transaction.tx = data.begin_transaction(resourceArn=CLUSTER, secretArn=SECRET, database=DB)["transactionId"]try: data.execute_statement( resourceArn=CLUSTER, secretArn=SECRET, database=DB, transactionId=tx, sql="UPDATE users SET email = :e WHERE id = :id", parameters=[{"name": "e", "value": {"stringValue": "ada@new.io"}}, {"name": "id", "value": {"longValue": 1}}], ) data.commit_transaction(resourceArn=CLUSTER, secretArn=SECRET, transactionId=tx)except Exception: data.rollback_transaction(resourceArn=CLUSTER, secretArn=SECRET, transactionId=tx) raise
// --- TypeScript (AWS SDK v3) ---import { RDSDataClient, ExecuteStatementCommand, BatchExecuteStatementCommand, BeginTransactionCommand, CommitTransactionCommand, RollbackTransactionCommand,} from "@aws-sdk/client-rds-data";import { RDSClient, ModifyDBClusterCommand } from "@aws-sdk/client-rds";const rds = new RDSClient({ region: "us-east-1" });const data = new RDSDataClient({ region: "us-east-1" });const CLUSTER = "arn:aws:rds:us-east-1:111122223333:cluster:app-aurora";const SECRET = "arn:aws:secretsmanager:us-east-1:111122223333:secret:app-db-abc";const DB = "appdb";// 0. Turn on the Data API for the cluster (one-time, control plane).await rds.send(new ModifyDBClusterCommand({ DBClusterIdentifier: "app-aurora", EnableHttpEndpoint: true, ApplyImmediately: true }));// 1. Insert one row with named parameters (no string interpolation - safe from injection).await data.send(new ExecuteStatementCommand({ resourceArn: CLUSTER, secretArn: SECRET, database: DB, sql: "INSERT INTO users (id, email) VALUES (:id, :email)", parameters: [ { name: "id", value: { longValue: 1 } }, { name: "email", value: { stringValue: "ada@x.io" } }, ],}));// 2. Read it back.const resp = await data.send(new ExecuteStatementCommand({ resourceArn: CLUSTER, secretArn: SECRET, database: DB, sql: "SELECT id, email FROM users WHERE id = :id", parameters: [{ name: "id", value: { longValue: 1 } }],}));console.log(resp.records);// 3. Batch insert: one SQL statement, many parameter sets.await data.send(new BatchExecuteStatementCommand({ resourceArn: CLUSTER, secretArn: SECRET, database: DB, sql: "INSERT INTO users (id, email) VALUES (:id, :email)", parameterSets: [ [{ name: "id", value: { longValue: 2 } }, { name: "email", value: { stringValue: "b@x.io" } }], [{ name: "id", value: { longValue: 3 } }, { name: "email", value: { stringValue: "c@x.io" } }], ],}));// 4. Multi-statement transaction.const { transactionId } = await data.send(new BeginTransactionCommand({ resourceArn: CLUSTER, secretArn: SECRET, database: DB }));try { await data.send(new ExecuteStatementCommand({ resourceArn: CLUSTER, secretArn: SECRET, database: DB, transactionId, sql: "UPDATE users SET email = :e WHERE id = :id", parameters: [{ name: "e", value: { stringValue: "ada@new.io" } }, { name: "id", value: { longValue: 1 } }], })); await data.send(new CommitTransactionCommand({ resourceArn: CLUSTER, secretArn: SECRET, transactionId }));} catch (err) { await data.send(new RollbackTransactionCommand({ resourceArn: CLUSTER, secretArn: SECRET, transactionId })); throw err;}
What this demonstrates:
Every call carries the resourceArn (cluster) and secretArn (credentials) - there is no connection object.
Named parameters (:id) with typed values keep SQL parameterized and injection-safe.
BatchExecuteStatement runs one statement across many parameter sets in a single call.
A transaction is a transactionId you thread through statements, then commit or roll back.
The Data API is an HTTPS endpoint in front of your Aurora cluster. You call ExecuteStatement on the rds-data client with a SQL string; AWS authenticates using the referenced Secrets Manager secret, runs the statement on the cluster, and returns rows in the response. There is no wire-protocol connection, no TLS handshake to the database, and nothing to pool. Your IAM principal needs permission for both rds-data:* actions and secretsmanager:GetSecretValue on the secret.
You enable it per cluster with EnableHttpEndpoint (on ModifyDBCluster, or --enable-http-endpoint at create). It is supported on Aurora - notably Aurora Serverless v2 and provisioned Aurora PostgreSQL and MySQL - not on standard RDS engines.
A database has a hard connection limit. Traditional drivers hold a connection open, and a fleet of Lambda functions that each open one can exhaust the limit under load. The Data API sidesteps this entirely: each call is a stateless HTTPS request, so a thousand concurrent Lambdas make a thousand requests without a thousand persistent connections. That is the core reason to choose it. The alternative for driver-based access at scale is RDS Proxy, covered on its own page.
Parameters are typed unions: longValue, stringValue, booleanValue, doubleValue, blobValue, isNull. Passing values this way keeps every query parameterized, so user input can never alter the SQL. Results come back as records, a list of rows where each field is the same typed union. Pass formatRecordsAs="JSON" (SDK: formatRecordsAs: "JSON") to get a JSON string of rows instead of the column-array shape, which many callers find easier to consume.
Each Data API call has more overhead than issuing SQL on a warm connection - you are paying for an HTTPS round trip and per-call auth. For chatty, latency-critical workloads a pooled driver (or RDS Proxy) is faster. The API also caps result-set size per call, so very large reads should paginate with SQL LIMIT/OFFSET or keyset pagination rather than pulling everything at once.
Missing IAM permissions. Calls fail without both rds-data and secretsmanager:GetSecretValue. Fix: grant both to the caller's role, scoped to the secret.
HTTP endpoint not enabled. The API rejects a cluster without it. Fix: set EnableHttpEndpoint=True via ModifyDBCluster (or at create).
Using it on standard RDS. The Data API is Aurora-only. Fix: use a driver (or RDS Proxy) for non-Aurora engines.
Interpolating values into SQL. String-building reintroduces injection risk. Fix: always pass typed named parameters.
Pulling huge result sets in one call. The API caps response size. Fix: paginate with SQL LIMIT or keyset pagination.
Expecting driver-level latency. Each call is an HTTPS request with auth overhead. Fix: use a pooled driver or RDS Proxy for chatty, latency-sensitive paths.
It is an HTTPS interface that runs SQL against an Aurora cluster through the AWS SDK, with no database driver, connection, or pool. You call ExecuteStatement with the cluster ARN and a Secrets Manager ARN, and rows come back in the SDK response.
Why use it instead of a normal driver?
Because it is connectionless. Short-lived, highly concurrent callers like Lambda would otherwise open many database connections and hit the limit. Each Data API call is a stateless HTTPS request, so concurrency does not translate into persistent connections.
What do resourceArn and secretArn point to?
resourceArn is the Aurora cluster's ARN, and secretArn is a Secrets Manager secret holding the database credentials. Every Data API call supplies both, and the caller's IAM role needs permission for rds-data actions and to read the secret.
Does the Data API work with standard RDS?
No. It is an Aurora feature (including Aurora Serverless v2 and provisioned Aurora PostgreSQL/MySQL). For standard RDS engines, connect with a normal driver, and use RDS Proxy if you need pooling at scale.
How do I run a transaction?
Call BeginTransaction to get a transactionId, pass that id on each ExecuteStatement, then call CommitTransaction (or RollbackTransaction on error). All statements sharing the id run in one transaction.
How do I avoid SQL injection with the Data API?
Never interpolate user input into the SQL string. Use named placeholders like :id and pass typed values through the parameters list, so input is bound as data and can never change the statement.
Is it slower than a direct connection?
Per call, yes. Each request is an HTTPS round trip with authentication overhead, so a warm pooled driver is faster for chatty workloads. The Data API wins on operational simplicity and connection safety, not raw latency.
How do I run the same insert for many rows?
Use BatchExecuteStatement with one sql string and a parameterSets list, where each entry is one row's parameters. It executes the statement once per set in a single call, which is far more efficient than many individual ExecuteStatement calls.