Google Apps Script - API POST returning a 200 instead of a 201

Heya folks!

Problem: POST requests are not posting. Are returning 200 and GET response instead of 201 and POST response.

Expectation: 201 POST New resource created. A new price rule is created with a return giving, among other things, its ID.
Reality: 200 GET of all price rules. No change to list of price rules or discount codes.

I have this here (sanitized) Google Apps Script based code:

var accessToken = '{my_access_token}';

function createPriceRule() {
  var shopName = '{my_shop_name}'; 
  var url = 'https://' + shopName + '.myshopify.com/admin/api/2023-10/price_rules.json';
  var headers = {
    "X-Shopify-Access-Token": accessToken,
    'Content-Type': 'application/json',
    "Cookie": "" 
  };

  var payload = {
    'price_rule': {
      "title": "abcdefg",
      "target_type": "order", 
      "target_selection": "all", 
      "allocation_method": "across",
      "value_type": "percentage",
      "value": "-100.0", 
      "customer_selection": "all",
      "starts_at": "2023-01-01T00:00:00Z" 
    }
  };

  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload)
  };

  try {
    var response = UrlFetchApp.fetch(url, options);
    Logger.log("Response Code: " + response.getResponseCode());
    Logger.log("Response Content: " + response.getContentText());
  } catch (e) {
    Logger.log("Exception: " + e.toString());
  }
}

For context, I created this custom app through the shopify admin via settings > apps and sales channels > develop apps > create an app. I created this app on Jan 4th 2024 and it is now Jan 6th 2024

I know the following to be true and triple checked:

  • I am using the correct access token.

  • I have the proper scopes set (I have over shot on purpose)

  • I am using the “X-Shopify-Access-Token” header

  • I am not passing any cookies in my request because by default Google apps script does not pass cookies, but I doubled down and passed an empty cookies parameter. (as part of attempting to troubleshoot this)

  • This request also is not functioning in curl, postman, or php for me.

I am thinking I am experiencing some sort of bug or "new access token blockage". Any help here would be greatly appreciated because I am pulling my hair out over what should be a simple non-oauth2 basic access token driven API call.

I don’t think it is a scope or access token problem since it is at least connecting and getting some type of valid 200 response back. If it isn’t triggering the create, then I feel like this would be an issue with your request format or the data being sent… I would focus your search there.

I was able to get a 201 when using the following curl:

curl -X POST "https://{your-shop-name}.myshopify.com/admin/api/2023-10/price_rules.json" \
     -H "Content-Type: application/json" \
     -H "X-Shopify-Access-Token: {your-access-token}" \
     -d '{
          "price_rule": {
            "title": "New Price Rule",
            "target_type": "line_item",
            "target_selection": "all",
            "allocation_method": "across",
            "value_type": "fixed_amount",
            "value": "-10.0",
            "customer_selection": "all",
            "starts_at": "2024-01-01"
          }
        }'

response:

Status:
201 (Created)
Time:
208 ms
Size:
1.01 kb
Content (39)
Headers (36)
Raw (38)
JSON
Timings
{
    "price_rule": {
        "id": 1399987994862,
        "value_type": "fixed_amount",
        "value": "-10.0",
        "customer_selection": "all",
        "target_type": "line_item",
        "target_selection": "all",
        "allocation_method": "across",
        "allocation_limit": null,
        "once_per_customer": false,
        "usage_limit": null,
        "starts_at": "2023-12-31T16:00:00-08:00",
        "ends_at": null,
        "created_at": "2024-01-06T23:36:58-08:00",
        "updated_at": "2024-01-06T23:36:58-08:00",
        "entitled_product_ids": [],
        "entitled_variant_ids": [],
        "entitled_collection_ids": [],
        "entitled_country_ids": [],
        "prerequisite_product_ids": [],
        "prerequisite_variant_ids": [],
        "prerequisite_collection_ids": [],
        "customer_segment_prerequisite_ids": [],
        "prerequisite_customer_ids": [],
        "prerequisite_subtotal_range": null,
        "prerequisite_quantity_range": null,
        "prerequisite_shipping_price_range": null,
        "prerequisite_to_entitlement_quantity_ratio": {
            "prerequisite_quantity": null,
            "entitled_quantity": null
        },
        "prerequisite_to_entitlement_purchase": {
            "prerequisite_amount": null
        },
        "title": "New Price Rule",
        "admin_graphql_api_id": "gid:\/\/shopify\/PriceRule\/1399987994862"
    }
}

The only thing for me, is check to make sure this is the correct format

starts_at": "2023-01-01T00:00:00Z

I notice you are sending a

“Starts at: 2024-01-01”

But receiving a

"“starts_at”: “2023-12-31T16:00:00-08:00”

Is this due to the unspecified time parameter?

And how does shopify want me to send time parameters.

Also I think it might be a little deeper than this, because I am getting the same exact error on POST, PUT and DELETEs as well, which I were I started looking for problems with access tokens.

Access token or scopes will return a 403 forbidden response. I just tried with incorrect scopes.

Yes. I tried that as well using this page as my reference

https://shopify.dev/docs/api/usage/response-codes

Also,

I updated the time param, like this:

var accessToken = '{my_access_token}';

function createPriceRule() {
  var shopName = '{my_shop_name}'; 
  var url = 'https://' + shopName + '.myshopify.com/admin/api/2023-10/price_rules.json';
  var headers = {
    "X-Shopify-Access-Token": accessToken,
    'Content-Type': 'application/json',
    "Cookie": "" 
  };

  var payload = {
    'price_rule': {
      "title": "abcdefg",
      "target_type": "order", 
      "target_selection": "all", 
      "allocation_method": "across",
      "value_type": "percentage",
      "value": "-100.0", 
      "customer_selection": "all",
      "starts_at": "2024-01-07" 
    }
  };

  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload)
  };

  try {
    var response = UrlFetchApp.fetch(url, options);
    Logger.log("Response Code: " + response.getResponseCode());
    Logger.log("Response Content: " + response.getContentText());
  } catch (e) {
    Logger.log("Exception: " + e.toString());
  }
}

and I am still receiving the same exact problem. 200 code. No 201, no return like what you are showing.

Did you make a brand new access token to test this, or was this an existing token?

Also for anyone else who may stumble across this, I also tried the time code of “2023-12-31T16:00:00-08:00”, which is what is listed in the response of @SomeUsernameHe 's 201 return

and your responses come with no other information besides the 200, OK?

I figured it out, your target type is invalid.

target_type can only be “line_item” or “shipping_line”

The target type that the price rule applies to. Valid values:

Hide target_type properties
line_item: The price rule applies to the cart's line items.
shipping_line: The price rule applies to the cart's shipping lines.

with the following curl:

curl -X POST "https://your-shop-name.myshopify.com/admin/api/2023-10/price_rules.json" \
     -H "Content-Type: application/json" \
     -H "X-Shopify-Access-Token: your-access-token" \
     -d '{
          "price_rule": {
            "title": "abcdefg",
            "target_type": "line_item",
            "target_selection": "all",
            "allocation_method": "across",
            "value_type": "percentage",
            "value": "-100.0",
            "customer_selection": "all",
            "starts_at": "2024-01-07"
          }
        }'

returns a 201:

Status:
201 (Created)

{
    "price_rule": {
        "id": 1400074207470,
        "value_type": "percentage",
        "value": "-100.0",
        "customer_selection": "all",
        "target_type": "line_item",
        "target_selection": "all",
        "allocation_method": "across",
        "allocation_limit": null,
        "once_per_customer": false,
        "usage_limit": null,
        "starts_at": "2024-01-06T16:00:00-08:00",
        "ends_at": null,
        "created_at": "2024-01-07T11:01:40-08:00",
        "updated_at": "2024-01-07T11:01:40-08:00",
        "entitled_product_ids": [],
        "entitled_variant_ids": [],
        "entitled_collection_ids": [],
        "entitled_country_ids": [],
        "prerequisite_product_ids": [],
        "prerequisite_variant_ids": [],
        "prerequisite_collection_ids": [],
        "customer_segment_prerequisite_ids": [],
        "prerequisite_customer_ids": [],
        "prerequisite_subtotal_range": null,
        "prerequisite_quantity_range": null,
        "prerequisite_shipping_price_range": null,
        "prerequisite_to_entitlement_quantity_ratio": {
            "prerequisite_quantity": null,
            "entitled_quantity": null
        },
        "prerequisite_to_entitlement_purchase": {
            "prerequisite_amount": null
        },
        "title": "abcdefg",
        "admin_graphql_api_id": "gid:\/\/shopify\/PriceRule\/1400074207470"
    }
}

I was very excited to try this when i got home.

I made that change and

var accessToken = '{accesstoken}';

function createPriceRule() {
  var shopName = '{shopname}'; 
  var url = 'https://' + shopName + '.myshopify.com/admin/api/2024-01/price_rules.json';
  var headers = {
    "X-Shopify-Access-Token": accessToken,
    'Content-Type': 'application/json',
    "Cookie": "" 
  };

  var payload = {
    'price_rule': {
      "title": "abcdefg",
      "target_type": "line_item", 
      "target_selection": "all", 
      "allocation_method": "across",
      "value_type": "percentage",
      "value": "-100.0", 
      "customer_selection": "all",
      "starts_at": "2023-12-31T16:00:00-08:00",
    }
  };

  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload)
  };

  try {
    var response = UrlFetchApp.fetch(url, options);
    Logger.log("Response Code: " + response.getResponseCode());
    Logger.log("Response Content: " + response.getContentText());
  } catch (e) {
    Logger.log("Exception: " + e.toString());
  }
}

…still not working for me :disappointed_face:

I even tried copying your Curl and running that.

Remove the comma after start_at:

change this:

"starts_at": "2023-12-31T16:00:00-08:00",

to this:

"starts_at": "2023-12-31T16:00:00-08:00"

Oh shoot. That was actually a typo here and not in my code as I had tried a few more things since then, but reverted back to paste this version. That comma is not present in the code here:

var accessToken = '{access}';

function createPriceRule() {
  var shopName = '{shopname}'; 
  var url = 'https://' + shopName + '.myshopify.com/admin/api/2024-01/price_rules.json';
  var headers = {
    "X-Shopify-Access-Token": accessToken,
    'Content-Type': 'application/json',
    "Cookie": "" 
  };

  var payload = {
    'price_rule': {
      "title": "abcdefg",
      "target_type": "line_item", 
      "target_selection": "all", 
      "allocation_method": "across",
      "value_type": "percentage",
      "value": "-100.0", 
      "customer_selection": "all",
      "starts_at": "2023-12-31T16:00:00-08:00"
    }
  };

  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload)
  };

  try {
    var response = UrlFetchApp.fetch(url, options);
    Logger.log("Response Code: " + response.getResponseCode());
    Logger.log("Response Content: " + response.getContentText());
  } catch (e) {
    Logger.log("Exception: " + e.toString());
  }

I am positive this is the version I just ran because I copied it direct and sanitized it here.

I guess it may be helpful to see a log, but really it is as described at the beginning of the post:

11:00:49 PM	Info	Response Code: 200
11:00:49 PM	Info	Logging output too large. Truncating output. Response Content: {"price_rules":[{"id":1441878638875,"value_type":"percentage","value":"-100.0","customer_selection":"all","target_type":"line_item","target_selection":"all","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":null,"starts_at":"2024-01-08T00:00:00-05:00","ends_at":"2024-01-08T23:59:59-05:00","created_at":"2024-01-05T16:15:07-05:00","updated_at":"2024-01-05T16:16:59-05:00","entitled_product_ids":[],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null},"prerequisite_to_entitlement_purchase":{"prerequisite_amount":null},"title":"DISCOUNTCODE","admin_graphql_api_id":"gid:\/\/shopify\/PriceRule\/1441878638875"},{"id":1441878540571,"value_type":"percentage","value":"-100.0","customer_selection":"all","target_type":"line_item","target_selection":"all","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":null,"starts_at":"2024-01-07T00:00:00-05:00","ends_at":"2024-01-07T23:59:59-05:00","created_at":"2024-01-05T16:14:29-05:00","updated_at":"2024-01-07T22:10:05-05:00","entitled_product_ids":[],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null},"prerequisite_to_entitlement_purchase":{"prerequisite_amount":null},"title":"DISCOUNTCODE","admin_graphql_api_id":"gid:\/\/shopify\/PriceRule\/1441878540571"},{"id":1441877819675,"value_type":"percentage","value":"-100.0","customer_selection":"all","target_type":"line_item","target_selection":"all","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":null,"starts_at":"2024-01-06T00:00:00-05:00","ends_at":"2024-01-06T23:59:59-05:00","created_at":"2024-01-05T16:08:48-05:00","updated_at":"2024-01-06T23:35:02-05:00","entitled_product_ids":[],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null},"prerequisite_to_entitlement_purchase":{"prerequisite_amount":null},"title":"DISCOUNTCODE","admin_graphql_api_id":"gid:\/\/shopify\/PriceRule\/1441877819675"},{"id":1393172185371,"value_type":"fixed_amount","value":"-500.0","customer_selection":"all","target_type":"line_item","target_selection":"entitled","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":1,"starts_at":"2023-06-14T20:42:43-04:00","ends_at":null,"created_at":"2023-06-14T20:42:43-04:00","updated_at":"2023-07-31T18:55:01-04:00","entitled_product_ids":[8048362946843],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null},"prerequisite_to_entitlement_purchase":{"prerequisite_amount":null},"title":"DISCOUNTCODE","admin_graphql_api_id":"gid:\/\/shopify\/PriceRule\/1393172185371"},{"id":1388207636763,"value_type":"fixed_amount","value":"-500.0","customer_selection":"all","target_type":"line_item","target_selection":"entitled","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":1,"starts_at":"2023-04-23T23:22:39-04:00","ends_at":null,"created_at":"2023-04-23T23:22:39-04:00","updated_at":"2023-04-23T23:25:02-04:00","entitled_product_ids":[8048362946843],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null},"prerequisite_to_entitlement_purchase":{"prerequisite_amount":null},"title":"DISCOUNTCODE","admin_graphql_api_id":"gid:\/\/shopify\/PriceRule\/1388207636763"},{"id":1387254776091,"value_type":"fixed_amount","value":"-500.0","customer_selection":"all","target_type":"line_item","target_selection":"entitled","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":1,"starts_at":"2023-04-11T16:50:30-04:00","ends_at":null,"created_at":"2023-04-11T16:50:30-04:00","updated_at":"2023-04-11T16:50:30-04:00","entitled_product_ids":[8048361931035],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null},"prerequisite_to_entitlement_purchase":{"prerequisite_amount":null},"title":"DISCOUNTCODE","admin_graphql_api_id":"gid:\/\/shopify\/PriceRule\/1387254776091"},{"id":1384951382299,"value_type":"fixed_amount","value":"-500.0","customer_selection":"all","target_type":"line_item","target_selection":"entitled","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":1,"starts_at":"2023-03-09T14:05:12-05:00","ends_at":null,"created_at":"2023-03-09T14:05:12-05:00","updated_at":"2023-03-09T14:05:12-05:00","entitled_product_ids":[8048362946843],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null},"prerequisite_to_entitlement_purchase":{"prerequisite_amount":null},"title":"DISCOUNTCODE","admin_graphql_api_id":"gid:\/\/shopify\/PriceRule\/1384951382299"},{"id":1384951316763,"value_type":"fixed_amount","value":"-500.0","customer_selection":"all","target_type":"line_item","target_selection":"entitled","allocation_method":"across","allocation_limit":null,"once_per_customer":false,"usage_limit":1,"starts_at":"2023-03-09T14:05:03-05:00","ends_at":null,"created_at":"2023-03-09T14:05:03-05:00","updated_at":"2023-03-09T14:05:03-05:00","entitled_product_ids":[8048361931035],"entitled_variant_ids":[],"entitled_collection_ids":[],"entitled_country_ids":[],"prerequisite_product_ids":[],"prerequisite_variant_ids":[],"prerequisite_collection_ids":[],"customer_segment_prerequisite_ids":[],"prerequisite_customer_ids":[],"prerequisite_subtotal_range":null,"prerequisite_quantity_range":null,"prerequisite_shipping_price_range":null,"prerequisite_to_entitlement_quantity_ratio":{"prerequisite_quantity":null,"entitled_quantity":null

(the discount codes have been sanitized --just in case)

Like I said, It is like returning me a GET despite being a post

I have other apis that use this same methodology through google apps script, so I know the post works as expected.

For this one, maybe try this really quick:

var accessToken = '{access}';

function createPriceRule() {
  var shopName = '{shopname}';
  var url = 'https://' + shopName + '.myshopify.com/admin/api/2024-01/price_rules.json';
  var headers = {
    "X-Shopify-Access-Token": accessToken,
    'Content-Type': 'application/json'
  };

  var payload = {
    'price_rule': {
      "title": "abcdefg",
      "target_type": "line_item",
      "target_selection": "all",
      "allocation_method": "across",
      "value_type": "percentage",
      "value": "-100.0",
      "customer_selection": "all",
      "starts_at": "2023-12-31T16:00:00-08:00"
    }
  };

  var options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true
  };

  try {
    var response = UrlFetchApp.fetch(url, options);
    var responseCode = response.getResponseCode();
    var responseBody = response.getContentText();
    
    if (responseCode === 201) {
      Logger.log("Price rule created successfully: " + responseBody);
    } else {
      Logger.log("Response Code: " + responseCode);
      Logger.log("Response Content: " + responseBody);
    }
  } catch (e) {
    Logger.log("Exception: " + e.toString());
  }
}

If you are still getting a 200, then I would reach our directly to shopify support and see if it is a bug with this specific store.

You can also go to this site: https://reqbin.com/curl

and test with this known working code:

curl -X POST "https://your-shop-name.myshopify.com/admin/api/2023-10/price_rules.json" \
     -H "Content-Type: application/json" \
     -H "X-Shopify-Access-Token: your-access-token" \
     -d '{
          "price_rule": {
            "title": "abcdefg",
            "target_type": "order",
            "target_selection": "all",
            "allocation_method": "across",
            "value_type": "percentage",
            "value": "-100.0",
            "customer_selection": "all",
            "starts_at": "2024-01-07"
          }
        }'

Just replace the access token and your shop. If this works, you know it is not an issue with the API but your code.

I figured it out!

So for some reason when it in https://reqbin.com/ I was able to run it under myshopname.myshopify.com and it returned just fine. Great!

I got frustrated with Google Apps Script, so I made an endpoint on my server and wrote what I just got working in reqbin in PHP…

php returned a 301…redirect. Weird. So I examined the entirety of the php and found out that it was redirecting to a different url within my schema. I am not sure why it is doing this because that is not the first domain on the shopify account, BUT I reran it in google apps script and used the new URL and it worked.

I am so dumbfounded as to why there is a redirect when i was petitioning example.myshopify.com over to example-dash-example.myshopify.com when example.myshopify.com is the actual domain that everything else redirects to.

For some reason unbeknownst to me, the example-dash-example.myshopify.com domain was the one that ended up working.

Your issue was a two-part solution. The target type was still not correct.

"target_type": "order",

and I am kind of confused about why you would be using anything other than your shop “shop.myshopify.com” domain. Is that not what your code and my code as doing?

var url = 'https://' + shopName + '.myshopify.com/admin/api/2023-10/price_rules.json';

It was not happy with the primary url for some reason which is still unbeknownst to me.

I rewrote it in php and examined the php errors using -t in cli. In tail, it was showing a 301 redirect to one of the other domains on my account. This is curious because I was originally petitioning the primary url.

For some reason a redirect url was what ended up being in the 301 response. When I petitioned the url in the 301 response instead it actually worked. Then from there it actually told me where I had messed up payload parameters.

I still after doing everything I needed to do could not tell you why it redirected me to a redirect url that redirects back to the primary…instead of just letting me use the primary.

Hi, i have the same problem here. i ran through postman for creating customer. this is my postman:

instead of getting 201 (created), i’m getting 200. i have tried the same payload to my trial store and i’m getting the right response with 201 created.

when i tried to console the hit processs. i got the same result as you. the domain got redirected to another domain, which is not my original domain. any idea how to solve this ?
Thanks in advance!