Back to Blog
2025-12-06 7 min read

Architecting Robust cURL Error Handling in FileMaker

The Illusion of a Successful API Call

When integrating external services using the Insert from URL script step, it is easy to fall into a dangerous trap. A developer might configure the step, test it once, and, seeing no error dialog, assume the integration is sound. This assumption is the foundation of a brittle system.

The most common but incomplete error handling method is to check Get(LastError) immediately after the call. While necessary, this function only tells part of the story. It reports FileMaker's ability to execute the instruction--for instance, whether it could resolve the domain name or establish a network connection. It tells you nothing about what the API server on the other end thought of your request.

A truly robust architecture acknowledges that failure can occur at multiple layers: the local network, the internet connection, or the remote server itself. To build reliable solutions, we must handle errors from every layer.

The Two Layers of Failure

Think of an API call as delivering a letter. Get(LastError) tells you if the postal service was able to leave your house and find the destination address. It does not tell you if the recipient opened the letter, understood it, or simply threw it in the trash.

  1. The Connection Layer: This is FileMaker's domain. Did the DNS lookup fail? Was the server unreachable? Is the SSL certificate invalid? FileMaker error codes, like 1631 ("Connection failed"), live here. Get(LastError) is how we query this layer.

  2. The Application Layer: This is the API's domain. Once a connection is made, the server processes your request. Was your authentication key invalid? Did you ask for a record that does not exist? Did you format your request incorrectly? The server communicates its response using standard HTTP status codes. This is the layer that Get(LastError) completely ignores. A successful connection (Get(LastError) = 0) can still result in a total request failure (e.g., HTTP 401 Unauthorized).

Our architecture must account for both layers to be considered complete.

The "Capture, Parse, Decide" Pattern

The most effective way to handle API calls is to capture all possible information, parse it for meaning, and then make a logical decision. This involves using cURL options to get not just the data, but the metadata about the response.

Step 1: Capture Everything

The key is the -D (or --dump-header) cURL option. This tells the server to return the HTTP response headers in addition to the body of the result. We will store these in a separate variable.

Your Insert from URL script step should be configured like this:

  • Target: $result
  • Specify cURL options:
    "-D $$headers"
    

Here, we've instructed FileMaker to place the main response from the API into the variable $result and the HTTP headers into the global variable $$headers. We use a global variable for the headers to ensure they persist if our script calls a subscript and we need to inspect them later.

Step 2: Check the Connection Layer

First, we handle the most basic failures. Immediately after the Insert from URL step, check the FileMaker error code.

Insert from URL [ Select ; With dialog: Off ; Target: $result ; "https://api.example.com/data" ; cURL options: "-D $$headers" ]
If [ Get ( LastError ) <> 0 ]
    # Connection failed. FileMaker couldn't even reach the server.
    # Log the error: Get(LastError) & " " & Get(LastErrorDetail)
    # Exit script, perhaps showing a user dialog.
    Exit Script [ Text Result: "Error: Connection Failure" ]
End If

This is our first gate. If we cannot even connect, there is no point in checking the headers or the result.

Step 3: Parse the Application Layer

If Get(LastError) is 0, we have successfully connected to the API server. Now we must ask the server how it handled our request. We do this by parsing the $$headers variable to find the HTTP status code.

The first line of the HTTP headers always contains the status. It looks something like HTTP/1.1 200 OK or HTTP/2 404. We need to extract that number.

# Extract the HTTP Status Code from the headers
Set Variable [ $httpStatus ; Value:
    Let (
        [
            firstLine = GetValue ( $$headers ; 1 ) ;
            // Result: "HTTP/2 200" or similar
            firstSpace = Position ( firstLine ; " " ; 1 ; 1 ) ;
            secondSpace = Position ( firstLine ; " " ; 1 ; 2 ) ;
            code = Middle ( firstLine ; firstSpace + 1 ; secondSpace - firstSpace - 1 )
        ] ;
        GetAsNumber ( code )
    )
]

This calculation isolates the first line of the headers and extracts the numeric code between the first and second space.

Step 4: Decide Based on Status

With the $httpStatus variable populated, we can now build logic to handle the outcome properly. HTTP status codes are grouped into classes:

  • 2xx (e.g., 200, 201): Success. The request was received, understood, and accepted.
  • 4xx (e.g., 400, 401, 404): Client Error. You did something wrong (bad request, bad authentication, requested a non-existent resource).
  • 5xx (e.g., 500, 503): Server Error. The API server itself had a problem.

Our script can now branch accordingly.

# --- CONTINUATION OF SCRIPT ---

# Extract the HTTP Status Code from the headers (calculation from above)
Set Variable [ $httpStatus ; Value: ... ]

If [ $httpStatus >= 200 and $httpStatus <= 299 ]
    # SUCCESS
    # The API call was successful. Now you can safely parse the $result.
    Set Variable [ $someValue ; Value: JSONGetElement ( $result ; "data.name" ) ]
    # ... continue with workflow ...

Else
    # FAILURE
    # The API call failed. Log the details and handle the error.
    Set Variable [ $errorMessage ; Value: "API Error. Status: " & $httpStatus ]

    # Many APIs provide error details in the JSON body.
    Set Variable [ $apiErrorDetail ; Value: JSONGetElement ( $result ; "error.message" ) ]
    If [ Left ( $apiErrorDetail ; 1 ) = "?" ]
        Set Variable [ $errorMessage ; Value: $errorMessage & ". No details provided." ]
    Else
        Set Variable [ $errorMessage ; Value: $errorMessage & ". Detail: " & $apiErrorDetail ]
    End If

    # Log the full error to a dedicated table for review.
    # Show a simplified message to the user.
    Show Custom Dialog [ "Request Failed" ; "Could not retrieve data from the server. Please try again later." ]
    Exit Script [ Text Result: $errorMessage ]
End If

Centralizing Logic in a Subscript

This error-handling pattern is essential for every API call you make. To avoid repeating this logic everywhere, you should encapsulate it in a centralized subscript.

Create a script named API.Call (or similar) that accepts parameters for the URL, cURL options, and request body. This script performs the Insert from URL step and the entire "Capture, Parse, Decide" pattern internally. It should then return a structured result, typically in JSON format, that clearly indicates success or failure and includes the relevant data or error message.

Example return from API.Call subscript:

For a successful call:

{
  "success": true,
  "statusCode": 200,
  "response": { "id": 123, "name": "Project Alpha" }
}

For a failed call:

{
  "success": false,
  "statusCode": 404,
  "error": "Record not found",
  "response": {
    "error": { "message": "The requested resource could not be found." }
  }
}

A calling script can then simply check the success key in the JSON result to determine its next steps, leading to cleaner, more maintainable, and vastly more reliable code.

Conclusion

Relying on Get(LastError) alone for API calls is an architectural flaw. By adopting the "Capture, Parse, Decide" pattern, you create a multi-layered defense against failure. You first validate the connection with Get(LastError), then interrogate the API's response by parsing HTTP status codes from the headers. This robust approach transforms brittle integrations into resilient, predictable systems that can gracefully handle the inevitable failures of distributed computing.

Architecting Robust cURL Error Handling in FileMaker | Jeffrey Henry