# Buggy code
def setup_app(config={}):
    LOG.info("Creating st2api: %s as OpenAPI app.", VERSION_STRING)

    is_gunicorn = config.get("is_gunicorn", False)
    if is_gunicorn:
        # Note: We need to perform monkey patching in the worker. If we do it in
        # the master process (gunicorn_config.py), it breaks tons of things
        # including shutdown
        monkey_patch()

        st2api_config.register_opts()
        # This should be called in gunicorn case because we only want
        # workers to connect to db, rabbbitmq etc. In standalone HTTP
        # server case, this setup would have already occurred.
        common_setup(
            service="api",
            config=st2api_config,
            setup_db=True,
            register_mq_exchanges=True,
            register_signal_handlers=True,
            register_internal_trigger_types=True,
            run_migrations=True,
            config_args=config.get("config_args", None),
        )

    # Additional pre-run time checks
    validate_rbac_is_correctly_configured()

    router = Router(debug=cfg.CONF.api.debug, auth=cfg.CONF.auth.enable)

    spec = spec_loader.load_spec("st2common", "openapi.yaml.j2")
    transforms = {
        "^/api/v1/": ["/", "/v1/", "/v1"],
        "^/api/v1/executions": ["/actionexecutions", "/v1/actionexecutions"],
        "^/api/exp/": ["/exp/"],
    }
    router.add_spec(spec, transforms=transforms)

    app = router.as_wsgi

    # Order is important. Check middleware for detailed explanation.
    app = StreamingMiddleware(app, path_whitelist=["/v1/executions/*/output*"])
    app = ErrorHandlingMiddleware(app)
    app = CorsMiddleware(app)
    app = LoggingMiddleware(app, router)
    app = RequestIDMiddleware(app)

    return app


# Traceback (ValidationError)
2018-07-19 18:31:46,668 INFO [-] f590ed84-3e54-4634-89f2-120eb3f956e5 - POST /tokens with query={} (remote_addr='127.0.0.1',method='POST',request_id='f590ed84-3e54-4634-89f2-120eb3f956e5',query={},path='/tokens')
2018-07-19 18:31:46,669 DEBUG [-] Recieved call with WebOb: POST /tokens HTTP/1.0
Accept: */*
Authorization: Basic c3QyYWRtaW46Q2hAbmdlTWU=
Content-Type: text/plain
Host: 127.0.0.1:9100
User-Agent: curl/7.47.0
X-Request-Id: f590ed84-3e54-4634-89f2-120eb3f956e5
2018-07-19 18:31:46,669 DEBUG [-] Match path: /tokens
2018-07-19 18:31:46,670 DEBUG [-] Parsed endpoint: {'x-parameters': [{'in': 'environ', 'type': 'string', 'description': 'source of the request', 'name': 'remote_addr'}, {'in': 'environ', 'type': 'string', 'description': 'set externally to indicate user identity in case of proxy auth', 'name': 'remote_user'}], 'responses': {'201': {'headers': {'x-api-url': {'type': 'string'}}, 'schema': {'$ref': '#/definitions/Token'}, 'examples': {'application/json': {'user': 'st2admin', 'token': '5e86421776f946e98faea36c29e5a7c7', 'expiry': '2016-05-28T12:39:28.650231Z', 'id': '574840001878c10d0b6e8fbf', 'metadata': {}}}, 'description': 'New token has been created'}, 'default': {'description': 'Unexpected error', 'schema': {'$ref': '#/definitions/Error'}}, '401': {'schema': {'$ref': '#/definitions/Error'}, 'examples': {'application/json': {'faultstring': 'Invalid or missing credentials'}}, 'description': 'Invalid or missing credentials has been provided'}}, 'parameters': [{'in': 'header', 'type': 'string', 'description': 'base64 encoded string containing login and password', 'name': 'authorization'}, {'in': 'header', 'type': 'string', 'description': 'set externally to indicate real source of the request', 'name': 'x-forwarded-for'}, {'in': 'body', 'description': 'Lifespan of the token', 'name': 'request', 'schema': {'$ref': '#/definitions/TokenRequest'}}], 'x-log-result': False, 'security': [], 'description': 'Authenticates a user with `Authorization` header and returns a `token`\nobject.\n', 'operationId': 'st2auth.controllers.v1.auth:token_controller.post'}
2018-07-19 18:31:46,670 DEBUG [-] Parsed path_vars: {}
2018-07-19 18:31:46,700 DEBUG [-] API call failed: Traceback (most recent call last):
File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/router.py", line 372, in __call__
CustomValidator(schema, resolver=self.spec_resolver).validate(data)
File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/jsonschema/validators.py", line 130, in validate
raise error
ValidationError: '{}' is not of type 'object'

Failed validating 'type' in schema:
{'properties': {'ttl': {'minimum': 1, 'type': ['integer', 'null']}},
'type': 'object'}

On instance:
'{}'
(exception_message="'{}' is not of type 'object'",exception_data={'comment': 'Traceback (most recent call last):\n  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/router.py", line 372, in __call__\n    CustomValidator(schema, resolver=self.spec_resolver).validate(data)\n  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/jsonschema/validators.py", line 130, in validate\n    raise error\nValidationError: \'{}\' is not of type \'object\'\n\nFailed validating \'type\' in schema:\n    {\'properties\': {\'ttl\': {\'minimum\': 1, \'type\': [\'integer\', \'null\']}},\n     \'type\': \'object\'}\n\nOn instance:\n    \'{}\'\n', '_headers': None, 'conditional_response': False, 'detail': "'{}' is not of type 'object'", '_app_iter': [''], '_status': '400 Bad Request', '_headerlist': [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]},exception_class='HTTPBadRequest')
2018-07-19 18:31:46,701 DEBUG [-] Match path: /tokens
2018-07-19 18:31:46,701 INFO [-] f590ed84-3e54-4634-89f2-120eb3f956e5 - 400 53 33.332ms (content_length=53,request_id='f590ed84-3e54-4634-89f2-120eb3f956e5',runtime=33.332,remote_addr='127.0.0.1',status=400,method='POST',path='/tokens')

# Fixed code
def setup_app(config={}):
    LOG.info("Creating st2api: %s as OpenAPI app.", VERSION_STRING)

    is_gunicorn = config.get("is_gunicorn", False)
    if is_gunicorn:
        # Note: We need to perform monkey patching in the worker. If we do it in
        # the master process (gunicorn_config.py), it breaks tons of things
        # including shutdown
        monkey_patch()

        st2api_config.register_opts()
        # This should be called in gunicorn case because we only want
        # workers to connect to db, rabbbitmq etc. In standalone HTTP
        # server case, this setup would have already occurred.
        common_setup(
            service="api",
            config=st2api_config,
            setup_db=True,
            register_mq_exchanges=True,
            register_signal_handlers=True,
            register_internal_trigger_types=True,
            run_migrations=True,
            config_args=config.get("config_args", None),
        )

    # Additional pre-run time checks
    validate_rbac_is_correctly_configured()

    router = Router(
        debug=cfg.CONF.api.debug, auth=cfg.CONF.auth.enable, is_gunicorn=is_gunicorn
    )

    spec = spec_loader.load_spec("st2common", "openapi.yaml.j2")
    transforms = {
        "^/api/v1/": ["/", "/v1/", "/v1"],
        "^/api/v1/executions": ["/actionexecutions", "/v1/actionexecutions"],
        "^/api/exp/": ["/exp/"],
    }
    router.add_spec(spec, transforms=transforms)

    app = router.as_wsgi

    # Order is important. Check middleware for detailed explanation.
    app = StreamingMiddleware(app, path_whitelist=["/v1/executions/*/output*"])
    app = ErrorHandlingMiddleware(app)
    app = CorsMiddleware(app)
    app = LoggingMiddleware(app, router)
    app = RequestIDMiddleware(app)

    return app