restdynamics-business-centraldynamics-al

How to properly format a POST request to a custom business central API


I have a business central API page. When I POST to it, I get a 400 error. Here is the AL code for the page:

page 50115 PunchoutReceiverAPI
{
    PageType = API;
    APIPublisher = 'Contoso';
    APIGroup = 'Punchout';
    APIVersion = 'v2.0';
    ApplicationArea = All;
    Caption = 'Punchout Receiver API';
    EntityName = 'PunchoutReceiver';
    EntitySetName = 'PunchoutReceivers';
    EntityCaption = 'Punchout Receiver';
    EntitySetCaption = 'Punchout Receivers';
    SourceTable = PunchoutReceiver;

    DelayedInsert = true;
    InsertAllowed = true;

    layout
    {
        area(content)
        {
            repeater(Group)
            {
                field(Payload; Rec.Payload)
                {
                    Caption = 'Payload';
                }
            }
        }
    }

    actions
    {

    }

}

and here is the cXML message I am trying to send

"Payload": "<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.014/cXML.dtd">
<cXML xml:lang="en-US" payloadID="933695160894" timestamp="2025-06-30T19:55:33.520Z">
  <Header>
    <From>
      <Credential domain="DUNS">
        <Identity>83528721</Identity>
      </Credential>
    </From>
    <To>
      <Credential domain="DUNS">
        <Identity>65652314</Identity>
      </Credential>
    </To>
    <Sender>
      <Credential domain="workchairs.com">
        <Identity>website 1</Identity>
      </Credential>
      <UserAgent>Zilch cXML application</UserAgent>
    </Sender>
  </Header>
  <Message>
    <PunchOutOrderMessage>
      <BuyerCookie>E281C149319B44BE96115813B382676B</BuyerCookie>
      <PunchOutOrderMessageHeader operationAllowed="edit">
        <Total>
          <Money currency="USD">140.00</Money>
        </Total>
      </PunchOutOrderMessageHeader>
      <ItemIn quantity="2">
        <ItemID>
          <SupplierPartID>1</SupplierPartID>
        </ItemID>
        <ItemDetail>
          <UnitPrice>
            <Money currency="USD">10.00</Money>
          </UnitPrice>
          <Description xml:lang="en">Product 1</Description>
          <UnitOfMeasure>EA</UnitOfMeasure>
        </ItemDetail>
      </ItemIn>
      <ItemIn quantity="2">
        <ItemID>
          <SupplierPartID>3</SupplierPartID>
        </ItemID>
        <ItemDetail>
          <UnitPrice>
            <Money currency="USD">30.00</Money>
          </UnitPrice>
          <Description xml:lang="en">Product 3</Description>
          <UnitOfMeasure>EA</UnitOfMeasure>
        </ItemDetail>
      </ItemIn>
      <ItemIn quantity="3">
        <ItemID>
          <SupplierPartID>2</SupplierPartID>
        </ItemID>
        <ItemDetail>
          <UnitPrice>
            <Money currency="USD">20.00</Money>
          </UnitPrice>
          <Description xml:lang="en">Product 2</Description>
          <UnitOfMeasure>EA</UnitOfMeasure>
        </ItemDetail>
      </ItemIn>
    </PunchOutOrderMessage>
  </Message>
</cXML>"

I dealt with CORS issues, then a 400 error, then a 401 error, then a 404 error, and now, again, a 400 error. This means that the CORS policy is correct, the basic structure of the payload is correct, the authentication is correct and the endpoint is correct. I am trying to POST to this endpoint: https://api.businesscentral.dynamics.com/v2.0/<user-domain-name>/api/Contoso/Punchout/v2.0/companies(<company-id>)/PunchoutReceiver. When I POST using postman I also get a 400 error. I have ensured that the table fields and JSON fields match. Here is the BC table and the POSt request in my flask backend, respectively:

table 50102 PunchoutReceiver
{
    DataClassification = ToBeClassified;

    fields
    {
        field(1; payloadid; Integer)
        {
            DataClassification = ToBeClassified;
            AutoIncrement = true;
            InitValue = 1;
        }
        field(2; Payload; Text[2048])
        {
            DataClassification = ToBeClassified;
        }
    }

    keys
    {
        key(PK; payloadid)
        {
            Clustered = true;
        }
    }

}
      external_payload = {
        "Payload": "this is the data"
      }
      external_url = "https://api.businesscentral.dynamics.com/v2.0/******************/api/Contoso/Punchout/v2.0/companies(*****************)/PunchoutReceiver"

      access_token = get_BC_token()

      response = requests.post(
        external_url, 
        json=external_payload,
        headers={
           "Authorization": f"Bearer {access_token}",
           'Content-Type': 'application/json'
           },
        timeout=30
      )

I have also tried including the payloadid in the post request, but to no avail. If anyone could point me in the right direction I would very much appreciate that. Thank you in advance.


Solution

  • The issue here was in fact the endpoint. Instead of

    https://api.businesscentral.dynamics.com/v2.0/<user-domain-name>/api/Contoso/Punchout/v2.0/companies(<company-id>)/<EntityName>
    

    it is

    https://api.businesscentral.dynamics.com/v2.0/<tenant id>/<environment name>/api/<Api publisher>/<Api group>/v2.0/companies(<company ID>)/<EntitySetName>
    

    If after this you receive a 403 forbidden error, simply generate a permission set within vs code that gives your add-on application full control over its tables/pages/data etc and go into the BC UI and search entra -> microsoft entra application -> select the application you are working on -> under permission sets, add the one you just created in vs code.