# Speakeasy retries can be configured with a top-level extension
x-speakeasy-retries:
  strategy: backoff
  statusCodes: [5XX]
  retryConnectionErrors: true
```

To add global retries, we need to customize the schema generated by the FastAPI `get_openapi` function:

```python
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi

app = FastAPI(
    summary="A simple API to manage burgers and orders",
    description="This API is used to manage burgers and orders in a restaurant",
    version="0.1.0",
    title="APItizing Burger API",
)

def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema

    openapi_schema = get_openapi(
        title=app.title,
        version=app.version,
        summary=app.summary,
        description=app.description,
        routes=app.routes,
    )

    # Add retries
    openapi_schema["x-speakeasy-retries"] = {
        "strategy": "backoff",
        "backoff": {
            "initialInterval": 500,
            "maxInterval": 60000,
            "maxElapsedTime": 3600000,
            "exponent": 1.5,
        },
        "statusCodes": [
            "5XX",
        ],
        "retryConnectionErrors": True,
    }

    app.openapi_schema = openapi_schema
    return app.openapi_schema


app.openapi = custom_openapi
```

Keep in mind that you'll need to add this customization _after_ declaring your operation routes.

This change adds the following top-level section to `openapi.yaml`:

```yaml
x-speakeasy-retries:
  backoff:
    exponent: 1.5
    initialInterval: 500
    maxElapsedTime: 3600000
    maxInterval: 60000
  retryConnectionErrors: true
  statusCodes:
    - 5XX
  strategy: backoff
```

To add `x-speakeasy-retries` to a single operation, update the operation and add the `openapi_extra` parameter as follows:

```python
from fastapi import FastAPI

app = FastAPI()

@app.get(
    "/burger/",
    openapi_extra={
        "x-speakeasy-retries": {
            "strategy": "backoff",
            "backoff": {
                "initialInterval": 500,
                "maxInterval": 60000,
                "maxElapsedTime": 3600000,
                "exponent": 1.5,
            },
            "statusCodes": [
                "5XX",
            ],
            "retryConnectionErrors": True,
        }
    },
)
def list_burgers():
    return []
```

### Authentication and security

FastAPI supports several authentication mechanisms that can easily be integrated into your API.

The example below demonstrates adding an API key authentication scheme to the `/burger/` endpoint of our API. We use the `APIKeyHeader` dependency to validate the API key passed in the `Authorization` header:

```python
from fastapi.security import APIKeyHeader

API_KEY = "your-apitizing-api-key"

header_scheme = APIKeyHeader(
    name=API_KEY,
    auto_error=True,
    description="API Key for the Burger listing API. API Key should be sent as a header, with the value 'your-apitizing-api-key'",
    scheme_name="api_key",
)
```

We can pass a `key` parameter to the `list_burgers` function, retrieve the API key from the header, and perform validation:

```python
@app.get(
    "/burger/",
    response_model=List[BurgerOutput],
    tags=["burger"],
    ...
)
def list_burgers(key: str = Depends(header_scheme)):
    """List all burgers"""

    if key != API_KEY:
        raise HTTPException(status_code=401, detail="Invalid API Key")

    return [BurgerOutput(**burger_data.dict()) for burger_data in burgers_db]
```

Now when generating the OpenAPI document, the API key authentication scheme will be included and only required for the listing on the `/burger/` endpoint.

### Handling form data

Form data is a common way to receive information from clients, particularly in web applications. FastAPI provides robust support for form data through its `Form` class, and it correctly documents these form fields in the OpenAPI schema.

When working with form data in FastAPI, you need to use the `Form` class from the `fastapi` module. This ensures that FastAPI correctly adds the appropriate schema information to your OpenAPI document.

```python
from fastapi import FastAPI, Form
from typing import Annotated

app = FastAPI()

@app.post("/burger/create/")
async def create_burger_form(
    name: Annotated[str, Form()],
    description: Annotated[str, Form()],
    price: Annotated[float, Form()]
):
    """Create a new burger using form data"""
    return {
        "name": name,
        "description": description,
        "price": price
    }
```

In the OpenAPI document, FastAPI correctly identifies this endpoint as accepting form data:

```yaml
paths:
  /burger/create/:
    post:
      summary: Create Burger Form
      operationId: createBurgerForm
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                price:
                  type: number
              required:
                - name
                - description
                - price
      responses:
        "200":
          description: Successful Response
          content:
            application/json:
              schema: {}
      tags:
        - burger
```

When Speakeasy generates an SDK from this OpenAPI document, it will correctly handle form data submissions. The generated SDK will provide a clean interface for submitting form data:

```python
import sdk
from sdk.models import operations

s = sdk.SDK()

req = operations.CreateBurgerFormRequest(
    name="Classic Burger",
    description="Our signature burger with special sauce",
    price=8.99
)

res = s.burger.create_burger_form(req)
```

#### File uploads

FastAPI also supports file uploads, both as individual files or as multiple files. The OpenAPI schema will correctly document these endpoints as accepting multipart form data.

```python
from fastapi import FastAPI, File, UploadFile
from typing import Annotated

app = FastAPI()

@app.post("/burger/image/")
async def upload_burger_image(
    burger_id: Annotated[int, Form()],
    image: Annotated[UploadFile, File()]
):
    """Upload an image for a burger"""
    contents = await image.read()
    # Process file contents...
    return {
        "burger_id": burger_id,
        "filename": image.filename,
        "content_type": image.content_type,
        "size": len(contents)
    }
```

This endpoint will be documented in the OpenAPI schema with `multipart/form-data` content type, allowing Speakeasy to generate appropriate SDK code for handling file uploads.

#### Advanced form validation

For form data that needs validation beyond simple type checking, you can combine Pydantic models with the `Form` class. First, define your model with the validation rules:

```python
from pydantic import BaseModel, Field

class BurgerFormData(BaseModel):
    name: str = Field(..., min_length=3, max_length=50)
    description: str = Field(..., min_length=10, max_length=200)
    price: float = Field(..., gt=0, le=100)
```

Then use the model fields with form data:

```python
@app.post("/burger/create/validated/")
async def create_burger_validated(
    name: Annotated[str, Form()],
    description: Annotated[str, Form()],
    price: Annotated[float, Form()]
):
    burger_data = BurgerFormData(
        name=name,
        description=description,
        price=price
    )
    return burger_data
```

FastAPI will generate an OpenAPI document that includes these validation constraints, allowing SDK users to understand the requirements before submitting the form.

## Summary

In this post, we've explored how you can set up a FastAPI-based SDK generation pipeline without hand-editing or updating OpenAPI documents. By using existing FastAPI methods for extending and customizing OpenAPI documents, you can improve the usability of your generated client SDKs.