{
  "openapi": "3.1.0",
  "info": {
    "title": "Envoke Custom Field API",
    "version": "2.0.0",
    "description": "Full CRUD API for managing contact-scoped custom fields. All endpoints require HTTP Basic Auth and return JSON."
  },
  "servers": [
    {
      "url": "https://e1.envoke.com/v2",
      "description": "Production"
    }
  ],
  "security": [
    {
      "basicAuth": []
    }
  ],
  "tags": [
    {
      "name": "Custom Fields",
      "description": "Operations on contact-scoped custom fields."
    }
  ],
  "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": {
      "CustomField": {
        "type": "object",
        "description": "A contact-scoped custom field definition.",
        "required": [
          "id",
          "name",
          "description",
          "field_type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true,
            "description": "Unique identifier assigned on creation. Returned as a string.",
            "examples": [
              "42"
            ]
          },
          "name": {
            "type": "string",
            "readOnly": true,
            "description": "The field identifier. Contains only alphanumeric characters and underscores. Unique per account.",
            "examples": [
              "phone_number"
            ]
          },
          "description": {
            "type": "string",
            "description": "Human-readable description of the field. Returns an empty string when not set.",
            "examples": [
              "Customer's mobile phone number",
              ""
            ]
          },
          "field_type": {
            "type": "string",
            "readOnly": true,
            "enum": [
              "text",
              "note",
              "date",
              "url"
            ],
            "description": "The data type of values stored in this field. Read-only after creation.",
            "examples": [
              "text"
            ]
          }
        }
      },
      "CustomFieldCreate": {
        "type": "object",
        "description": "Request body for creating a new custom field.",
        "required": [
          "name",
          "field_type"
        ],
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9_]+$",
            "description": "The field identifier. Alphanumeric characters and underscores only. Must be unique per account.",
            "examples": [
              "phone_number"
            ]
          },
          "field_type": {
            "type": "string",
            "enum": [
              "text",
              "note",
              "date",
              "url"
            ],
            "description": "The data type for values stored in this field. Cannot be changed after creation.",
            "examples": [
              "text"
            ]
          },
          "description": {
            "type": "string",
            "description": "Optional human-readable description of the field.",
            "examples": [
              "Customer's mobile phone number"
            ]
          }
        }
      },
      "CustomFieldUpdate": {
        "type": "object",
        "description": "Request body for updating an existing custom field. At least one property must be provided. `field_type` cannot be changed.",
        "minProperties": 1,
        "properties": {
          "name": {
            "type": "string",
            "pattern": "^[a-zA-Z0-9_]+$",
            "description": "New name for the field. Alphanumeric characters and underscores only. Must be unique per account.",
            "examples": [
              "mobile_number"
            ]
          },
          "description": {
            "type": "string",
            "description": "Updated description for the field.",
            "examples": [
              "Customer's mobile phone number"
            ]
          }
        }
      },
      "CreateSuccessResponse": {
        "type": "object",
        "description": "Returned on successful creation.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Created"
          },
          "result_data": {
            "$ref": "#/components/schemas/CustomField"
          }
        }
      },
      "UpdateSuccessResponse": {
        "type": "object",
        "description": "Returned on successful update.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Updated"
          },
          "result_data": {
            "$ref": "#/components/schemas/CustomField"
          }
        }
      },
      "DeleteSuccessResponse": {
        "type": "object",
        "description": "Returned on successful deletion.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Deleted"
          },
          "result_data": {
            "type": "array",
            "maxItems": 0,
            "description": "Always an empty array on deletion."
          }
        }
      },
      "ValidationErrorResponse": {
        "type": "object",
        "description": "Returned when request body 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."
              }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "Returned for general errors (e.g. not found, bad request).",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string",
            "description": "Human-readable summary of the error.",
            "examples": [
              "Item with id '42' not found",
              "An 'id' must be provided in order to update"
            ]
          },
          "result_data": {
            "type": "array",
            "maxItems": 0
          }
        }
      }
    },
    "parameters": {
      "CustomFieldId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The unique ID of the custom field.",
        "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` (shaped responses), 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 omitted, all fields are returned. When set, the default limit increases to 10,000 and the 100-record hard cap is lifted.",
        "schema": {
          "type": "string"
        },
        "example": "id,name,field_type"
      },
      "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. Useful for populating dropdowns. When set, the default limit increases to 10,000 and the 100-record hard cap is lifted.",
        "schema": {
          "type": "string",
          "enum": [
            "kvp"
          ]
        }
      }
    },
    "responses": {
      "ValidationError": {
        "description": "One or more request body fields failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorResponse"
            },
            "example": {
              "success": false,
              "result_text": "Validation failed, see 'result_data' -> 'errors'",
              "result_data": {
                "errors": [
                  "The field 'name' is required",
                  "field_type is required"
                ]
              }
            }
          }
        }
      },
      "NotFoundGet": {
        "description": "The requested custom field ID was not found. GET by ID returns an empty array rather than an error envelope.",
        "content": {
          "application/json": {
            "schema": {
              "type": "array",
              "maxItems": 0
            },
            "example": []
          }
        }
      },
      "NotFoundMutate": {
        "description": "The requested custom field ID was not found. PATCH and DELETE return 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 '42' not found"
                ]
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/customfields": {
      "get": {
        "summary": "List custom fields",
        "description": "Returns a paginated array of contact-scoped custom fields for the authenticated account. Use `?limit` and `?skip` for pagination. Use `?result_type=kvp` for a flat id-to-name map.",
        "operationId": "listCustomFields",
        "tags": [
          "Custom Fields"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Skip"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/ResultType"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response. Returns an array of custom fields, or a key-value object when `result_type=kvp` is used.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CustomField"
                      }
                    },
                    {
                      "type": "object",
                      "description": "Key-value map of field ID to field name. Returned when `result_type=kvp` is used.",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  ]
                },
                "examples": {
                  "array": {
                    "summary": "Default array response",
                    "value": [
                      {
                        "id": "1",
                        "name": "phone_number",
                        "description": "Customer's mobile number",
                        "field_type": "text"
                      },
                      {
                        "id": "2",
                        "name": "birthday",
                        "description": "",
                        "field_type": "date"
                      }
                    ]
                  },
                  "kvp": {
                    "summary": "Key-value pair response (result_type=kvp)",
                    "value": {
                      "1": "phone_number",
                      "2": "birthday"
                    }
                  },
                  "empty": {
                    "summary": "No custom fields defined",
                    "value": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a custom field",
        "description": "Creates a new contact-scoped custom field. `name` and `field_type` are required. The `field_type` cannot be changed after creation.",
        "operationId": "createCustomField",
        "tags": [
          "Custom Fields"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomFieldCreate"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal (required fields only)",
                  "value": {
                    "name": "phone_number",
                    "field_type": "text"
                  }
                },
                "full": {
                  "summary": "With optional description",
                  "value": {
                    "name": "phone_number",
                    "field_type": "text",
                    "description": "Customer's mobile phone number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Custom field created successfully. Returns the new field object.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Created",
                  "result_data": {
                    "id": "42",
                    "name": "phone_number",
                    "description": "Customer's mobile phone number",
                    "field_type": "text"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                },
                "examples": {
                  "missing_fields": {
                    "summary": "Required fields missing",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'name' is required",
                          "field_type is required"
                        ]
                      }
                    }
                  },
                  "invalid_name": {
                    "summary": "Name contains invalid characters",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Only alphanumeric and underscore characters are permitted in the name field"
                        ]
                      }
                    }
                  },
                  "invalid_field_type": {
                    "summary": "Invalid field_type value",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'field_type'; allowed values are: text, note, date, url"
                        ]
                      }
                    }
                  },
                  "duplicate_name": {
                    "summary": "Name already exists",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "A custom field named 'phone_number' already exists"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/customfields/{id}": {
      "get": {
        "summary": "Get a single custom field",
        "description": "Returns a single custom field by its ID.",
        "operationId": "getCustomField",
        "tags": [
          "Custom Fields"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CustomFieldId"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The requested custom field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomField"
                },
                "example": {
                  "id": "42",
                  "name": "phone_number",
                  "description": "Customer's mobile phone number",
                  "field_type": "text"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/NotFoundGet"
          }
        }
      },
      "patch": {
        "summary": "Update a custom field",
        "description": "Updates the `name` and/or `description` of an existing custom field. `field_type` cannot be changed after creation.",
        "operationId": "updateCustomField",
        "tags": [
          "Custom Fields"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CustomFieldId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomFieldUpdate"
              },
              "examples": {
                "update_description": {
                  "summary": "Update description only",
                  "value": {
                    "description": "Updated description for the field"
                  }
                },
                "update_both": {
                  "summary": "Update name and description",
                  "value": {
                    "name": "mobile_number",
                    "description": "Customer's mobile number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Custom field updated successfully. Returns the updated field object.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Updated",
                  "result_data": {
                    "id": "42",
                    "name": "phone_number",
                    "description": "Updated description for the field",
                    "field_type": "text"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error or field not found. All errors use the validation envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                },
                "examples": {
                  "field_type_immutable": {
                    "summary": "Attempted to change field_type",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'field_type' cannot be changed after creation"
                        ]
                      }
                    }
                  },
                  "not_found": {
                    "summary": "Custom field not found",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Item with id '42' not found"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a custom field",
        "description": "Permanently deletes a custom field and **all associated contact values**. This action cannot be undone.",
        "operationId": "deleteCustomField",
        "tags": [
          "Custom Fields"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CustomFieldId"
          }
        ],
        "responses": {
          "200": {
            "description": "Custom field deleted successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Deleted",
                  "result_data": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/NotFoundMutate"
          }
        }
      }
    }
  }
}
