I am experiencing a critical platform-level issue that is blocking all app development for my account
Every client.request.invoke call from any custom app fails with the error: {message: 'Invalid feature or feature not enabled.'}.
We have definitively proven that this is a platform issue and not a code or configuration error.
- Direct API Calls Work: I can successfully make API calls to my Freshdesk instance using 
cURLandPostmanwith my Admin API key and standardBasicauthentication. My key is valid and the API is responsive. - App Fails with an Identical Request: We built a minimal test app that does only one thing: makes the exact same 
add noteAPI call as my workingcURLcommand, using the exact sameBasicauthentication method. This app still fails with the “Invalid feature” error. - This happens on all app types: We have tried 
platform-version: 3.0apps,platform-version: 2.3apps, plain JavaScript apps, and React apps. We have triedBasicandBearerauthentication methods. Every single attempt to useclient.request.invokeresults in the same error. 
Conclusion: The Freshworks app platform is blocking all server-side requests originating from my custom apps. The “feature” that is “not enabled” appears to be the Request Method (proxy) service itself for my account.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ticket Action</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/css/crayons.css">
    <script async src="{{{appclient}}}"></script>
    <script type="module" src="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/dist/crayons/crayons.esm.js"></script>
    <script nomodule src="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/dist/crayons/crayons.js"></script>
    <style>
        body {
            font-family: 'Inter', sans-serif;
            margin: 0;
        }
    </style>
</head>
<body>
    <div class="fw-flex fw-flex-column fw-p-16">
        <fw-select
            id="status-select"
            label="Ticket Status"
            value="0"
            placeholder="Your choice"
            disabled
        >
            <fw-select-option value="0" disabled>Please select a status</fw-select-option>
            <fw-select-option value="1">Pending Input</fw-select-option>
            <fw-select-option value="2">Resolved</fw-select-option>
        </fw-select>
        <fw-button color="primary" id="action-btn" disabled>Action Handled</fw-button>
    </div>
    <script src="https://static.freshdev.io/fdk/2.0/assets/fresh_client.js"></script>
    <script src="scripts/tekno.js"></script>
</body>
</html>
[Read me.pdf|attachment](upload://tA1QajFdMset54zO7mMxkDJCjZU.pdf) (83.4 KB)
{
  "getTicketDetails": {
    "schema": {
      "protocol": "https",
      "method": "GET",
      "host": "<%= iparam.freshdesk_domain %>",
      "path": "/api/v2/tickets/<%= context.ticketId %>",
      "headers": {
        "Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
        "Content-Type": "application/json"
      }
    }
  },
  "updateTicket": {
    "schema": {
      "protocol": "https",
      "method": "PUT",
      "host": "<%= iparam.freshdesk_domain %>",
      "path": "/api/v2/tickets/<%= context.ticketId %>",
      "headers": {
        "Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
        "Content-Type": "application/json"
      }
    }
  },
  "getConversations": {
    "schema": {
      "protocol": "https",
      "method": "GET",
      "host": "<%= iparam.freshdesk_domain %>",
      "path": "/api/v2/tickets/<%= context.ticketId %>/conversations",
      "headers": {
        "Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
        "Content-Type": "application/json"
      }
    }
  },
  "addNote": {
    "schema": {
      "protocol": "https",
      "method": "POST",
      "host": "<%= iparam.freshdesk_domain %>",
      "path": "/api/v2/tickets/<%= context.ticketId %>/notes",
      "headers": {
        "Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
        "Content-Type": "application/json"
      }
    }
  }
}
// scripts/app.js
document.addEventListener("DOMContentLoaded", function() {
    // Initialize the Freshworks client
    app.initialized().then(function(_client) {
        window.client = _client;
        client.events.on("app.activated", getTicketDetails);
    }).catch(function(error) {
        console.error("Failed to initialize the app: ", error);
        client.interface.trigger("showNotify", {
            type: "danger",
            message: "Error initializing app."
        });
    });
});
/**
let  client;
(async function init() {
  client = await app.initialized();
  client.events.on('app.activated', getTicketDetails);
})();
 * Fetches ticket details, checks if it's a child ticket, and updates the UI accordingly.
 */
async function getTicketDetails() {
    const statusSelect = document.getElementById('status-select');
    const actionButton = document.getElementById('action-btn');
    try {
        const data = await client.data.get("ticket");
        const ticket = data.ticket;
        // Save the ticketId to window for later use
        window.currentTicketId = ticket.id;
        // Based on the sample object, 'association_type' is a direct property of the ticket.
        if (ticket.association_type === 2) {
            // Enable UI elements
            statusSelect.disabled = false;
            actionButton.disabled = false;
            client.interface.trigger("showNotify", {
                type: "success",
                message: "This is a child ticket. Please select an action."
            });
        } else {
            // Keep UI disabled
            statusSelect.disabled = true;
            actionButton.disabled = true;
            client.interface.trigger("showNotify", {
                type: "info",
                message: "This app can only be used on child tickets."
            });
        }
    } catch (error) {
        client.interface.trigger("showNotify", {
            type: "danger",
            message: "Could not load ticket details."
        });
        // Ensure UI remains disabled on error
        if(statusSelect) statusSelect.disabled = true;
        if(actionButton) actionButton.disabled = true;
    }
}
// Add event listener for the button
document.getElementById('action-btn').addEventListener('click', async function() {
    const statusSelect = document.getElementById('status-select');
    const selectedValue = statusSelect.value;
    if (selectedValue === "0") {
        client.interface.trigger("showNotify", {
            type: "warning",
            message: "Please select a status from the dropdown before proceeding."
        });
        return;
    }
    if (selectedValue === "1") { // Pending Input
        client.interface.trigger("showNotify", {
            type: "info",
            message: "Team will check your last note and provide you with the input soon."
        });
        setTimeout(() => {
            client.interface.trigger("showNotify", {
                type: "info",
                message: "Ticket will be pending till we provide the input."
            });
        }, 2000);
        await handleAction("pending");
    }
    if (selectedValue === "2") { // Resolved
        client.interface.trigger("showNotify", {
            type: "info",
            message: "Issue will be closed and we will confirm with the customer."
        });
        setTimeout(() => {
            client.interface.trigger("showNotify", {
                type: "info",
                message: "Ticket is closed for now."
            });
        }, 2000);
        await handleAction("closed");
    }
});
/**
 * Main handler for the action. Will:
 * 1. Get ticket details (for parent ID)
 * 2. Get ticket conversations (for last note)
 * 3. Update status
 * 4. Add note to the parent
 */
async function handleAction(targetStatus) {
    try {
        // 1. Get child ticket details
        const ticketDetails = await client.request.invoke("getTicketDetails", {
            context: { ticketId: window.currentTicketId }
        });
        const ticketData = JSON.parse(ticketDetails.response);
        // Find parent ID from associated_tickets_list
        const parentIds = ticketData.associated_tickets_list || [];
        const parentId = parentIds.length > 0 ? parentIds[0] : null;
        if (!parentId) {
            client.interface.trigger("showNotify", {
                type: "danger",
                message: "No parent ticket found for this child ticket."
            });
            return;
        }
        // 2. Get conversations/notes for this ticket
        const convosResp = await client.request.invoke("getConversations", {
            context: { ticketId: window.currentTicketId }
        });
        const conversations = JSON.parse(convosResp.response);
        // Find the latest private note (if any)
        let latestNote = null;
        if (Array.isArray(conversations) && conversations.length > 0) {
            // Sort by created_at descending, just in case
            conversations.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
            latestNote = conversations[0];
        }
        if (!latestNote) {
            client.interface.trigger("showNotify", {
                type: "danger",
                message: "No notes found on this ticket to copy to parent."
            });
            return;
        }
        // 3. Update the ticket status
        let statusCode = 2; // Default "Open"
        if (targetStatus === "pending") statusCode = 3;
        if (targetStatus === "closed") statusCode = 5;
        await client.request.invoke("updateTicket", {
            context: { ticketId: window.currentTicketId },
            body: JSON.stringify({ status: statusCode })
        });
        // 4. Add the latest note to the parent
        await client.request.invoke("addNote", {
            context: { ticketId: parentId },
            body: JSON.stringify({
                body: latestNote.body_text || latestNote.body,
                private: true
            })
        });
        client.interface.trigger("showNotify", {
            type: "success",
            message: "Ticket status updated and note added to parent successfully."
        });
    } catch (err) {
        console.error("Error in handleAction:", err);
        client.interface.trigger("showNotify", {
            type: "danger",
            message: "An error occurred while processing the action. Please try again."
        });
    }
}
This is a hard blocker for any app development.
Thank you.
