Sending data to AITable (error with sending a number from record)

I am using an automation to send records to AI table using Send HTTP Request.

One of the fields in AITable is a number. When I try and add a number like below it works fine:
{
“records”: [
{
“fields”: {
“Location”: “Spain”,
“AA Number”: 1,
“Full Name”: “Ben Jones”
}
}
]
}

But when I bring the number from a record like below I get - Body must be a valid JSON object
{
“records”: [
{
“fields”: {
“Location”: “Spain”,
“AA Number”: <%= JSON.stringify(_actions.act0ji5Npq4XruEGRw5ggBzF.records[0].cells.fldwLmcTAeaOg3vhuowr5nLx) %>
}
}
]
}

If I add it quotes it does not have the error about being valid, but AITable complains that it is not a number so will not add the record.

UPDATE With the Body must be a valid JSON object I can run test and it works fine, it just will not let me save the automation part.

Any ideas anyone?

Thank you for bringing this to our attention! :raised_hands:

We’ve escalated this issue to our development team and they’re working on a fix. We’ll keep you updated on our progress.

Hi Nagisa

Any update on this, I really need the ability to send a number back to AITable, but I cannot do it with this issue.

Is it because the output from the script shows it as a text field Aa (Calls_Made)?

I don’t think there is a way of outputting the value as anything else in BIKA the code definately outputs as a number. From ChatGPT:

Your JavaScript Code is Correct: The line const callsMade = parseInt(webhookData["Calls Made"], 10) || 0; correctly converts the incoming “Calls Made” value into an integer. Then, let updatedCallsMade = callsMade + 1; performs a numerical addition. So, the updatedCallsMade variable (which is assigned to "Calls Made" in your output object) is a JavaScript number.

Many thanks

I’ve escalated this to the product team. It’s a complex change and is now with the lead for a decision. There isn’t a reliable workaround at the moment, and it’s unlikely to be resolved immediately. :flushed:

Thanks for your patience — feel free to reach out if you have more questions.

Thanks for the update.

Just a thought but adding a simple calculation action into automations would probably sort this out. I.e add/subtract to a number from a trigger or previous action?

The output of this would be a number and would pass the JSON rules, do you think?

Thanks
Ben

Hey Ben! Thanks for the suggestion :blush: While adding math operations sounds practical, using HTTP requests for that might not be the best approach. Instead, here’s a workaround: write a quick script in the “Run Script” action to handle calculations via AITable’s API. It also works really well with JSON rules! Just my two cents~

Hi Nagisa

I am not using AirTable but AITable, so not quite sure what you mean?

My current steps are to run a script that outputs a number (amongst other data). I need PATCH a record in AITable, are you saying instead of using the built in HTTP PATCH method, I should just PATCH AITable within the script? I could do this if thats a better way to go until you have a full fix in place.

Thanks
Ben

Hey Ben! Sorry for the typo earlier! Yep exactly – until the full fix is in place, using a script to send the PATCH HTTP request directly would make things more reliable right now. Thanks for bearing with us!

Hi Nagisa

If I share some code with you can you see what it does not work. I have tried everything to get an HTTP Request working in a script. I have also tried ChatGPT, Claaud, Gemini and I still cannot get the script action to send a PATCH to AITable. Below is a diagnostic script that shows errors in trying to make an HTTP POST.

// Complete AITable API diagnostic script
(async () => {
    const baseUrl = "https://aitable.ai";
    const databaseId = "dsty5rpZZZ"; // Replace with real ID
    const recordId = "recXExXXX"; // Replace with real ID
    const apiKey = "XXX"; // Replace with your COMPLETE API key
    
    const results = {
        timestamp: new Date().toISOString(),
        tests: [],
        summary: {}
    };
    
    // Test 1: API Key format check
    results.tests.push({
        name: "API Key Format",
        result: {
            length: apiKey.length,
            starts_with: apiKey.substring(0, 10) + "...",
            contains_spaces: apiKey.includes(" "),
            is_placeholder: apiKey === "usk7X..." || apiKey.includes("..."),
            diagnosis: apiKey === "usk7X..." || apiKey.includes("...") ? 
                "PLACEHOLDER DETECTED - You need to use your actual full API key" : 
                "Format looks reasonable"
        }
    });
    
    // Test 2: Basic GET request to list records
    try {
        const getUrl = `${baseUrl}/fusion/v1/datasheets/${databaseId}/records?maxRecords=1`;
        const getResponse = await fetch(getUrl, {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/json'
            }
        });
        
        const getStatus = getResponse.status;
        const getStatusText = getResponse.statusText;
        
        if (getResponse.ok) {
            const getData = await getResponse.json();
            results.tests.push({
                name: "GET Records Test",
                success: true,
                status: getStatus,
                statusText: getStatusText,
                message: "API key works for reading data",
                recordCount: getData.data?.records?.length || 0,
                hasTargetRecord: getData.data?.records?.some(r => r.recordId === recordId) || false
            });
        } else {
            const errorText = await getResponse.text();
            results.tests.push({
                name: "GET Records Test",
                success: false,
                status: getStatus,
                statusText: getStatusText,
                errorBody: errorText,
                diagnosis: getStatus === 401 ? "API key invalid/expired" : 
                          getStatus === 403 ? "API key lacks read permissions" :
                          getStatus === 404 ? "Database ID not found" : "Other error"
            });
        }
    } catch (error) {
        results.tests.push({
            name: "GET Records Test",
            success: false,
            error: error.message,
            diagnosis: "Network/CORS error"
        });
    }
    
    // Test 3: Try to get the specific record
    try {
        const recordUrl = `${baseUrl}/fusion/v1/datasheets/${databaseId}/records/${recordId}`;
        const recordResponse = await fetch(recordUrl, {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/json'
            }
        });
        
        if (recordResponse.ok) {
            const recordData = await recordResponse.json();
            results.tests.push({
                name: "GET Specific Record Test",
                success: true,
                status: recordResponse.status,
                message: "Target record found",
                recordData: recordData,
                hasCallsMadeField: recordData.data?.fields?.hasOwnProperty("Calls Made") || false
            });
        } else {
            const errorText = await recordResponse.text();
            results.tests.push({
                name: "GET Specific Record Test",
                success: false,
                status: recordResponse.status,
                statusText: recordResponse.statusText,
                errorBody: errorText,
                diagnosis: recordResponse.status === 404 ? "Record ID not found" : "Other error"
            });
        }
    } catch (error) {
        results.tests.push({
            name: "GET Specific Record Test",
            success: false,
            error: error.message,
            diagnosis: "Network/CORS error"
        });
    }
    
    // Test 4: Check if we can get datasheet metadata
    try {
        const metaUrl = `${baseUrl}/fusion/v1/datasheets/${databaseId}`;
        const metaResponse = await fetch(metaUrl, {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/json'
            }
        });
        
        if (metaResponse.ok) {
            const metaData = await metaResponse.json();
            results.tests.push({
                name: "Datasheet Metadata Test",
                success: true,
                status: metaResponse.status,
                message: "Can access datasheet metadata",
                fields: metaData.data?.fields?.map(f => f.name) || []
            });
        } else {
            const errorText = await metaResponse.text();
            results.tests.push({
                name: "Datasheet Metadata Test",
                success: false,
                status: metaResponse.status,
                statusText: metaResponse.statusText,
                errorBody: errorText
            });
        }
    } catch (error) {
        results.tests.push({
            name: "Datasheet Metadata Test",
            success: false,
            error: error.message
        });
    }
    
    // Generate summary
    const successfulTests = results.tests.filter(t => t.success).length;
    const totalTests = results.tests.length;
    
    results.summary = {
        testsRun: totalTests,
        testsSuccessful: successfulTests,
        overallStatus: successfulTests === 0 ? "FAILED" : 
                      successfulTests === totalTests ? "PASSED" : "PARTIAL",
        recommendations: []
    };
    
    // Add recommendations based on results
    const apiKeyTest = results.tests.find(t => t.name === "API Key Format");
    if (apiKeyTest?.result?.is_placeholder) {
        results.summary.recommendations.push("CRITICAL: Replace 'usk7X...' with your actual complete API key");
    }
    
    const getTest = results.tests.find(t => t.name === "GET Records Test");
    if (getTest && !getTest.success && getTest.status === 401) {
        results.summary.recommendations.push("API key is invalid or expired - generate a new one");
    }
    
    if (getTest && !getTest.success && getTest.status === 403) {
        results.summary.recommendations.push("API key lacks permissions - ensure it has read/write access");
    }
    
    if (getTest && !getTest.success && getTest.status === 404) {
        results.summary.recommendations.push("Database ID 'dsty5rpeetfGMQU1nq' not found - verify it's correct");
    }
    
    const recordTest = results.tests.find(t => t.name === "GET Specific Record Test");
    if (recordTest && !recordTest.success && recordTest.status === 404) {
        results.summary.recommendations.push("Record ID 'recXExJTn5z9j' not found - verify it exists");
    }
    
    if (successfulTests === 0) {
        results.summary.recommendations.push("Start by getting a valid API key from AITable settings");
    }
    
    return results;
})();

The output is the following:

Output:{

timestamp:

"2025-07-07T15:29:59.708Z"

tests:[

0:{

name:

"API Key Format"

result:{

length:

23

starts_with:

"XXX"

contains_spaces:

false

is_placeholder:

false

diagnosis:

"Format looks reasonable"

}

}

1:{

name:

"GET Records Test"

success:

false

error:

"Invalid URL"

diagnosis:

"Network/CORS error"

}

2:{

name:

"GET Specific Record Test"

success:

false

error:

"Invalid URL"

diagnosis:

"Network/CORS error"

}

3:{

name:

"Datasheet Metadata Test"

success:

false

error:

"Invalid URL"

}

]

summary:{

testsRun:

4

testsSuccessful:

0

overallStatus:

"FAILED"

recommendations:[

0

:

"Start by getting a valid API key from AITable settings"

]

}

}

This is using the same API key that I have been using (and still works) in the HTTP Actions.

Any guidance or some example code to patch an AITable record would be great. Do you know when the integration with AITable will be available and usable?

Thanks
Ben