{
  "openapi": "3.1.0",
  "info": {
    "title": "Envoke Template API",
    "version": "2.0.0",
    "description": "Full CRUD API for managing email templates. All endpoints require HTTP Basic Auth and return JSON. Templates are soft-deleted and all timestamps are returned in the account's configured timezone."
  },
  "servers": [
    {
      "url": "https://e1.envoke.com/v2",
      "description": "Production"
    }
  ],
  "security": [
    {
      "basicAuth": []
    }
  ],
  "tags": [
    {
      "name": "Templates",
      "description": "Operations on email templates."
    }
  ],
  "components": {
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic Auth. Use your API ID as the username and API Key as the password."
      }
    },
    "schemas": {
      "Template": {
        "type": "object",
        "description": "An email template definition.",
        "required": [
          "id",
          "external_id",
          "preview_url",
          "name",
          "status",
          "language",
          "group",
          "create_time",
          "modified_time"
        ],
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true,
            "description": "Unique identifier assigned on creation. Returned as a string.",
            "examples": [
              "42"
            ]
          },
          "external_id": {
            "type": "string",
            "readOnly": true,
            "description": "Randomly assigned external identifier used to construct the preview URL.",
            "examples": [
              "a1b2c3d4e5f6g7h8"
            ]
          },
          "preview_url": {
            "type": "string",
            "readOnly": true,
            "description": "Public URL to preview the template. Built on the account's custom domain when it has one, otherwise Envoke's message domain (not the API host), from the account's external ID and the template's external_id.",
            "examples": [
              "https://engage.envoke.com/m/acme-corp/m/a1b2c3d4e5f6g7h8"
            ]
          },
          "name": {
            "type": "string",
            "minLength": 3,
            "description": "The template name. Minimum 3 characters.",
            "examples": [
              "Welcome Email"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "published",
              "unpublished"
            ],
            "description": "Publication status of the template. New templates are always created as `published`.",
            "examples": [
              "published"
            ]
          },
          "language": {
            "type": "string",
            "description": "IETF language code for the template (e.g. `en`, `fr`). Defaults to `en`.",
            "examples": [
              "en"
            ]
          },
          "group": {
            "type": "string",
            "description": "Name of the group the template belongs to. Empty string (`\"\"`) when no group is assigned.",
            "examples": [
              "Marketing Templates",
              ""
            ]
          },
          "create_time": {
            "type": "string",
            "readOnly": true,
            "description": "Timestamp when the template was created, in the account's configured timezone. Format: `Y-m-d H:i:s`.",
            "examples": [
              "2026-04-11 09:00:00"
            ]
          },
          "modified_time": {
            "type": "string",
            "readOnly": true,
            "description": "Timestamp when the template was last modified, in the account's configured timezone. Format: `Y-m-d H:i:s`.",
            "examples": [
              "2026-04-11 14:22:10"
            ]
          }
        }
      },
      "TemplateWithHTML": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Template"
          },
          {
            "type": "object",
            "required": [
              "html"
            ],
            "properties": {
              "html": {
                "type": "string",
                "description": "Full HTML content of the template. Only present when `?include_html=1` is passed on a single-template GET request.",
                "examples": [
                  "<html><body><h1>Hello!</h1></body></html>"
                ]
              }
            }
          }
        ]
      },
      "TemplateCreate": {
        "type": "object",
        "description": "Request body for creating a new template. Either `html` or `template_id` must be provided. `name` is optional — if omitted, it is auto-generated as `API Template {timestamp}`.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 3,
            "description": "The template name. Minimum 3 characters. Auto-generated if omitted.",
            "examples": [
              "Welcome Email"
            ]
          },
          "html": {
            "type": "string",
            "description": "Full HTML content of the template. Maximum 200 KB. Required if `template_id` is not provided.",
            "examples": [
              "<html><body><h1>Welcome!</h1></body></html>"
            ]
          },
          "template_id": {
            "type": "integer",
            "description": "ID of an existing template or message to copy HTML from. Required if `html` is not provided. Must be owned by your account or accessible as a shared template.",
            "examples": [
              10
            ]
          },
          "language": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Language for the template. Accepts an IETF language code (e.g. `en`, `fr`) or a numeric language ID. Defaults to `en` if omitted.",
            "examples": [
              "en",
              1
            ]
          },
          "group": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Group to assign the template to. Accepts the group name (string) or group ID (integer). The group must be of type `message_template`.",
            "examples": [
              "Marketing Templates",
              5
            ]
          }
        }
      },
      "TemplateUpdate": {
        "type": "object",
        "description": "Request body for updating a template. At least one field must be provided. `external_id`, `preview_url`, `create_time`, and `modified_time` are silently ignored if included. `id` in the request body is validated against the URL id — if they differ, an error is returned.",
        "minProperties": 1,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 3,
            "description": "New name for the template. Minimum 3 characters.",
            "examples": [
              "Updated Welcome Email"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "published",
              "unpublished"
            ],
            "description": "New publication status.",
            "examples": [
              "unpublished"
            ]
          },
          "language": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Language for the template. Accepts an IETF language code or numeric language ID.",
            "examples": [
              "fr"
            ]
          },
          "group": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Group to assign the template to. Accepts group name or group ID. Pass an empty string (`\"\"`) to remove the group assignment.",
            "examples": [
              "Seasonal Campaigns",
              ""
            ]
          },
          "html": {
            "type": "string",
            "description": "Replacement HTML content for the template. Maximum 200 KB.",
            "examples": [
              "<html><body><h1>Updated content</h1></body></html>"
            ]
          }
        }
      },
      "CreateSuccessResponse": {
        "type": "object",
        "description": "Returned on successful template creation.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Created"
          },
          "result_data": {
            "$ref": "#/components/schemas/Template"
          }
        }
      },
      "UpdateSuccessResponse": {
        "type": "object",
        "description": "Returned on successful template update.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Updated"
          },
          "result_data": {
            "$ref": "#/components/schemas/Template"
          }
        }
      },
      "DeleteSuccessResponse": {
        "type": "object",
        "description": "Returned on successful deletion. Templates are soft-deleted.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Deleted"
          },
          "result_data": {
            "type": "array",
            "maxItems": 0
          }
        }
      },
      "ValidationErrorResponse": {
        "type": "object",
        "description": "Returned when request body or path parameter validation fails.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string",
            "const": "Validation failed, see 'result_data' -> 'errors'"
          },
          "result_data": {
            "type": "object",
            "required": [
              "errors"
            ],
            "properties": {
              "errors": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "One or more human-readable validation error messages."
              },
              "allowed_values": {
                "type": "object",
                "description": "Present on some errors. Maps a field name to its allowed values.",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "Returned for general request errors.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string"
          },
          "result_data": {
            "type": "array",
            "maxItems": 0
          }
        }
      }
    },
    "parameters": {
      "TemplateId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The unique ID of the template.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Maximum number of records to return. Default is 10, hard cap is 100. When used alongside `?fields=` or `?result_type=kvp`, the cap is lifted and an explicit limit above 100 is honoured as-is.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 10
        }
      },
      "Skip": {
        "name": "skip",
        "in": "query",
        "description": "Number of records to skip. Used for pagination.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      },
      "Fields": {
        "name": "fields",
        "in": "query",
        "description": "Comma-separated list of fields to include in each response object. When set, the default limit increases to 10,000 and the 100-record hard cap is lifted.",
        "schema": {
          "type": "string"
        },
        "example": "id,name,status"
      },
      "ResultType": {
        "name": "result_type",
        "in": "query",
        "description": "Set to `kvp` to receive results as a flat key-value object (`{ \"<id>\": \"<name>\" }`) instead of an array of objects. When set, the default limit increases to 10,000 and the hard cap is lifted.",
        "schema": {
          "type": "string",
          "enum": [
            "kvp"
          ]
        }
      },
      "IncludeHTML": {
        "name": "include_html",
        "in": "query",
        "description": "Set to `1` to include the full HTML content in a single-template response. Has no effect on list requests.",
        "schema": {
          "type": "integer",
          "enum": [
            0,
            1
          ],
          "default": 0
        }
      }
    },
    "responses": {
      "NotFoundGet": {
        "description": "The requested template ID was not found. Returns an empty array.",
        "content": {
          "application/json": {
            "schema": {
              "type": "array",
              "maxItems": 0
            },
            "example": []
          }
        }
      },
      "NotFoundMutate": {
        "description": "The requested template ID was not found. Returns a validation error envelope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorResponse"
            },
            "example": {
              "success": false,
              "result_text": "Validation failed, see 'result_data' -> 'errors'",
              "result_data": {
                "errors": [
                  "Template not found"
                ]
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/templates": {
      "get": {
        "summary": "List templates",
        "description": "Returns a paginated array of templates for the authenticated account, sorted by most recently modified first by default. Supports sorting via `sort[]` query parameters. Supports filtering by `id`, `name`, `status`, `language`, and `group`. Use `?result_type=kvp` for a flat id-to-name map.",
        "operationId": "listTemplates",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Skip"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/ResultType"
          },
          {
            "name": "filter[name]",
            "in": "query",
            "description": "Filter by template name. Supports operators: `(ne)`, `(in)`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter[status]",
            "in": "query",
            "description": "Filter by status. Valid values: `published`, `unpublished`.",
            "schema": {
              "type": "string",
              "enum": [
                "published",
                "unpublished"
              ]
            }
          },
          {
            "name": "filter[language]",
            "in": "query",
            "description": "Filter by language. Accepts a two-letter IETF code (e.g. `en`, `fr`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter[group]",
            "in": "query",
            "description": "Filter by group name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter[id]",
            "in": "query",
            "description": "Filter by template ID.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sort[name]",
            "in": "query",
            "description": "Sort by name. Value: `asc` or `desc`.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "sort[modified_time]",
            "in": "query",
            "description": "Sort by last modified time. Value: `asc` or `desc`.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "sort[create_time]",
            "in": "query",
            "description": "Sort by creation time. Value: `asc` or `desc`.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "sort[language]",
            "in": "query",
            "description": "Sort by language. Value: `asc` or `desc`.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "sort[group]",
            "in": "query",
            "description": "Sort by group name. Value: `asc` or `desc`.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "sort[status]",
            "in": "query",
            "description": "Sort by status. Value: `asc` or `desc`.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A list of templates.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Template"
                      }
                    },
                    {
                      "type": "object",
                      "description": "Key-value map of template ID to name.",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  ]
                },
                "examples": {
                  "array": {
                    "summary": "Default array response",
                    "value": [
                      {
                        "id": 1,
                        "external_id": "a1b2c3d4e5f6",
                        "preview_url": "https://engage.envoke.com/m/acme-corp/m/a1b2c3d4e5f6",
                        "name": "Welcome Email",
                        "status": "published",
                        "language": "en",
                        "group": "Onboarding",
                        "create_time": "2026-04-01 09:00:00",
                        "modified_time": "2026-04-10 18:00:00"
                      }
                    ]
                  },
                  "kvp": {
                    "summary": "Key-value pair response (result_type=kvp)",
                    "value": {
                      "1": "Welcome Email",
                      "2": "Monthly Newsletter"
                    }
                  },
                  "empty": {
                    "summary": "No templates",
                    "value": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a template",
        "description": "Creates a new email template. Either `html` or `template_id` must be provided. `name` is optional — if omitted, it is auto-generated as `API Template {timestamp}`. New templates are always created with `status: published` regardless of any `status` value in the request body.",
        "operationId": "createTemplate",
        "tags": [
          "Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateCreate"
              },
              "examples": {
                "with_html": {
                  "summary": "Create with inline HTML",
                  "value": {
                    "name": "Welcome Email",
                    "html": "<html><body><h1>Welcome!</h1></body></html>",
                    "language": "en",
                    "group": "Onboarding"
                  }
                },
                "from_template": {
                  "summary": "Copy from existing template",
                  "value": {
                    "name": "Welcome Email Copy",
                    "template_id": 10
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template created successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Created",
                  "result_data": {
                    "id": 42,
                    "external_id": "a1b2c3d4e5f6",
                    "preview_url": "https://engage.envoke.com/m/acme-corp/m/a1b2c3d4e5f6",
                    "name": "Welcome Email",
                    "status": "published",
                    "language": "en",
                    "group": "Onboarding",
                    "create_time": "2026-04-11 09:00:00",
                    "modified_time": "2026-04-11 09:00:00"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                },
                "examples": {
                  "missing_content": {
                    "summary": "Neither html nor template_id provided",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "One of the fields 'template_id' or 'html' must be provided in order to create a template"
                        ]
                      }
                    }
                  },
                  "name_too_short": {
                    "summary": "Name fewer than 3 characters",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'name' must be at least 3 characters long"
                        ]
                      }
                    }
                  },
                  "invalid_language": {
                    "summary": "Unrecognised language code",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'language'"
                        ],
                        "allowed_values": {
                          "language": {
                            "1": "English",
                            "2": "French"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/templates/{id}": {
      "get": {
        "summary": "Get a single template",
        "description": "Returns a single template by its ID. Add `?include_html=1` to include the full HTML content in the response.",
        "operationId": "getTemplate",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateId"
          },
          {
            "$ref": "#/components/parameters/IncludeHTML"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested template. Includes `html` field only when `?include_html=1` is set.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Template"
                    },
                    {
                      "$ref": "#/components/schemas/TemplateWithHTML"
                    }
                  ]
                },
                "examples": {
                  "default": {
                    "summary": "Standard response",
                    "value": {
                      "id": 42,
                      "external_id": "a1b2c3d4e5f6",
                      "preview_url": "https://engage.envoke.com/m/acme-corp/m/a1b2c3d4e5f6",
                      "name": "Welcome Email",
                      "status": "published",
                      "language": "en",
                      "group": "",
                      "create_time": "2026-04-11 09:00:00",
                      "modified_time": "2026-04-11 09:00:00"
                    }
                  },
                  "with_html": {
                    "summary": "With ?include_html=1",
                    "value": {
                      "id": 42,
                      "external_id": "a1b2c3d4e5f6",
                      "preview_url": "https://engage.envoke.com/m/acme-corp/m/a1b2c3d4e5f6",
                      "name": "Welcome Email",
                      "status": "published",
                      "language": "en",
                      "group": "",
                      "create_time": "2026-04-11 09:00:00",
                      "modified_time": "2026-04-11 09:00:00",
                      "html": "<html><body><h1>Welcome!</h1></body></html>"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/NotFoundGet"
          }
        }
      },
      "patch": {
        "summary": "Update a template",
        "description": "Updates one or more fields on an existing template. `name`, `status`, `language`, `group`, and `html` are updatable. `external_id`, `preview_url`, `create_time`, and `modified_time` are silently ignored if included. If `id` is included in the request body, it must match the `{id}` in the URL — if they differ, a validation error is returned.",
        "operationId": "updateTemplate",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateUpdate"
              },
              "examples": {
                "update_name": {
                  "summary": "Rename the template",
                  "value": {
                    "name": "Updated Welcome Email"
                  }
                },
                "unpublish": {
                  "summary": "Unpublish a template",
                  "value": {
                    "status": "unpublished"
                  }
                },
                "update_html": {
                  "summary": "Replace HTML content",
                  "value": {
                    "html": "<html><body><h1>New content</h1></body></html>"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template updated successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Updated",
                  "result_data": {
                    "id": 42,
                    "external_id": "a1b2c3d4e5f6",
                    "preview_url": "https://engage.envoke.com/m/acme-corp/m/a1b2c3d4e5f6",
                    "name": "Updated Welcome Email",
                    "status": "published",
                    "language": "en",
                    "group": "",
                    "create_time": "2026-04-11 09:00:00",
                    "modified_time": "2026-04-11 15:00:00"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error or template not found. All errors use the validation envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                },
                "examples": {
                  "name_too_short": {
                    "summary": "Name too short",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'name' must be at least 3 characters long"
                        ]
                      }
                    }
                  },
                  "invalid_status": {
                    "summary": "Invalid status value",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'status'"
                        ],
                        "allowed_values": {
                          "status": [
                            "published",
                            "unpublished"
                          ]
                        }
                      }
                    }
                  },
                  "not_found": {
                    "summary": "Template not found",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Item with id '999999' not found"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a template",
        "description": "Soft-deletes a template. The template is archived and no longer returned by the API, but the underlying data is not immediately purged.",
        "operationId": "deleteTemplate",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/TemplateId"
          }
        ],
        "responses": {
          "200": {
            "description": "Template deleted successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Deleted",
                  "result_data": []
                }
              }
            }
          },
          "400": {
            "description": "The template ID was not found. Returns a validation error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                },
                "example": {
                  "success": false,
                  "result_text": "Validation failed, see 'result_data' -> 'errors'",
                  "result_data": {
                    "errors": [
                      "Item with id '999999' not found"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
