Open the ClassDefinition: Set ClassDef=##class(%Dictionalry.ClassDefinition).%OpenId(ClassName)
Add the Parameter

Set Parm=##class(%Dictionary.ParameterDefinition).%New(ClassName_":"_ParameterName)
Set Parm.Default=ParameterValue
Do ClassDef.Parameters.Insert(Parm)

Find the desired method in ClassDef.Methods collection by MethDef.Name
Use the MethDef.Implementation stream to manipulate the code
Compile the class.

I don't know if this is the case, but be sure not to output anything before setting Content-Type or HTTP status.
In my case it was a debug Write statement that wrote directly to the current TCP stream.
Because of IO redirect when something is written to the stream (the HTTP body) first the headers must be written and those are at that time the default values.

But it's not about the front-end, you can use any you like.
The power of CSP is in the back-end: hybrid server-side rendering, seamless integration with Classes, #server/#call.
If you are developing an InterSystems-only web application, not using CSP would be foolish.
You can still use Bootstrap, Tailwind, etc for the UI sugar, but for data integration and routing there's no match.

ClassMethod CreateApplication() As %Status [ CodeMode = objectgenerator ]
Set $ZTrap="Error"
Set sc=$$$OK
Write !,"Creating Mitz API",!
Set Here=$Namespace
Set URL="/mitz"
Set $Namespace="%SYS"
If ##class(Security.Applications).Exists(URL) {
Write "Mitz API already exists",!
Else {
Set Props("Name")=URL
Set Props("NameSpace")=Here
Set Props("Description")="Mitz notificatie API endpoint"
Set Props("AutheEnabled")=64
Set Props("DispatchClass")="LSP.Mitz.API"
Set Props("MatchRoles")=":%All"
Set Props("Type")=2 ; CSP App
Set sc=##class(Security.Applications).Create(URL,.Props)
Set $Namespace=Here
Return sc
Set $ZTrap=""
If $Data(Here) Set $Namespace=Here
Return $$$ERROR($$$GeneralError,"ErrorTrap: "_$ZError)

About RESTfull...

/search isn't really RESTfull, REST works on Resources (e.g. /persons or /orders)

To query Resources you use GET (with parameters)

But this is all very 'religious'. To use POST for a complex query is IMO defendable, but then I would do that on a Resource.

You should have a look at the OData (Oasis) specification:

What version of Cache are you running ?

We had a similar issue with %Open, it was a bug:

there was a leak in $ZE variable, which was fixed by change DLP3616 - Object Storage - 
complete object initialization when %LoadData fails. The change now reset $ZE="" on start 
of %LoadData method.
2016.2.1 (Build 803_0_16949U)

Maybe it is related

Do ##class(%Dictionary.ClassDefinition).%DeleteId("Test.Class")
Set ClassDef=##class(%Dictionary.ClassDefinition).%New()
Set ClassDef.Name="Test.Class"
Set ClassDef.Super="%Persistent"
Set ClassDef.ProcedureBlock=1
Set MethDef=##class(%Dictionary.MethodDefinition).%New()
Set MethDef.Name="Name"
Set MethDef.ReturnType="%String"
; Uncomment for ClassMethod
; Set MethDef.ClassMethod=1
Set MethDef.FormalSpec="First:%Boolean,Second:%String=""Default"""
Do MethDef.Implementation.WriteLine($Char(9)_"If First Return 1")
Do MethDef.Implementation.WriteLine($Char(9)_"Return Second")
Set MethDef.parent=ClassDef
Do ClassDef.Methods.Insert(MethDef)
Do ClassDef.%Save()

You can use XSLT to do this:

ClassMethod Run()
  Set XML=##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||XML").Data
  Set XSLT=##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||XSLT").Data
  Set sc = ##class(%XML.XSLT.Transformer).TransformStream(XML, XSLT, .Result,, .Params,)
  Do Result.OutputToDevice()

XData XML{
<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV=''>
<SomeCustomHeader/> </SOAP-ENV:Header>

XData XSLT {
<xsl:stylesheet version="1.0" xmlns:xsl="">
<xsl:output method="xml" omit-xml-declaration="yes"/>
  <xsl:strip-space elements="*"/>
  <xsl:template match="@*|node()">
    <xsl:apply-templates select="@*|node()"/>