How to create sales order via rest api and tax calculation dynamically n erpnext

When creating a sales order via the REST API, taxes are not being calculated, even though they are correctly assigned in the item master. Tax calculation works fine in the UI but not through the API.
this is json example

{
  "data": {
    "customer": "CUST-2025-00001",
    "delivery_date": "2025-10-04",
    "company": "ABC PVT LTD",
    "taxes_and_charges": "Output GST In-state - APL",
    "items": [
      {
        "item_code": "STO-ITEM-2025-00001",
        "qty": 1,
        "rate": 1000,
        "delivered_by_supplier": 1,
        "supplier": "SUP-SUPOB-MSSW-00001-0001"
      }
    ]
  }
}

What is your API endpoint?

Share your postman request and response if possible

sitename/api/resources/Sales Order
This is my ali url

API Response

{
  "data": {
    "name": "SAL-ORD-2025-00031",
    "owner": "Administrator",
    "creation": "2025-10-06 09:45:59.678736",
    "modified": "2025-10-06 09:45:59.678736",
    "modified_by": "Administrator",
    "docstatus": 0,
    "idx": 0,
    "workflow_state": "Pending",
    "title": "{customer_name}",
    "naming_series": "SAL-ORD-.YYYY.-",
    "customer": "CUST-2025-00001",
    "order_type": "Sales",
    "transaction_date": "2025-10-06",
    "delivery_date": "2025-10-08",
    "company": "ABC PVT LTD",
    "skip_delivery_note": 0,
    "is_reverse_charge": 0,
    "is_export_with_gst": 0,
    "has_unit_price_items": 0,
    "currency": "INR",
    "conversion_rate": 1,
    "selling_price_list": "Standard Selling",
    "price_list_currency": "INR",
    "plc_conversion_rate": 1,
    "ignore_pricing_rule": 0,
    "last_scanned_warehouse": null,
    "reserve_stock": 0,
    "total_qty": 1,
    "total_net_weight": 0,
    "base_total": 1000,
    "base_net_total": 1000,
    "total": 1000,
    "net_total": 1000,
    "tax_category": "In-State",
    "taxes_and_charges": "Output GST In-state - APL",
    "base_total_taxes_and_charges": 0,
    "total_taxes_and_charges": 0,
    "base_grand_total": 1000,
    "base_rounding_adjustment": 0,
    "base_rounded_total": 0,
    "base_in_words": "INR One Thousand only.",
    "grand_total": 1000,
    "rounding_adjustment": 0,
    "rounded_total": 0,
    "in_words": "INR One Thousand only.",
    "advance_paid": 0,
    "disable_rounded_total": 1,
    "apply_discount_on": "Grand Total",
    "base_discount_amount": 0,
    "additional_discount_percentage": 0,
    "discount_amount": 0,
    "gst_breakup_table": null,
    "customer_address": "CUST-2025-00001-Billing",
    "address_display": "s n layout<br>Bangalore<br>\nKarnataka, State Code: 29<br>PIN Code: 560001<br>India<br>\n",
    "gst_category": "Unregistered",
    "place_of_supply": "29-Karnataka",
    "shipping_address_name": "CUST-2025-00001-Billing",
POST<br>BANGALORE<br>\nKarnataka, State Code: 29<br>PIN Code: 560049<br>India<br>\nPhone: +91-8876541242<br>Email: raghavebdra@ms.co<br>",
    "company_address": "ABC PVT LTD-Billing",
    "company_gstin": "29AABCA2138H1ZW",
    "company_address_display": "EMBASSY CHAMBERS 1 5 VITTAL MALLYA ROAD BENGALURU<br>Bangalore<br>\nKarnataka, State Code: 29<br>PIN Code: 560001<br>India<br>\nGSTIN: 29AABCA2138H1ZW<br>",
    "status": "Draft",
    "delivery_status": "Not Delivered",
    "per_delivered": 0,
    "per_billed": 0,
    "per_picked": 0,
    "billing_status": "Not Billed",
    "amount_eligible_for_commission": 1000,
    "commission_rate": 0,
    "supplier_sales_partner_commission_rate": 0,
    "supplier_sales_partner_commission": 0,
    "total_commission": 0,
    "loyalty_points": 0,
    "loyalty_amount": 0,
    "group_same_items": 0,
    "language": "en",
    "is_internal_customer": 0,
    "ecommerce_supply_type": null,
    "doctype": "Sales Order",
    "packed_items": [],
    "taxes": [],
    "sales_team": [],
    "pricing_rules": [],
    "payment_schedule": [
      {
        "name": "6840gai63d",
        "owner": "Administrator",
        "creation": "2025-10-06 09:46:00.470300",
        "modified": "2025-10-06 09:46:00.470300",
        "modified_by": "Administrator",
        "docstatus": 0,
        "idx": 1,
        "due_date": "2025-10-06",
        "invoice_portion": 100,
        "discount": 0,
        "payment_amount": 1000,
        "outstanding": 1000,
        "paid_amount": 0,
        "discounted_amount": 0,
        "base_payment_amount": 1000,
        "base_outstanding": 1000,
        "base_paid_amount": 0,
        "parent": "SAL-ORD-2025-00031",
        "parentfield": "payment_schedule",
        "parenttype": "Sales Order",
        "doctype": "Payment Schedule"
      }
    ],
    "items": [
      {
        "name": "67tt2vg14q",
        "owner": "Administrator",
        "creation": "2025-10-06 09:45:59.678736",
        "modified": "2025-10-06 09:45:59.678736",
        "modified_by": "Administrator",
        "docstatus": 0,
        "idx": 1,
        "item_code": "STO-ITEM-2025-00001",
        "ensure_delivery_based_on_produced_serial_no": 0,
        "is_stock_item": 0,
        "reserve_stock": 0,
        "delivery_date": "2025-10-08",
        "item_name": "Laptop",
        "description": "Laptop",
        "gst_hsn_code": "010130",
        "item_group": "Consumable",
        "image": "",
        "qty": 1,
        "stock_uom": "Nos",
        "uom": "Nos",
        "conversion_factor": 1,
        "stock_qty": 1,
        "stock_reserved_qty": 0,
        "price_list_rate": 1000,
        "base_price_list_rate": 1000,
        "margin_type": "",
        "margin_rate_or_amount": 0,
        "rate_with_margin": 0,
        "discount_percentage": 0,
        "discount_amount": 0,
        "distributed_discount_amount": 0,
        "base_rate_with_margin": 0,
        "rate": 1000,
        "amount": 1000,
        "item_tax_template": "GST 12% - APL",
        "gst_treatment": "Nil-Rated",
        "base_rate": 1000,
        "base_amount": 1000,
        "stock_uom_rate": 1000,
        "is_free_item": 0,
        "grant_commission": 1,
        "net_rate": 1000,
        "net_amount": 1000,
        "base_net_rate": 1000,
        "base_net_amount": 1000,
        "taxable_value": 1000,
        "igst_rate": 0,
        "cgst_rate": 0,
        "sgst_rate": 0,
        "cess_rate": 0,
        "cess_non_advol_rate": 0,
        "igst_amount": 0,
        "cgst_amount": 0,
        "sgst_amount": 0,
        "cess_amount": 0,
        "cess_non_advol_amount": 0,
        "billed_amt": 0,
        "valuation_rate": 0,
        "gross_profit": 1000,
        "delivered_by_supplier": 1,
        "supplier": "SUP-SUPOB-MSSW-00001-0001",
        "weight_per_unit": 0,
        "total_weight": 0,
        "warehouse": "Stores - APL",
        "against_blanket_order": 0,
        "blanket_order_rate": 0,
        "actual_qty": 0,
        "company_total_stock": 0,
        "projected_qty": 0,
        "ordered_qty": 0,
        "planned_qty": 0,
        "production_plan_qty": 0,
        "work_order_qty": 0,
        "delivered_qty": 0,
        "produced_qty": 0,
        "returned_qty": 0,
        "picked_qty": 0,
        "page_break": 0,
        "item_tax_rate": "{\"Output Tax SGST - APL\": 6.0, \"Output Tax CGST - APL\": 6.0, \"Output Tax IGST - APL\": 12.0, \"Output Tax SGST RCM - APL\": -6.0, \"Output Tax CGST RCM - APL\": -6.0, \"Output Tax IGST RCM - APL\": -12.0, \"Output Tax SGST Refund - APL\": -6.0, \"Output Tax CGST Refund - APL\": -6.0, \"Output Tax IGST Refund - APL\": -12.0, \"Input Tax SGST - APL\": 6.0, \"Input Tax CGST - APL\": 6.0, \"Input Tax IGST - APL\": 12.0, \"Input Tax SGST RCM - APL\": 6.0, \"Input Tax CGST RCM - APL\": 6.0, \"Input Tax IGST RCM - APL\": 12.0}",
        "transaction_date": "2025-10-06",
        "cost_center": "Main - APL",
        "parent": "SAL-ORD-2025-00031",
        "parentfield": "items",
        "parenttype": "Sales Order",
        "doctype": "Sales Order Item",
        "__unsaved": 1
      }
    ]
  },
  "_server_messages": "[\"{\\\"message\\\": \\\"Please setup default outgoing Email Account from Tools > Email Account\\\", \\\"title\\\": \\\"Message\\\", \\\"indicator\\\": \\\"red\\\", \\\"raise_exception\\\": 1, \\\"__frappe_exc_id\\\": \\\"6bc2d76b57ec5f9e4579c553723e35e38b4b76ddeca80e7966a0dcc9\\\"}\"]"
}

Get the code,

for_validate is true so condition not met.

Need to investigate.

Hey :wave:,

Typically, the app that is calling the API should know exactly what tax to apply, and thus apply it by itself. Doing the calculation in ERPNext is “risky” because you’re delegating some control that you’re supposed to have when using the API.

If you still don’t want to calculate the tax before, the you will need a Server Script to apply the taxes before saving the document.

Corentin

1 Like

got a solution for this

Create an app in that app create a file and paste it then call that file via post method and fill the details as shown below

import frappe
from frappe import _
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
from erpnext.controllers.accounts_controller import get_taxes_and_charges

@frappe.whitelist(allow_guest=True)
def sales_o(**kwargs):

    data = frappe._dict(kwargs)
    doc = frappe.new_doc("Sales Order")
    doc.customer = data.get("customer")
    doc.company = data.get("company") or frappe.defaults.get_user_default("Company")
    doc.transaction_date = frappe.utils.nowdate()
    doc.delivery_date = data.get("delivery_date") or frappe.utils.add_days(frappe.utils.nowdate(), 2)
    doc.currency = data.get("currency", "INR")
    doc.selling_price_list = data.get("selling_price_list", "Standard Selling")
    doc.tax_category = data.get("tax_category") or "In-State"
    doc.taxes_and_charges = data.get("taxes_and_charges")
    doc.disable_rounded_total = 1

    
    doc.shipping_address_name = data.get("shipping_address_name")
    doc.dispatch_address_name = data.get("dispatch_address_name")

    
    items = data.get("items")
    if not items:
        frappe.throw(_("At least one item is required"))

    for item in items:
        doc.append("items", {
            "item_code": item.get("item_code"),
            "qty": item.get("qty", 1),
            "rate": item.get("rate"),
            "delivered_by_supplier": item.get("delivered_by_supplier", 0),
            "supplier": item.get("supplier"),
            "item_tax_template": item.get("item_tax_template")
        })

    
    customer_gst_category = frappe.db.get_value("Customer", doc.customer, "gst_category")
    if customer_gst_category == "Unregistered":
        doc.gst_treatment = "Unregistered"
    elif customer_gst_category == "Registered Regular":
        doc.gst_treatment = "Registered Regular"
    else:
        doc.gst_treatment = customer_gst_category or "Unregistered"

    
    if doc.taxes_and_charges:
        taxes_list = get_taxes_and_charges(
            master_doctype="Sales Taxes and Charges Template",
            master_name=doc.taxes_and_charges
        )
        
        if isinstance(taxes_list, dict) and taxes_list.get("taxes"):
            for tax in taxes_list["taxes"]:
                doc.append("taxes", tax)

    
    doc.run_method("set_missing_values")
    calculate_taxes_and_totals(doc)

    
    doc.insert(ignore_permissions=True)
    frappe.db.commit()

    return {
        "status": "success",
        "sales_order": doc.name,
        "grand_total": doc.grand_total,
        "gst_treatment": doc.gst_treatment
    }

can customize code as per your requirement
this is Form Format

{
  "doctype": "Sales Order",
  "customer": "CUST-2025-00001",
  "currency": "INR",
  "selling_price_list": "Standard Selling",
  "delivery_date": "2025-10-08",
  "shipping_address_name": "CUST-2025-00001-Billing",
  "dispatch_address_name": "M S Q World-Shipping",
  "disable_rounded_total": 1,
  "taxes_and_charges": "Output GST In-state - APL",
  "tax_category": "In-State",
  "items": [
        {
            
      "item_code": "STO-ITEM-2025-00001",
      "qty": 1,
      "delivered_by_supplier": 1,
      "supplier": "SUP-SUPOB-MSSW-00001-0001",
      "item_tax_template": "GST 12% - APL"
    }
  ]
}

Thank you for providing a step to solve it

allow_guest=True can lead to security problem.

Also no need of manual frappe.db.commit().

If it is just testing then ignore my suggestions

Ok
Thank you for the optimization

Also you might want to add a specific permission check: any user, even any e-commerce customer (website user). It can be as simple and dirty as:

if frappe.session.user != "bot@example.com":
    frappe.throw("invalid user")

or better with frappe.only_for("Specific Role")


For the commit, it’s done automatically for POST requests, so make sure to use the POST method!

Okay
Good suggestion

1 Like