# Create attachment

POST /attachments. Parameters, permissions, request and response schemas.

`POST /attachments`

Create attachment. Requires the REST `write` token ability. The bearer token selects the Team; a supplied team\_id cannot switch tenants. Additional role, entity-policy and plan requirements vary by operation. A Team in blocked billing mode cannot write and receives 402.

## Authentication

Use a team API key as `Authorization: Bearer YOUR_API_KEY`. The token ability and the user’s role must both allow this operation. See [authentication](https://conversionlab.app/docs/developer/authentication).

```json
{
  "ability": "write",
  "team": "Token-bound Team",
  "authorization": "Additional role, entity-policy and plan requirements vary by operation.",
  "billing": "Blocked billing mode rejects writes with 402."
}
```

## Request body

A request body is required.

### multipart/form-data

```json
{
  "type": "object",
  "properties": {
    "project_id": {
      "type": "integer"
    },
    "name": {
      "type": [
        "string",
        "null"
      ],
      "maxLength": 255
    },
    "type": {
      "type": "string",
      "enum": [
        "image",
        "document",
        "link"
      ],
      "title": "AttachmentType"
    },
    "file": {
      "type": "string",
      "format": "binary",
      "contentMediaType": "application/octet-stream",
      "description": "Maximum file size: 10240 kilobytes."
    },
    "url": {
      "type": "string",
      "format": "uri",
      "maxLength": 2048
    },
    "attachable_type": {
      "type": "string",
      "enum": [
        "experiment",
        "idea",
        "insight",
        "variation"
      ]
    },
    "attachable_id": {
      "type": "integer"
    }
  },
  "required": [
    "project_id",
    "type",
    "attachable_type",
    "attachable_id"
  ],
  "title": "StoreAttachmentRequest",
  "description": "For image/document attachments, file is required (up to 10 MB; JPEG, PNG, GIF, PDF, DOC/DOCX, XLS/XLSX). For link attachments, url is required. The linked record must exist in this Team.",
  "allOf": [
    {
      "if": {
        "properties": {
          "type": {
            "enum": [
              "image",
              "document"
            ]
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "required": [
          "file"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "link"
          }
        },
        "required": [
          "type"
        ]
      },
      "then": {
        "required": [
          "url"
        ]
      }
    }
  ],
  "example": {
    "project_id": 101,
    "type": "link",
    "url": "https://example.com/research",
    "attachable_type": "idea",
    "attachable_id": 101
  }
}
```

```json
{
  "project_id": 101,
  "type": "link",
  "url": "https://example.com/research",
  "attachable_type": "idea",
  "attachable_id": 101
}
```

## Example request

For file fields, replace the synthetic local filename with an existing file of the documented type and size. Let curl set the multipart boundary.

```bash
curl --request POST 'https://api.example.test/api/attachments' \
  --header 'Authorization: Bearer YOUR_API_KEY' \
  --header 'Accept: application/json' \
  --form-string 'project_id=101' \
  --form-string 'type=link' \
  --form-string 'attachable_type=idea' \
  --form-string 'attachable_id=101' \
  --form-string 'url=https://example.com/research'
```

Examples use fictional data. Replace resource IDs and the API host with your environment’s values.

## Responses

### 200

`AttachmentResource`

Content type: `application/json`.

```json
{
  "type": "object",
  "properties": {
    "data": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer"
        },
        "project_id": {
          "type": "integer"
        },
        "name": {
          "type": [
            "string",
            "null"
          ]
        },
        "file_type": {
          "type": [
            "string",
            "null"
          ]
        },
        "file_size": {
          "type": [
            "integer",
            "null"
          ]
        },
        "type": {
          "type": "string",
          "enum": [
            "image",
            "document",
            "link"
          ],
          "title": "AttachmentType"
        },
        "url": {
          "type": [
            "string",
            "null"
          ]
        },
        "attachable_type": {
          "type": "string"
        },
        "attachable_id": {
          "type": "integer"
        },
        "created_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "updated_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        }
      },
      "required": [
        "id",
        "project_id",
        "name",
        "file_type",
        "file_size",
        "type",
        "url",
        "attachable_type",
        "attachable_id",
        "created_at",
        "updated_at"
      ],
      "title": "AttachmentResource"
    }
  },
  "required": [
    "data"
  ]
}
```

```json
{
  "data": {
    "id": 101,
    "project_id": 101,
    "name": null,
    "file_type": null,
    "file_size": null,
    "type": "image",
    "url": null,
    "attachable_type": "example",
    "attachable_id": 101,
    "created_at": null,
    "updated_at": null
  }
}
```

### 401

Unauthenticated

Content type: `application/json`.

```json
{
  "type": "object",
  "properties": {
    "message": {
      "type": "string",
      "description": "Error overview."
    }
  },
  "required": [
    "message"
  ]
}
```

```json
{
  "message": "The request could not be completed."
}
```

### 402

The Team is in read-only billing mode.

Content type: `application/json`.

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "string",
      "const": "subscription_required"
    },
    "mode": {
      "type": "string",
      "const": "blocked"
    },
    "message": {
      "type": "string",
      "example": "Your team is in read-only mode. Update billing to make changes."
    },
    "grace_period_ends_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    }
  },
  "required": [
    "error",
    "mode",
    "message",
    "grace_period_ends_at"
  ]
}
```

```json
{
  "error": "subscription_required",
  "mode": "blocked",
  "message": "Your team is in read-only mode. Update billing to make changes.",
  "grace_period_ends_at": null
}
```

### 403

Token ability, user permission, Team membership, plan, or billing mode does not allow this operation.

Content type: `application/json`.

```json
{
  "type": "object",
  "properties": {
    "message": {
      "type": "string",
      "example": "This action is unauthorized."
    }
  },
  "required": [
    "message"
  ]
}
```

```json
{
  "message": "This action is unauthorized."
}
```

### 422

Validation error

Content type: `application/json`.

```json
{
  "type": "object",
  "properties": {
    "message": {
      "type": "string",
      "description": "Errors overview."
    },
    "errors": {
      "type": "object",
      "description": "A detailed description of each field that failed validation.",
      "additionalProperties": {
        "type": "array",
        "items": {
          "type": "string"
        }
      }
    }
  },
  "required": [
    "message",
    "errors"
  ]
}
```

```json
{
  "message": "The request could not be completed.",
  "errors": {
    "name": [
      "The name field is required."
    ]
  }
}
```

### 429

Rate limit exceeded. Honor Retry-After before retrying.

Response headers:

```json
{
  "Retry-After": {
    "description": "Seconds before retrying.",
    "schema": {
      "type": "integer"
    }
  }
}
```

Content type: `application/json`.

```json
{
  "type": "object",
  "properties": {
    "message": {
      "type": "string",
      "example": "Too Many Attempts."
    }
  },
  "required": [
    "message"
  ]
}
```

```json
{
  "message": "Too Many Attempts."
}
```

See [API conventions](https://conversionlab.app/docs/developer/api-conventions) for error handling, rate limits, pagination, and uploads.
