MCP tools
Manual tenders, items and participation
MCP tools for creating manual tenders and managing their items, marking the items you bid on and choosing the product for each.
A manual tender is a tender you enter yourself for a request that never appeared on Etimad or NUPCO. Your organization owns it, no other organization sees it, and it is the only kind of tender these tools can edit or delete. Tenders from Etimad and NUPCO are shared and read-only.
Participation, product selection and supplier prices work on any tender you can see, manual or published, and they belong to the whole organization: what a teammate marks, you see. Tender and item IDs come from tenders_get and tenders_search.
| Tool | What it does |
|---|---|
| tenders_create | Create a manual tender |
| tenders_update | Update a manual tender |
| tenders_delete | Delete a manual tender |
| tender_items_create | Add an item to a manual tender |
| tender_items_update | Update an item |
| tender_items_delete | Remove an item |
| tender_item_participation_list | List participating items |
| tender_item_participation_set_batch | Set participation for many items |
| tenders_product_links_list | List a tender's product links |
| tender_item_product_selection_set | Select a product for an item |
| tender_item_price_levels_set | Set what one unit of many items is |
| tender_item_product_selection_clear | Clear an item's product selection |
| tenders_product_links_auto_link | Link products to items automatically |
| tenders_catalog_pricing | Price a tender from the catalog |
| tenders_create_products_from_items | Create products from items |
| tenders_sourcing_price_add | Record a supplier price for an item |
| tenders_badail_poa_import | Import NUPCO Badail POA requests |
Manual tenders
Three tools create, edit and delete a manual tender. They need the owner or admin role.
tenders_create
Creates a tender you enter by hand, with its items if you send them. Use it for a request that never appeared on Etimad or NUPCO, so that bids, RFQs and quotations can be prepared on it like on any other tender. The tender is private to your organization.
Changes data · scope tenders.write · roles: owner, admin · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_name | string | Yes | The tender's name, 1 to 500 characters. |
agency_name | string | Yes | The government agency that issued the tender, up to 300 characters. |
branch_name | string | No | The agency's branch or department, up to 200 characters. |
tender_number | string | No | The tender number as the agency states it, up to 100 characters. Search finds the tender by this number. |
reference_number | string | No | The reference number, up to 100 characters. Search finds the tender by it too. |
tender_type | string | No | The tender type as free text, up to 100 characters. |
tender_status | string | No | The tender's status as free text, up to 100 characters. |
source_url | string | No | A link to the original announcement, up to 2,000 characters. |
last_enquiries_date | string | No | The last date for questions to the agency. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. |
last_offer_date | string | No | The submission deadline: when the tender closes. The deadline column, the remaining days, the calendar and the alerts all read it. A single date given for a tender goes here, not in submission_date. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. |
offers_opening_date | string | No | When the offers are opened. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. |
submission_date | string | No | The date the tender was published or announced. It is not the deadline, which belongs in last_offer_date. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. |
booklet_price | number | No | The booklet price. In SAR, zero or more. Left out or sent as null, it is stored as 0. |
financial_fees | number | No | The financial fees. In SAR, zero or more. Left out or sent as null, it is stored as 0. |
invitation_cost | number | No | The invitation cost. In SAR, zero or more. Left out or sent as null, it is stored as 0. |
buying_cost | number | No | The buying cost. In SAR, zero or more. Left out or sent as null, it is stored as 0. |
items | object[] | No | The tender's starting items, up to 2,000. Every one is added as a new item, in order. |
items[].id | integer | No | Not used when creating: every item is added as new. |
items[].item_name | string | Yes | The item's name, 1 to 1,000 characters. |
items[].description | string | No | The item's description, up to 4,000 characters. |
items[].quantity | number | No | The quantity requested, zero or more. |
items[].unit | string | No | The unit of measure, up to 50 characters. |
items[].estimated_price | number | No | The estimated unit price in SAR, zero or more. |
items[].material_code | string | No | The NUPCO material code, up to 100 characters. It is how the platform matches the item to catalog products and price history. |
{
"tender_name": "Supply of disposable syringes",
"agency_name": "Eastern Region Health Cluster",
"tender_number": "ERHC-2026-0457",
"last_offer_date": "2026-11-18T12:00:00",
"items": [
{
"item_name": "Syringe 5 ml with needle, sterile",
"quantity": 20000,
"unit": "Piece",
"material_code": "4111610529900"
},
{
"item_name": "Examination gloves, nitrile, medium",
"quantity": 500,
"unit": "Box"
}
]
}Returns: The new tender's id and its web_url, the address of its page in the web app.
- Only the owner and admin roles can create, edit or delete a manual tender. Sales and purchasing members can still add and edit its items with the item tools below.
- A tender is saved with all of its items or not at all: if the items cannot be saved, no tender is created.
tenders_update
Edits a manual tender: its header fields and, when you send items, its whole list of items. A header field you leave out keeps its stored value and null clears it, but the tender name and the agency name are required on every call. Tenders from Etimad and NUPCO cannot be edited.
Changes data · scope tenders.write · roles: owner, admin · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The tender's ID. It must be a manual tender of your organization. |
tender_name | string | Yes | The tender's name, 1 to 500 characters. Required on every call: send the current value when you are not changing it. |
agency_name | string | Yes | The government agency that issued the tender, up to 300 characters. Required on every call: send the current value when you are not changing it. |
branch_name | string | No | The agency's branch or department, up to 200 characters. Send null to clear it. |
tender_number | string | No | The tender number as the agency states it, up to 100 characters. Search finds the tender by this number. Send null to clear it. |
reference_number | string | No | The reference number, up to 100 characters. Search finds the tender by it too. Send null to clear it. |
tender_type | string | No | The tender type as free text, up to 100 characters. Send null to clear it. |
tender_status | string | No | The tender's status as free text, up to 100 characters. Send null to clear it. |
source_url | string | No | A link to the original announcement, up to 2,000 characters. Send null to clear it. |
last_enquiries_date | string | No | The last date for questions to the agency. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. Send null to clear it. |
last_offer_date | string | No | The submission deadline: when the tender closes. The deadline column, the remaining days, the calendar and the alerts all read it. A single date given for a tender goes here, not in submission_date. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. Send null to clear it. |
offers_opening_date | string | No | When the offers are opened. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. Send null to clear it. |
submission_date | string | No | The date the tender was published or announced. It is not the deadline, which belongs in last_offer_date. An ISO 8601 timestamp; a value without a time zone is read as Saudi local time. Send null to clear it. |
booklet_price | number | No | The booklet price. In SAR, zero or more. Left out, it stays as it is; null stores 0. |
financial_fees | number | No | The financial fees. In SAR, zero or more. Left out, it stays as it is; null stores 0. |
invitation_cost | number | No | The invitation cost. In SAR, zero or more. Left out, it stays as it is; null stores 0. |
buying_cost | number | No | The buying cost. In SAR, zero or more. Left out, it stays as it is; null stores 0. |
items | object[] | No | The complete list of items, up to 2,000. It replaces the stored list: an item with an id is updated, one without is added, and any stored item missing from it is removed. Leave it out to keep the items as they are; [] removes them all. |
items[].id | integer | No | The ID of a stored item of this tender, to update it in place. Leave it out to add a new item. An ID that does not belong to this tender is treated as a new item. |
items[].item_name | string | Yes | The item's name, 1 to 1,000 characters. |
items[].description | string | No | The item's description, up to 4,000 characters. Send null to clear it. |
items[].quantity | number | No | The quantity requested, zero or more. Send null to clear it. |
items[].unit | string | No | The unit of measure, up to 50 characters. Send null to clear it. |
items[].estimated_price | number | No | The estimated unit price in SAR, zero or more. Send null to clear it. |
items[].material_code | string | No | The NUPCO material code, up to 100 characters. It is how the platform matches the item to catalog products and price history. Send null to clear it. |
{
"id": 4412,
"tender_name": "Supply of disposable syringes",
"agency_name": "Eastern Region Health Cluster",
"last_offer_date": "2026-11-25T12:00:00"
}Returns: The tender's id and its web_url.
itemsreplaces the list: an item with theidof a stored item is updated in place, an item without anidis added, and every stored item missing from the list is removed ([]removes them all). A removed item is only hidden: its prices, RFQ lines and participation marks are kept, and it comes back when you send itsidagain or call tender_items_update withrestore.- Answers 404 when the tender does not exist or belongs to another organization, and 403 when it is a published tender rather than a manual one.
tenders_delete
Deletes a manual tender permanently, together with its items, bids, notes, RFQs, quotations and product selections. It cannot be undone, and tenders from Etimad and NUPCO cannot be deleted. To drop only some lines, use tender_items_delete.
Deletes or overwrites data · scope tenders.write · roles: owner, admin · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The tender's ID. It must be a manual tender of your organization. |
Returns: A confirmation, success: true.
- Sales orders, purchase orders, deliveries and invoices that mention the tender are kept and lose only the link to it.
- Answers 404 for an unknown tender or another organization's, and 403 for a published one.
Items of a manual tender
Add, change and remove the items of a manual tender one at a time. Removing an item only hides it, so the prices and marks attached to it survive.
tender_items_create
Adds one item to a manual tender. The item number is assigned for you, one above the highest number ever used on the tender, so the number of a removed item is never reused. To send the whole list at once, use tenders_update.
Changes data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | Yes | The tender's ID. It must be a manual tender of your organization. |
item_name | string | Yes | The item's name, 1 to 1,000 characters. |
description | string | No | The item's description, up to 4,000 characters. |
quantity | number | No | The quantity requested, zero or more. |
unit | string | No | The unit of measure, up to 50 characters. |
estimated_price | number | No | The estimated unit price in SAR, zero or more. |
material_code | string | No | The NUPCO material code, up to 100 characters. It is how the platform matches the item to catalog products and price history. |
{
"tender_id": 4412,
"item_name": "IV cannula 20G, sterile",
"quantity": 3000,
"unit": "Piece",
"material_code": "4111610530118"
}Returns: The saved item as item, with its id and item_number.
- Answers 404 when the tender does not exist or belongs to another organization, and 403 when it is a published tender rather than a manual one.
tender_items_update
Changes one item of a manual tender. Only the fields you send change, and null clears an optional one. Send restore: true to bring back an item that was removed.
Changes data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The item's ID, from tenders_get. |
item_name | string | No | The new name, 1 to 1,000 characters. It cannot be cleared. |
description | string | No | The item's description, up to 4,000 characters. Send null to clear it. |
quantity | number | No | The quantity requested, zero or more. Send null to clear it. |
unit | string | No | The unit of measure, up to 50 characters. Send null to clear it. |
estimated_price | number | No | The estimated unit price in SAR, zero or more. Send null to clear it. |
material_code | string | No | The NUPCO material code, up to 100 characters. It is how the platform matches the item to catalog products and price history. Send null to clear it. |
restore | boolean | No | true puts an item that was removed back on the tender's list. |
{
"id": 902114,
"quantity": 3500,
"estimated_price": 1.8
}Returns: The item after the change, as item.
- A call that names no field to change is refused with 400.
- Answers 404 when the item does not exist or its tender belongs to another organization, and 403 when its tender is a published one rather than a manual one.
tender_items_delete
Removes an item from a manual tender's list by hiding it; the item is not erased. Its prices, RFQ lines and participation marks are kept. To undo, use tender_items_update with restore: true.
Deletes or overwrites data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The item's ID. |
Returns: A confirmation, success: true.
- Answers 404 when the item does not exist or its tender belongs to another organization, and 403 when its tender is a published one rather than a manual one.
Participation
Participation says which items your organization bids on. It is shared by the whole organization and decides what the bid files contain. These tools work on any tender you can see.
tender_item_participation_list
Lists the items your organization has marked as participating, newest first, for one tender or for all of them. The marks are the whole organization's, whoever set them.
Read-only · scope tenders.read · all roles · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | No | Only marks on this tender's items. Left out, the marks of every tender come back. |
Returns: A list of marks, each with id, tender_id, tender_item_id and created_at.
- The list is not paged. Pass
tender_idto narrow it to one tender.
tender_item_participation_set_batch
Marks or clears participation on up to 500 items of one tender in a single call. Use it to mark a whole tender or to drop a group of items at once.
Changes data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | Yes | The tender's ID. |
item_ids | integer[] | Yes | The item IDs, from 1 to 500. A repeated ID counts once. |
participating | boolean | Yes | true marks the items as participating, false removes the mark. |
{
"tender_id": 48213,
"item_ids": [
902114,
902115,
902118
],
"participating": true
}Returns: updated, the IDs of the items the change was applied to, and failed, the IDs that are not live items of the tender.
- The valid items are saved even when some fail, so check
failedafter every call. - Answers 404 when the tender is not visible to you. A key whose creator was deleted is refused with 403 when marking.
Products for items
A product is a candidate for a NUPCO material code when it is linked to that code in your catalog. A selection is the one product your organization will supply for a particular item.
tenders_product_links_list
Shows, for one tender, which catalog products are candidates for each NUPCO material code on its items and which product your organization has selected for each item. Run it before auto-linking or creating products, to see what is already linked.
Read-only · scope tenders.read · all roles · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The tender's ID. |
Returns: links: one entry per material code that has candidates (nupco_code and products, each with id, product_name, description, nupco_product_code, manufacturers, is_default and packing), and selections: one entry per item with a selection (tender_item_id, product_id, product_name, catalogue_number, archived, packing, the item's stored level, level and conversion). Each candidate and selection also carries the product's catalog cost (cost_price in cost_currency, null when none is recorded) when your role may see cost pricing, and each selection then carries cost_sar_per_tender_unit too. They carry the sale price (sale_price_sar) when your role may see bid pricing, and each selection then carries sale_price_per_tender_unit too.
- A code with no candidate does not appear in
links.archived: truemarks a selected product that has since been moved to the trash. packingis the product's levels: the unit (unit_of_measure), the pack (package_type, holdingpackage_sizeunits), the case (case_type, holdingunits_per_caseunits) and what one unit holds (content_quantityofcontent_unit), withprice_basis, the level its cost and sale prices are stated per.nullmeans not stated: the price is used as is.levelsays what one unit of the item is of the selected product: the levellevel(unit,pack,caseorcontent) timescount, inmeasurefor content. Itsstatusisset(chosen with tender_item_price_levels_set or tender_item_product_selection_set),matched(read from the item's unit, name and description) orcheck(unsure: priced at one unit of the product until checked, with asuggestion).stated_countis the pack count the item's description states, if any.- The item's stored level is
price_level,price_level_count,price_level_measureandprice_level_product_id;nullmeans automatic, and a stored level applies only whileprice_level_product_idis the selected product.sale_price_per_tender_unitis the product's sale price for one unit of the item, to four decimals, which is what tenders_catalog_pricing bids, andcost_sar_per_tender_unitis the product's cost in SAR for one unit of the item.conversionisnullwhen the product's price can be converted to the item's unit, elseno_conversionwithmissing(package_size,units_per_case,content_quantityormeasure_family): fill the count with products_update or set the item's level. - Candidates come from the links between products and NUPCO codes, which product_nupco_link and product_nupco_unlink add and remove.
tender_item_product_selection_set
Chooses the one catalog product your organization will supply for a tender item, replacing any earlier choice. It also links the product to the item's NUPCO material code, so the product shows up as a candidate wherever the code recurs.
Changes data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | Yes | The tender's ID. |
tender_item_id | integer | Yes | The item's ID. It must be a live item of the tender you name. |
product_id | integer | Yes | The ID of a product in your organization's catalog that is not in the trash, from products_list. |
price_level | string | No | What one unit of the item is of this product: unit (one unit of the product), pack (one pack of package_size units), case (one case of units_per_case units) or content (one content_unit of what a unit holds), times price_level_count. Left out, it stays as it is while the product stays the same and returns to automatic when the product changes. null returns it to automatic. |
price_level_count | number | No | How many of price_level make one unit of the item, from 0.0001 to 1,000,000 with at most four decimals. Defaults to 1, and is sent only with price_level. |
price_level_measure | string | No | With content only: the measure one unit of the item is counted in when it differs from the product's content_unit but is of the same kind, such as l for a product whose unit holds 500 ml. Left out, the product's own content_unit is used. |
{
"tender_id": 48213,
"tender_item_id": 902114,
"product_id": 3071,
"price_level": "pack"
}Returns: The saved selection: id, tender_id, tender_item_id, product_id, nupco_code and the stored level (price_level, price_level_count, price_level_measure and price_level_product_id), plus level, what one unit of the item is as the server reads it (as in tenders_product_links_list).
- Answers 400 with
product_not_foundwhen the product is not a live product of your catalog, 400 withitem_not_foundwhen the item is not a live item of the tender, and 404 when the tender is not visible to you. - Answers 400 with
level_missingwhen the product lacks the count the level needs, withfieldnaming the field to fill with products_update. A level never changes a saved price: it changes what the product's price and cost convert to when pricing from the catalog or by margin. For many items at once, use tender_item_price_levels_set.
tender_item_price_levels_set
Sets, for up to 500 items of one tender, what one unit of each item is of the product selected for it, or returns it to automatic. It changes neither the product nor a saved price: it changes what the product's price and cost convert to when pricing from the catalog or by margin.
Changes data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_id | integer | Yes | The tender's ID. |
items | object[] | Yes | The items, from 1 to 500, each item once. |
items[].tender_item_id | integer | Yes | The item's ID. |
items[].price_level | string | Yes | What one unit of the item is of its selected product: unit, pack, case or content, times price_level_count. null returns it to automatic. |
items[].price_level_count | number | No | How many of price_level make one unit of the item, from 0.0001 to 1,000,000 with at most four decimals. Defaults to 1. |
items[].price_level_measure | string | No | With content only: the measure one unit of the item is counted in when it differs from the product's content_unit but is of the same kind. |
{
"tender_id": 48213,
"items": [
{
"tender_item_id": 902114,
"price_level": "pack"
},
{
"tender_item_id": 902115,
"price_level": "unit",
"price_level_count": 2
},
{
"tender_item_id": 902118,
"price_level": null
}
]
}Returns: updated: the items whose level was saved, each with tender_item_id, product_id, price_level, price_level_count, price_level_measure and price_level_product_id. failed: the items not saved, each with tender_item_id, reason and error. With updated_count and failed_count.
- Failure reasons:
item_not_foundfor an item that is not a live item of the tender,no_selectionfor an item with no selected product (select one with tender_item_product_selection_set, which takes the level too),product_not_foundfor an item whose selected product is no longer in the catalog (select a live one),level_missingfor a level whose count the product lacks, withfieldnaming the field to fill with products_update, andselection_changedfor an item whose product changed during the call. - The valid items are saved even when some fail, and a repeated
tender_item_idrejects the whole request. After setting the units, run tenders_catalog_pricing withreplace_existing: trueto re-price. - Answers 404 when the tender is not visible to you.
tender_item_product_selection_clear
Removes the product your organization chose for an item. The link between the product and the item's NUPCO code stays, so the product remains a candidate for that code.
Deletes or overwrites data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
tender_item_id | integer | Yes | The item's ID. |
Returns: A confirmation, success: true.
- The call succeeds even when the item had no selection.
tenders_product_links_auto_link
Selects a product for every item where the choice is obvious: the item's NUPCO code has exactly one candidate product in your catalog. It never overwrites a selection, leaves codes with several candidates alone, and can safely be run again.
Changes data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The tender's ID. |
Returns: linked, the number of selections made, and selections, each with tender_item_id, product_id and nupco_code.
- Removed items, items without a material code and items that already have a selection are skipped. For items with several candidates, choose yourself with tender_item_product_selection_set.
tenders_catalog_pricing
Prices every item your catalog has a product for at the product's sale price (sale_price_sar) converted to the item's unit, and marks the item participating. It is the tender page's "Price from catalog" button.
Changes data · scope bids.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The tender's ID. |
dry_run | boolean | No | true reports what would be priced and writes nothing. Defaults to false. |
replace_existing | boolean | No | true also re-prices items that already have a price, a zero included. Defaults to false, which keeps every price already entered. |
{
"id": 48213,
"dry_run": true
}Returns: priced: the items priced (or that would be, on a dry run), each with tender_item_id, product_id, unit_price (for one unit of the item), source (selected, only, default or lowest), catalog_price (the sale price as in the catalog), basis, price_level, level_count, level_measure, level_status, level_reason, suggestion and factor. unit_check lists the priced items whose unit is unsure, already_priced counts the items whose price was kept, skipped counts the items left alone by reason (ambiguous, no_sale_price, no_product, no_conversion, rounds_to_zero), skipped_items lists them, messages says what to do about each case, and lowest_price_allowed says which rule applied.
- The product for each item: the one selected for the item, else the only product linked to the item's NUPCO code, else the code's default product (set with product_nupco_link and
is_default: true), else the lowest sale price among the code's products, unless the owner turned that off in organization settings (it is on by default). Anything else is left asambiguous. - A product with no sale price is left as
no_sale_priceand never swapped for another. A product the call picks becomes the item's selected product. - The catalog sale price is stated per the product's
price_basisand is converted to what one unit of the item is, rounded to four decimals: the level set for the item, else the one read from the item's unit, name and description. A product with noprice_basisis priced as is. The lowest price is compared after conversion, among the products whose unit for the item is sure first. - An item whose unit is unsure is priced at one unit of the product and listed in
unit_checkwith a suggestion: check it, set its unit with tender_item_price_levels_set, then re-price withreplace_existing: true. An item whose price cannot be converted because a packing count is missing (no_conversion, withmissing) or rounds to 0.0000 (rounds_to_zero) is left alone. - Run it with
dry_run: truefirst and show the person the result, then run it for real once they agree.
tenders_create_products_from_items
Creates one catalog product for each item you name, taking the product's name, description and NUPCO code from the item, and selects it as that item's product. It is how an item with no NUPCO code gets a linked product. Every call creates new products.
Changes data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The tender's ID. |
item_ids | integer[] | Yes | The item IDs, from 1 to 500. Each item becomes one product. The tool takes no product text: the product's data comes from the item alone. |
{
"id": 48213,
"item_ids": [
902114,
902115
]
}Returns: created: the products made, each with tender_item_id, product_id, product_name, description and nupco_code, and created_count with their number.
- It is not safe to repeat: running it twice creates duplicate products. Check tenders_product_links_list first.
- It replaces any product already selected for these items. The earlier product stays in your catalog and stays a candidate for the code.
- The whole request is checked before anything is created. It answers 400 with
items_not_foundwhen an ID is not a live item of the tender, 400 withitem_unnamedfor an item with no name, and 402 withplan_limit_reached(pluslimitandcurrent) when the products would pass your plan's limit. - If the reply carries
selections_failed: true, the products were created but could not be selected for their items. Do not run it again; select them with tender_item_product_selection_set.
Supplier prices
Record what a supplier quoted for an item without sending an RFQ. For full RFQs, see RFQs and supplier quotes.
tenders_sourcing_price_add
Records the price a supplier quoted you for one tender item, typed in by hand with no RFQ sent. Name the supplier by manufacturer_id from your directory or by supplier_name. Each call adds a new quote, and the quote joins your sourcing history for items with the same product or NUPCO code on other tenders.
Changes data · scope tenders.write · roles: owner, admin, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The tender's ID. |
tender_item_id | integer | Yes | The item's ID. It must be a live item of this tender. |
unit_price | number | Yes | The unit price as the supplier quoted it, in the supplier's currency, zero or more. Do not convert it yourself. |
currency | string | No | The three-letter currency code (ISO 4217), such as SAR, EUR or CNY. Defaults to USD. The supported currencies are listed by fx_rates_get. |
fx_rate | number | No | Units of the currency per 1 USD. Left out, the platform picks a default: USD and SAR are fixed (1 and 3.75), currencies pegged to the dollar use their peg, and the rest use the latest European Central Bank rate. A currency with no known rate (such as KWD or EGP) needs this field. |
manufacturer_id | integer | No | The ID of the manufacturer in your directory the quote came from, from manufacturers_list. Send null to leave it empty. |
supplier_name | string | No | The supplier's name as free text, up to 200 characters. Required when you do not send manufacturer_id. Leave the field out rather than sending null. |
supplier_company | string | No | The supplier's company, up to 200 characters. |
supplier_email | string | No | The supplier's email address. It must be a valid address, up to 320 characters. |
supplier_phone | string | No | The supplier's phone number, up to 50 characters. |
incoterms | string | No | The delivery term, such as CIF or EXW, up to 100 characters. |
lead_time_days | integer | No | The lead time in days, from 0 to 3,650. Send null to leave it empty. |
catalog_no | string | No | The supplier's catalog number, up to 100 characters. Send null to leave it empty. |
weight_kg | number | No | The unit weight in kilograms, zero or more. Send null to leave it empty. |
units_per_case | integer | No | Units per case, a positive whole number. Send null to leave it empty. |
product_id | integer | No | The ID of the catalog product the quote is for, from products_list. Send null to leave it empty. |
notes | string | No | Notes on the quote, up to 2,000 characters. Send null to leave them empty. |
{
"id": 48213,
"tender_item_id": 902114,
"unit_price": 0.34,
"currency": "EUR",
"supplier_name": "Medline Europe GmbH",
"incoterms": "CIF",
"lead_time_days": 60,
"units_per_case": 1000
}Returns: The recorded quote: rfq_id (the tender's sourcing record that holds the quote), response_id, supplier_name, currency and fx_rate.
- The price is kept as the supplier quoted it, in its own currency, with the exchange rate used when you recorded it. Comparisons run in USD and show the original price beside it.
- Either
manufacturer_idorsupplier_nameis required, and one is enough. The supplier's optional text fields are left out when unused, never sent asnull. - Answers 400 with
invalid_itemswhen the item is not a live item of this tender, and 400 withmanufacturer_not_foundwhen the manufacturer is not in your directory. An unsupported currency, or one with no rate, answers 400 with a message that names what to send. - A key whose creator was deleted is refused with 403.
The Badail POA import
The one tool on this page that writes outside your organization: it creates shared NUPCO tenders that every organization sees.
tenders_badail_poa_import
Reads the Pay On Account (POA) export of the Badail program downloaded from the NUPCO portal and creates one NUPCO tender per POA request, with the request's products as items. The tenders join the shared NUPCO list that every organization sees, so the tool is limited to platform admins and members granted Etimad collection access. The server reads the whole workbook: send the file itself, never rows you copied from it.
Changes data · scope tenders.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans
| Parameter | Type | Required | Description |
|---|---|---|---|
content_base64 | string | No | One .xlsx file downloaded from the POA Requests list in the NUPCO portal, encoded as base64. It may be up to 5 MB once decoded, and the server refuses a request larger than about 4.5 MB. Always send the real file. Send this or files, never both. |
filename | string | No | The original name of the content_base64 file, up to 300 characters. The reply names rows by it; it does not affect the import. |
files | object[] | No | Several exports imported together instead of content_base64, from 1 to 20, read in the order sent. Together they may be up to 5 MB once decoded. |
files[].content_base64 | string | Yes | The .xlsx file, encoded as base64. Always send the real file. |
files[].filename | string | No | The original file name, up to 300 characters. The reply names the file by it; left out, files are called file 1, file 2 and so on. |
statuses | string[] | No | The portal statuses to import, such as PAY_ON_ACCOUNT_BY_BADAIL, up to 50, matched without regard to letter case. Left out, every row is imported. |
dry_run | boolean | No | true reports what would be created or updated and writes nothing. Defaults to false. |
{
"content_base64": "UEsDBBQABgAIAAAAIQ...",
"filename": "poaRequest_12.xlsx",
"statuses": [
"PAY_ON_ACCOUNT_BY_BADAIL"
],
"dry_run": true
}Returns: The counters created, updated, failed and dry_run; requests, one per POA request with poa_request, tender_id, outcome (one of created, updated, would_create, would_update, failed), items, deadline, status, file and web_url; skipped and warnings, each with the sheet row, its file and a message; filtered_out and status_counts, the number of requests per portal status; and files, each file with the number of requests taken from it.
- Each item's material code is the first half of the portal's generic code (
4111610529900_4111610529905becomes4111610529900). The request date is read as day-month-year. The deadline,last_offer_date, is the end of the fifth Saudi working day (Sunday to Thursday) after the request day; public holidays are not counted. - Importing a newer file again updates the requests already on file in their header only (status, customer, purchase order and name). It never touches their items or dates, so the prices and quotations built on them stay intact.
- Rows missing the request number, the customer name, a valid date or the generic code, and repeated request numbers, are listed in
skippedand nothing is imported from them. Rows whose packed lists do not line up are imported with a note inwarnings. - With several files, a request that appears more than once is taken from the first file it appears in, and its repeats in later files are listed in
skipped, naming that first file. A file that cannot be read refuses the whole import with a 422 naming it, before anything is written. - Besides the scope, whoever created the key or connection must be a platform admin or hold Etimad collection access; the organization role does not grant it, and without it the call is refused with 403. A file that is not a valid POA export answers 422.