# Map API Entities to Terraform Resources
Source: https://speakeasy.com/docs/terraform/customize/entity-mapping

import { Callout } from "@/mdx/components";



## Entity Mapping

Add the `x-speakeasy-entity` extension to objects in your OpenAPI Specification document to include them as entities in the Terraform provider, such as managed resources. The extension value may be a single string or an array of strings if the object should be represented by multiple API entities.

As a component:

```yaml
components:
  schemas:
    Order:
      description: An order helps you make coffee
      x-speakeasy-entity: Order
      properties:
        id:
          type: integer
          description: Numeric identifier of the order.
        name:
          type: string
          description: Product name of the coffee.
        price:
          type: number
          description: Suggested cost of the coffee.
      required:
        - name
        - price
      type: object
```

Or inline in a path:

```yaml
paths:
  /order:
    post:
      tags:
        - Order
      summary: Create a coffee order
      x-speakeasy-entity-operation: Order#create
      requestBody:
        content:
          application/json:
            schema:
              x-speakeasy-entity: Order
              properties:
                id:
                  type: integer
                  description: Numeric identifier of the order.
                name:
                  type: string
                  description: Product name of the coffee.
                price:
                  type: number
                  description: Suggested cost of the coffee.
              required:
                - name
                - price
              type: object
```

```hcl
resource "yourprovider_order" "example" {
  name  = "Filter Blend"
  price = 11.5
}
```

Where you place the `x-speakeasy-entity` annotation affects the Terraform resource schema structure.

- **At the top level:** Properties are nested objects.
- **At a lower level:** Properties above the annotation are flattened.

### Top Level

```yaml
Pet:
  x-speakeasy-entity: Order
  type: object
  properties:
    data:
      type: object
      properties:
        name:
          type: string
        # ...
```

Results in the following resource schema and configuration:

```hcl
resource "yourprovider_order" "example" {
  data = {
    name = "Filter Blend"
  }
}
```

### Lower Level

```yaml
Pet:
  type: object
  properties:
    data:
      x-speakeasy-entity: Order
      type: object
      properties:
        name:
          type: string
        #...
```

Results in the following resource schema and configuration:

```hcl
resource "yourprovider_order" "example" {
  name = "Filter Blend"
}
```

<Callout title="Warning" type="warning">
  Properties above the `x-speakeasy-entity` annotation are flattened, which
  could cause conflicts. Apply the annotation carefully to align the structure
  of the Terraform provider with the API&apos;s intended interaction.
</Callout>

## Specify CRUD Operations for API Endpoints

The `x-speakeasy-entity-operation` annotation specifies CRUD (create, read, update, and delete) operations associated with each endpoint in the OpenAPI spec for a Terraform entity. The value determines the behavior of operations such as create, read, update, and delete and is structured as `Entity#operation,operation,...#order`:

- `Entity` represents the name of the entity.
- `operation` can be one or more of `create`, `read`, `update`, and `delete`, concatenated with commas.
- `order` is optional and can be used to define additional API calls that should be invoked for a given CRUD invocation.

### Behavior of Operations

- `Entity#create` makes the entity a Terraform resource.
- `Entity#read` ensures consistency with Terraform state, updates attributes, and generates a data source.
- `Entity#update` provides update support for the resource. Without it, any attribute change requires resource replacement (`ForceNew`).
- `Entity#delete` enables deletion of the resource. Without it, no action is taken on deletion.
- `Entity#create,update` **(idempotent operations)** indicates the API is idempotent. Combine these operations to allow the same API call to create new objects and update existing ones, depending on attribute changes.

In this example, a Pet managed resource with full create, read, update, and delete lifecycle and Pet data resource (due to `Pet#read`) are defined:

```yaml
paths:
  /pet:
    post:
      tags:
        - pet
      summary: Add a new pet to the store
      x-speakeasy-entity-operation: Pet#create
  /pet/{petId}:
    get:
      tags:
        - pet
      summary: Info for a specific pet
      x-speakeasy-entity-operation: Pet#read
    put:
      tags:
        - pet
      summary: Update the pet
      x-speakeasy-entity-operation: Pet#update
    delete:
      tags:
        - pet
      summary: Delete the pet
      x-speakeasy-entity-operation: Pet#delete
```

<Callout title="Note" type="info">
  Terraform generation automatically handles pagination implementation details
  via the `x-speakeasy-pagination`
  [extension](/docs/customize/runtime/pagination). This includes paging through
  all responses and removing unnecessary pagination handling properties from the
  schema.
</Callout>

In this example, an automatically paginated Pets data resource is defined:

```yaml
paths:
  /pet:
    get:
      tags:
        - pet
      summary: Lists all pets
      x-speakeasy-entity-operation: Pets#read
      x-speakeasy-pagination:
        type: offsetLimit
        inputs:
          - name: page
            in: parameters
            type: page
        outputs:
          results: $.results
```

### Multiple API Operations for One Resource

When multiple API operations are necessary for a single resource, use the additional entity-ordering capabilities of the `x-speakeasy-entity-operation` annotation.

```yaml
paths:
  /pet/{petId}:
    get:
      x-speakeasy-entity-operation: Pet#read#1
  /animal:
    get:
      x-speakeasy-entity-operation: Pet#read#2
```

Multiple API operations for one resource can be combined with multiple entity operations of [one API operation for multiple resources](#one-api-operation-for-multiple-resources) as necessary.

If additional API operations have transient properties, such as an asynchronous task identifier necessary for polling, use `x-speakeasy-terraform-ignore: schema` to remove them from the Terraform schema.

### One API Operation for Multiple Resources

When a single API operation is necessary for multiple resources, use multiple entity operation entries with the `x-speakeasy-entity-operation` annotation.

```yaml
parameters:
  - in: query
    name: id
    required: false
    schema:
      type: string
operationId: GetAnimal
x-speakeasy-entity-operation:
  - Cat#read
  - Dog#read
```

One API operation for multiple resources can be combined with the entity operation ordering of [multiple API operations for one resource](#multiple-api-operations-for-one-resource) as necessary.

If the resource creation requires both a create and update operation where the create response includes an API assigned resource identifier, mark the update request resource identifier property as read only via `x-speakeasy-param-readonly: true` to ensure the property remains non-configurable in the schema.

### API Operation Polling

Define automatic API operation polling logic for APIs with asynchronous behaviors via the `x-speakeasy-polling` and `x-speakeasy-entity-operation` extensions in the OpenAPI Specification document. Refer to the [SDK Polling documentation](/docs/customize/runtime/polling) for additional information about configuring `x-speakeasy-polling`.

In this example:

```yaml
/task:
  post:
    x-speakeasy-entity-operation: Task#create#1
/task/{id}:
  get:
    responses:
      "200":
        description: OK
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                status:
                  type: string
                  enum:
                    - completed
                    - errored
                    - pending
                    - running
              required:
                - name
                - status
    x-speakeasy-polling:
      - name: WaitForCompleted
        failureCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == "errored"
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == "completed"
    x-speakeasy-entity-operation:
      - Task#read
      - entityOperation: Task#create#2
        options:
          polling:
            name: WaitForCompleted
```

The API operation is used for both the read operation and the second create operation, where the second create operation will use the `WaitForCompleted` polling method to ensure the success criteria is met before the resource logic (and therefore Terraform) continues.

There are `delaySeconds`, `intervalSeconds`, and `limitCount` configurations to override the `x-speakeasy-polling` configuration values for a specific entity operation.

In this example:

```yaml
/task/{id}:
  get:
    x-speakeasy-polling:
      - name: WaitForCompleted
        failureCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == "errored"
        intervalSeconds: 5
        successCriteria:
          - condition: $statusCode == 200
          - condition: $response.body#/status == "completed"
    x-speakeasy-entity-operation:
      - Task#read
      - entityOperation: Task#create#2
        options:
          polling:
            name: WaitForCompleted
            intervalSeconds: 10
```

The `WaitForCompleted` polling method for the API operation defaults to a 5 second interval, however the create entity operation overrides to a 10 second interval.

### Update Patch Semantics

Define automatic patch semantics for Terraform provider update operations via the `x-speakeasy-entity-operation` extension in the OpenAPI Specification document. When enabled, entity operations will only send attributes that have changed from their prior state, rather than sending the entire resource representation.

This is particularly useful for APIs that support partial updates (such as PATCH semantics) where sending unchanged fields may cause unintended side effects or where bandwidth efficiency is important.

#### Basic Usage

In this example:

```yaml
/resource:
  post:
    x-speakeasy-entity-operation:
      - entityOperation: Resource#create,update
        options:
          patch:
            style: only-send-changed-attributes
    operationId: create-or-update-resource
    requestBody:
      required: true
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ResourceRequest'
    responses:
      '200':
        description: OK
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResourceResponse'
```

The `create,update` entity operation uses the `only-send-changed-attributes` patch style. During entity operations, the generated Terraform provider will compare each attribute against its prior state and only include attributes that have changed in the API request.

#### Configuration

The patch style is configured within the `options` section of an entity operation mapping:

```yaml
x-speakeasy-entity-operation:
  - entityOperation: Entity#operation
    options:
      patch:
        style: only-send-changed-attributes
```

#### When to Use

Use `only-send-changed-attributes` when:

- Your API supports partial updates (PATCH semantics) and may have side effects when unchanged fields are sent
- You want to minimize request payload size by excluding unchanged attributes
- Your API has fields where re-sending the same value triggers unwanted behavior (such as regenerating tokens or timestamps)


### Manual association between Operations and Resource / Data Sources

The default behavior within Speakeasy is to automatically infer a data source from all operations that have an `x-speakeasy-entity-operation: Entity#read` association defined.

For some APIs, you might want the data source to use a "search" endpoint (e.g., search for an entity by name, where name is non-unique), while using a "get" operation for the resource (e.g., to find an entity by ID for state reconciliation).

In this case, use an object syntax for the `x-speakeasy-entity-operation` annotation to explicitly control whether an operation generates a resource, a data source, or both:

```yaml
paths:
  "/example":
    get:
      operationId: getThing
      x-speakeasy-entity-operation:
        terraform-datasource: null
        terraform-resource: Thing#read
```

This syntax allows you to:

- Prevent automatic generation of a data source by setting `terraform-datasource` to `null`
- Prevent invocation of the operation during the resource's Read method ("invoked as part of terraform state refresh") by setting `terraform-resource` to `null`

For example, the configuration above declares that `getThing` is associated with just a resource, and a data source should not be automatically generated.

### Wrapping Additional API Operation Response Data

When defining multiple API operations for a single entity, an API definition may be written such that those API operation response are a flattened object. When adding those additional operations to the entity, those flattened object properties are added to the top level of the resource schema by default.

Use `x-speakeasy-wrapped-attribute` extension to override this behavior, which will create a wrapping attribute that contains the underlying object properties in the final resource schema.

In this example, the resource will put the second API operation response properties underneath a `subconfig` attribute:

```yaml
paths:
  /example/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      x-speakeasy-entity-operation: Example#read
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ExampleResponse"
  /example/{id}/subconfig:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    get:
      x-speakeasy-entity-operation: Example#read#2
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/ExampleSubconfigResponse"
                  - x-speakeasy-wrapped-attribute: subconfig
```

### Array Response Wrapping

Terraform resources require an object-type root schema. When an API returns an array, Speakeasy automatically wraps it in an attribute (default name: `data`).

**Default behavior:**

```yaml
paths:
  /things:
    get:
      x-speakeasy-entity-operation: Things#read
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  x-speakeasy-entity: Things
```

Since the response is `type: array`, Speakeasy wraps it in a `data` attribute for Terraform compatibility.

Access in Terraform:

```hcl
data.example_things.data[0].id
```

**Customize the wrapper name:**

```yaml
paths:
  /things:
    get:
      x-speakeasy-entity-operation: Things#read
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                x-speakeasy-wrapped-attribute: items
                type: array
                items:
                  x-speakeasy-entity: Things
```

Now the wrapping attribute is named `items` instead:

```hcl
data.example_things.items[0].id
```

<Callout title="Note" type="info">
  The wrapping attribute name is a Terraform construct created by Speakeasy,
  completely separate from your API's response structure. Use
  `x-speakeasy-wrapped-attribute` to customize it.
</Callout>

### Resources with Soft Delete

By default, a generated managed resource uses the HTTP 404 Not Found status code on read to automatically remove the resource from the Terraform state which causes the next Terraform plan to propose recreating the resource. For resource APIs that support soft delete (grace time period before the resource is fully deleted), the `x-speakeasy-soft-delete-property` annotation adds a check against a read response property to also propose resource recreation.

For managed resources, any `x-speakeasy-soft-delete-property` attribute is omitted from the schema and state. For data resources, the attribute remains to preserve client-side filtering capabilities.

In this example, the resource will be proposed for recreation if the `deleted_at` property has a value:

```yaml
paths:
  "/example":
    get:
      x-speakeasy-entity-operation: Example#read
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schema/ExampleGetResponse"
components:
  schemas:
    ExampleGetResponse:
      type: object
      properties:
        # ...
        deleted_at:
          type: string
          format: date-time
          x-speakeasy-soft-delete-property: true
```