Request lifecycle

Cold start

On the first request a new Lambda execution environment imports lambda_handlers:

  1. config is imported, whose first line imports secrets_loader. If APP_SECRET_ID is set and AWS_SECRETS_MANAGER is not "False", the JSON secret is fetched and each key is copied into os.environ with setdefault (an explicit environment variable wins).

  2. run.py calls create_app("config"): flask-restx namespaces and blueprints are registered under BASE_PREFIX.

  3. Importing pyb_flask_utils.auth loads the shared common/crm secret (instance-id tokens and, if not already in the environment, AUTH_JWT_RSA_PUBLIC_KEY).

  4. setup_sentry() initialises Sentry outside dev/local/test.

A cold start therefore costs two GetSecretValue calls. Warm invocations reuse everything.

Paths and stages

API Gateway passes the full request path to Lambda in event["path"], and apig-wsgi uses it as PATH_INFO:

Request event["path"] Matches

https://api.payability.com/actionstools/notes/ (custom domain, base path actionstools)

/actionstools/notes/

BASE_PREFIX + "/notes/" in production

https://<api-id>.execute-api.us-east-1.amazonaws.com/prod/actionstools/notes/

/actionstools/notes/

same

https://<api-id>.execute-api.us-east-1.amazonaws.com/stage/notes/

/notes/

BASE_PREFIX is empty outside production

The stage name is never part of event["path"]; the custom-domain base path is. That is why production keeps BASE_PREFIX = "/actionstools" (config.py).

Request

  1. CORS preflight. OPTIONS is answered by API Gateway (mock integration from the Cors block in template.yaml), never reaching Lambda.

  2. Routing. /{proxy+} → ApiFunction → apig_wsgi → Flask.

  3. Authentication. Every resource uses one of the decorators from pyb_flask_utils.auth — see api/index.adoc#authentication. On success the view receives login_id (email) and login_key (user uuid).

  4. Header context. Helper(HEADERS).parse_headers maps X-Actions-* and instance headers to target_uuid, target_type, owner_uuid, instance_id (config.HEADERS).

  5. Service call. The view calls a service in the same package, which reads or writes DynamoDB and, for owner-only fields, asks the Payability internal API whether the user is the owner or a CRM_ADMIN super user.

  6. Response. prepare_json_response wraps everything in the {data, meta, status_code} envelope. Exceptions are converted by handle_errors / PybApi.handle_error.

Side effects

Anything slow or best-effort is queued with @async_task:

Trigger Async task

Task created (sync mode)

send_notification_to_new_assignee → emails/Slack to the assignee and followers

Task created with "_mode": "async"

async_create_task — the whole creation runs on the Worker

Task updated by a logged-in user

send_events_and_notifications → event logger entries plus notifications

Comment on a task

do_notify → owner, assignee and followers

Each Slack DM

notify_on_slack

First request for an unknown counter

async_calculate_count

The API returns as soon as the lambda:Invoke (type Event) is accepted. See Worker Lambda & async tasks for what happens next and how failures surface.