Query an API with JavaScript?

Summary:
I’m querying an API that returns JSON data in a different structure depending on the query that I send it. As a result, I would like to parse the response conditionally.

Details:
I am using the UPS ‘Address Validation’ API. With this api, you send it an address with a city, state, address, zip, etc. If the address is valid, it will return the correct address you sent it as a single value. If the address is not valid, it returns a list of suggested addresses in an array. The difference in syntax between these two responses means that the bubble API connector
and the plugin editor API are not capable of handling both conditions.

I’m learning about the plugin editor, and I’m wondering if is possible to build a something to use JavaScript to fetch the data and conditionally parse the response? Is this possible some other kind of way? It seems like a bit of a gap in dealing with many external APIs and so I’m hoping there is a simple solution for this.

If I were to use JS, this API requires secret login credentials with each request, meaning that exposing this to the user via a client-side script would be an issue. I’m new to this, so perhaps I’m wrong in my approach, but it seems to me the only way to achieve this is with a plugin using via the new ‘server-side’ JavaScript as an action. It also seems like I must hard-code in the credentials since there doesn’t seem to be a way to pass in secret credentials from the plugins tab to a server-side script. If so, that’s unfortunate since it means I couldn’t even build this as a reusable plugin, share with the community, etc.

1 Like

Just a thought on using a standard api connector or plugin… I’ve had situations like this and got through it by manually building a json response containing all the possible values across the various types of response and then handing that to the api call in the set-up. If the possible elements are known, you can prepare bubble to see them all.

1 Like

The issue I’m running into is that the JSON is the same key value, but it structures its response differently depending on what I send it. When the address is found it returns a single set of values. When the address is not found it returns an array (list) of sets of values. Bubble can’t tell the difference. When I try typing in the key twice with each structure it just treats it like a list and doesn’t import single value (correct address) responses.

When the address is correct.

{
"XAVResponse": {
    "Response": {
        "ResponseStatus": {
            "Code": "1",
            "Description": "Success"
        },
        "TransactionReference": {
            "CustomerContext": "Your Customer Context"
        }
    },
    "ValidAddressIndicator": "",
    "Candidate": {
        "AddressKeyFormat": {
            "AddressLine": "12380 MORRIS RD",
            "PoliticalDivision2": "ALPHARETTA",
            "PoliticalDivision1": "GA",
            "PostcodePrimaryLow": "30005",
            "PostcodeExtendedLow": "4616",
            "Region": "ALPHARETTA GA 30005-4616",
            "CountryCode": "US"
        }
    }
}

.

.
When the address is incorrect: Returns an array.

{
    "XAVResponse": {
        "Response": {
            "ResponseStatus": {
                "Code": "1",
                "Description": "Success"
            },
            "TransactionReference": {
                "CustomerContext": "Your Customer Context"
            }
        },
        "AmbiguousAddressIndicator": "",
        "Candidate": [{
                    "AddressKeyFormat": {
                    "AddressLine": "27-35 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "2727",
                    "Region": "Beverly Hills MA 90210-2727",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "30-36 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "2739",
                    "Region": "Beverly Hills MA 90210-2739",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "37-47 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "2716",
                    "Region": "Beverly Hills MA 90210-2716",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "38-58 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "2732",
                    "Region": "Beverly Hills MA 90210-2732",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "49-57 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "2725",
                    "Region": "Beverly Hills MA 90210-2725",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "59-77 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "27ND",
                    "Region": "Beverly Hills MA 90210-27ND",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "60-62 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "2736",
                    "Region": "Beverly Hills MA 90210-2736",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "64-98 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "2737",
                    "Region": "Beverly Hills MA 90210-2737",
                    "CountryCode": "US"
                }
            },
            {
                "AddressKeyFormat": {
                    "AddressLine": "79-99 SOUTH RD",
                    "PoliticalDivision2": "Beverly Hills",
                    "PoliticalDivision1": "CA",
                    "PostcodePrimaryLow": "90210",
                    "PostcodeExtendedLow": "27ND",
                    "Region": "Beverly Hills MA 90210-27ND",
                    "CountryCode": "US"
                }
            }
        ]
    }
}

So Candidate swaps between an object and a list of objects.


A workaround could be to check the result, if the Candidate is empty, make a second call to a different API connector call, with the same url and parameters, but with the list result expected.


If you want to go with sending JSON to javascript, set the API Connector result to Text, and it won’t parse the JSON into different fields, instead returning a single text string.

1 Like

A workaround could be to check the result, if the Candidate is empty, make a second call to a different API connector call, with the same url and parameters, but with the list result expected.

Not sure how to best achieve this conditional logic? From what I’ve understood so far it doesn’t look like I can use JS to trigger any of the calls I’ve configured inside the API connector directly from the script. Is this assumption correct? Am I to assume there is a workaround using a standard workflow? I have a guess as to how this might work, but I’m hoping there is an easier way.

My proposed hackey work-around(s)

  1. One way I’m thinking this could be done is that if the text string from my first call fails some sort of evaluation, then I could send some state back to the page from my plugin, trigger a 2nd workflow to call upon 2nd api connector. This seems cumbersome and essentially would mean I could not make a ‘drop in’ plugin for this purpose.

  2. If I’m stuck using workflows to make this happen, then an even easier way is that bubble reports the :count for this value as zero when there is no list array present inside the result. I could filter a workflow based on that value and run a 2nd API call conditionally, but I’d really prefer to encapsulate the entire logic of this inside the plugin…

If what I’m saying above is true, then it seems like a significant limitation to making easy-to-use, elegant, plugins for many run-of-the-mill APIs, which I really don’t think are all that ‘edge-case’.

If you want to go with sending JSON to javascript, set the API Connector result to Text, and it won’t parse the JSON into different fields, instead returning a single text string.

Good to know I can parse the JSON as text with JS. I’ll look into that.

Alternatively I guess could fetch a URL directly from JS, however unless I do this via a server-side action I don’t see how I can avoid exposing secret keys unless they are hard-coded into the script, which seems less than ideal.

  • Can I securely pass stored secret variables into server-side action scripts?
  • Is there a way to have the plugin fetch results as data via a server-side JavaScript or at least keep APIs keys hidden?

Variables in the form of fields show up in the server logs.

You can define keys on the plugin, and access them via context. If the key is of type Private, it won’t be sent to the client, and they don’t show up on server logs. The problem is, they aren’t variable.

Not sure what you mean by “as data”, but yes you can use the node request, and return the results to the workflow as a text or something else that you decide upon.

You can define keys on the plugin, and access them via context. If the key is of type Private, it won’t be sent to the client, and they don’t show up on server logs. The problem is, they aren’t variable.

To confirm:
I cannot modify the keys inside my script, but the user can could them (like a password)?

I’m not all that familiar with JS, and so I’m not sure I completely understand how the secret keys are stored on the server and then accessed from the client-side script in abstraction (via context?) to be used for page actions without exposing them.

Not sure what you mean by “as data”

What I was referring to was the type of API call i.e. ‘action’ or ‘data’ as it would be defined in the API connector. As in the case of ‘data’, being able to call an API from a dynamic value under ‘Get From API’ in the bubble editor. This would be as opposed to a workflow action calling for the data.

but yes you can use the node request, and return the results to the workflow as a text or something else that you decide upon.

My understanding is that, at some point the standard ‘bubble API connector’ when it is configured as ‘data’ essentially runs some sort of server-side JS to make the call and presents the data back to the bubble environment, but obviously the ability manipulate the way the connector makes calls ‘is what it is.’ Is this a correct understanding?

Thanks!

Plugin keys are set by the app developer on the plugin settings page.
“User” meaning end user, could potentially view the value of Public keys on the plugin, but not Private keys.
Client-side javascript only has readonly access to the Public keys.
Server-side node has readonly access to Public and Private keys.
“context” means a particular javascript object with the name of “context”, its mentioned in the plugin editor documentation link on the code section.

Plugin server and client scripts are completely independent of the API connections defined, the script cannot directly access them. Its up to the app developer to pass values to the plugin, including API connection results.

Alternatively, both javascript and node scripts can make HTTP requests independently of API connections defined.

Yes I agree, its a bit clunky lol.

Thanks for the clarification. I appreciate it.

Thnks guys!! It cleared my so many doubts

I wanted to mention that with the particular API I’m working with I was able to pass a blank value to the key of a parent JSON element, which in turn, caused the API to ignore everything nested underneath it. This actually solved my need for needing JS at all to query to this API.

Lets say this simplified example below would return a successful result:

{
"ShippingInsurance":{
   "Amount":"60.00",
   "Currency":"USD"
 }
}

In bubble API editor that might look like

{
"ShippingInsurance":{
   "Amount":"<Insurance Amount>",
   "Currency":"<Insurance Currency>"
 }
}

But what if I don’t want shipping insurance? Sending these requests don’t work with this API and both raise errors.

{
"ShippingInsurance":{
   "Amount":"",
   "Currency":""
 }
}

or

{
"ShippingInsurance":{
   "Amount":"0.00",
   "Currency":"USD"
 }
}

You could create two API calls–one with and without “ShippingInsurance” being present, but realistically there are dozens of options and even more combinations which make creating separate API calls with different templates unrealistic.

This is what did work for this API

{
"<Insurance Key Value>":{
   "Amount":<Insurance Amount>",
   "Currency":"<Insurance Currency>"
 }
}

Setting a blank value in bubble for ‘Insurance Key Value’ caused the UPS API to ignore that entire element and its nested values. Setting ‘Insurance Key Value’ to “ShippingInsurance” properly invokes the insurance call (as long as you also provide the required values). It was a great lesson is working with some of these older APIs

Fortunately, the Shipping API ignores this request

{
"":{
   "Amount":"",
   "Currency":""
 }
}

It seems that the UPS REST API (JSON) is built on top of their SOAP/XML API and so perhaps (and I’m guessing here) that also follow this scheme of skipping a blank parent value. After all of this, I’m thinking bubble’s XML template could have been a better fit. :slight_smile:

2 Likes

That’s a pretty good workaround!

If you set the key value to “ignoreMePlease”, does the UPS API throw an error, or does it ignore what it doesn’t know about?

An alternative to having individual parameters inside the JSON is to not have any parameters, and build up the JSON body at the time of making the API call. Its generally messier, because you have to escape quotes etc, but more flexible.

You will probably find that the UPS API will also accept a url-encoded or XML body if you set the correct header in the API Connector.

@mishav

Regarding the naming of values:
You bring up something CRITICAL. The bubble API connector does NOT pass blank values inside the JSON body of a POST request. This behavior may extend other areas of the connector as well. Blank values are recognized by the API connector and converted to the text string value “null” (not to be confused with a null byte). I’m hazarding a guess here that this is because many/most/all? JSON/other? APIs do not like blank values. such as {"key":""} where “” is your blank value. Likely an attempt to make the connector more ‘user friendly’ by changing your values without telling you.

I only ran into this issue when testing API calls with Postman using the variables feature within that program just as I had within bubble. In bubble, I simply left those variables blank (thinking they were sending out as blank). I spent hours troubleshooting this, getting UPS support involved to only have them tell me. ‘Its not working because you are sending a blank value (ie. “”) in your API call’. I thought, thats exactly what I’ve been doing! Only when I redirected the API URL inside the bubble API connector to postman-echo.com/postand finally saw what the request actually looked like did I discover it.

So, in short. You can type “IgnoreThis” and the UPS api will ignore it… and presumably any other key values that aren’t assigned in the namespace of the API. This makes it tricky to troubleshoot if you misspell a key though.

An alternative to having individual parameters inside the JSON is to not have any parameters, and build up the JSON body at the time of making the API call.

I am considering doing this with the parameter that is used for the address line. UPS accepts the address line as an array of values.

{"AddressLine":["Suite 404","Building A","123 Fake St"]}
{"AddressLine":["<line3>,"<line2>","<line1>"]}

If you only have an address with one line, you can’t send a blank value for lines 2 and 3. You cant send a space character or even a single character of punctuation. As a result, the printed label would look like

123 Fake St.
NULL
NULL

or some other not-correct format which looks bad and could cause confusion or even misdelivery. To ignore address line 2 & 3, the array must be a different size. i.e. a two line address would look like ["line2,"line1"] or a single line address value isn’t even an array and looks like "line1"

Another option I could use because this array has 3 possible forms is to include all three forms of the array and tell the API which one to use by filling in the text “AddressLine” where appropriate.

{"<3_AddressLines>":["<line3>,"<line2>","<line1>"]}
{"<2_AddressLines>"::["<line2>","<line1>"]}
{"<1_AddressLine>":"<line1>"}

In this case, you would use bubble workflow to figure out if line 2 or line 3 exists, and if so, conditionally fill out ‘AddressLine’ in the variable for the key that matches the right form. For example if you had a 2 line address, you would fill out 2_AddressLine and leave the others blank so the API would ignore them. Your api call would look like this:

{"NULL":["NULL","Apt 2","123 Fake St."]} . –IGNORED
{"AddressLine":["Apt 2","123 Fake St"]}
{"NULL":"123 Fake St"} –IGNORED

This is a bit of a hack workaround, but doable.

If the array were many more values, then I think you’d might want to construct that as

{"AddressLine":<AddressArray>}

and build the text for your array AddressArray inside bubble with commas quotations, brackets , and values.

UPS does accept XML as well. I’m pretty sure that the JSON interface is simply built on top of that. The API documentation has a JSON appendix, but the main guide documentation assumes XML. I did not start this project myself and so, if I had it to do over again, I would have written this in XML because that seems to be what it expects.

This is correct for JSON, see https://json.org/

So it should look like

{ "key": null }

Nice!

Are you saying its too hard to conditionally build up the array of address lines?

Did you get any further with this?

This is correct for JSON, see https://json.org/

So it should look like

{ "key": null }

I totally get it. Despite that fact, It can definitely be confusing for the novice API builder that bubble is adding null string value into the JSON request when the variables are left blank. Smartly, I night add.

What triggered this was that I copy/pasted my api request from bubble into postman. I then used postman’s variables in place of my bubble variables. Postman doesn’t know where your variables are being used (json or otherwise), so if you leave them blank it will pass a blank value into your request. So in postman of "Key": "{Variable}" will be passed as "Key":""

Weirdly, my JSON syntax checker didn’t throw an error with this either. I don’t love that you cannot preview your API request exactly as it’s being sent.

.
.
.
So here’s what I’ve done

#1 – The Array
Not related to Bubble development or JSON or anything, but I was wrong about how the address array was formed and its actually more intuitive:

The correct address array is:

["Address1","Address2","Address3"] —as expected

I never noticed this, but UPS actually prints their labels like this:

Address Line 2
Address Line 1

Apt 4
123 Fake St.

I noticed this when forming a Ship From address array, where Address2 was dropped from the label and I was actually only seeing Address2 in the from address.

.
.
.
#2 — Conditionals In Bubble
I formed this array conditionally in bubble, but limited it to an Array of 2 items. (I don’t need 3 address lines) I also find it difficult to manage nested too many conditionals inside the bubble editor.

I built the API connector as:

Addressline: ["<Address Array>"]

I built the bubble editor as:

Search For Contact:first item:Addressline2:is empty

:is empty

Yes
Search For Contact:first item:Addressline1

No
Search For Contact:first item:Addressline1 “,” Search For Contact:first item:Addressline2

When Address2 is empty it generates

["Address1"]

And When Address2 is not empty it generates

["Address1","Address2"]

For my needs, this is fine, but I wonder if I could fill the array dynamically with a larger list using this plugin

1 Like

This topic was automatically closed after 70 days. New replies are no longer allowed.