Tenancy model
How the API keeps one account's data out of another's.
The API is multi-tenant: one deployment serves every account. Isolation is enforced at the application layer, because API Gateway sits between callers and DynamoDB and IAM sees only the API's own execution role, not the end user.
The rule is simple: keys are built from the token's sub claim, never from
request input.
What that means per route
| Data | How it is scoped |
|---|---|
| Aliases | Read through an index partitioned by USER#<sub>; writes additionally carry an ownership condition. |
| Recipients | Stored under USER#<sub> / RECIPIENT#<id>; the id alone cannot reach another account's item. |
| Profile, subscription | Stored under USER#<sub>; addressed only by the claim. |
| Alias addresses | Globally unique across all accounts, like any email address. |
Because the partition key comes from the claim, a valid id from a different
account simply matches nothing. Routes that look up a recipient by id therefore
return 404, not 403 — the API does not confirm that a foreign id exists.
Why not request parameters
No route accepts a user_id or email-address owner. alias_id and
recipient_id are opaque; they select within the caller's own partition. If you
find yourself wanting to pass an owner, the route you want does not exist.
Conditions on writes
Reads are structurally scoped by the partition key. Mutations that reach an alias
by its globally-unique address additionally carry user_sub = <caller> as a
condition, so a race or a bug cannot rewrite someone else's alias.
Quotas
Alias and recipient counts live on the profile item and are moved in the same
transaction as the create or delete that changes them, so a quota can never
drift from the rows it counts. GET /account reports both counts; limits come
from GET /subscription.
Deletion
DELETE /account removes the Cognito user and every item it owns. A hard delete,
like aliases and recipients individually: there is no tombstone and no restore.