Browse the documentation

MCP tools

Photos, attachments and spreadsheet imports

MCP tools for product photos and attachments, manufacturer attachments, your organization's logo, stamp and signature, and importing records from a spreadsheet.

This page gathers the tools that handle files: product photos and attachments, manufacturer attachments, your organization's logo and stamp, and your own signature. It also holds sheet_import, the tool that turns a spreadsheet into records.

A file never travels inside a tool call. Every upload is two steps: the start tool, such as product_photo_upload_url_create, checks the record and the declared file and returns a temporary upload link, upload_url, to which you send the file's bytes directly with a PUT, from a browser or any program. Then the matching finalize tool, such as product_photo_upload_finalize, checks the file's real size and type, processes an image where needed, and files it on its record. So no request size limits the file, and its bytes never pass through the agent's context.

const blob = await (await fetch(SOURCE_URL)).blob();
await fetch(upload_url, { method: "PUT", headers: { "Content-Type": blob.type }, body: blob });
Uploading a file from a browser to the link the start tool returned. The link takes one upload and expires after two hours.

A file the platform already holds, such as one sent to the WhatsApp assistant, is not uploaded at all: send its reference in the staged_ref field to the start tool, which copies it into place inside the server, then call the finalize tool straight away. That is how a full catalog of up to 100 MB gets filed.

FilesAccepted formatsLargest size
Product photosJPEG, PNG, WebP, GIF; AVIF and TIFF are converted to JPEG or PNG5 MB
Product and manufacturer attachmentsPDF, images (JPEG, PNG, WebP, GIF), Word documents, Excel workbooks, PowerPoint decks, plain text, CSV15 MB uploaded, up to 100 MB with staged_ref
Logo, stamp and signaturePNG, JPEG, WebP, SVG; GIF, AVIF and TIFF are converted to JPEG or PNG2 MB
Note:What you file under sfda_cert and catalog on a product is what the product document on quotations and the NUPCO attachments file draw from, and a manufacturer's authorization letter rides with its products in submission packs. File each document once instead of attaching it to every quotation.
ToolWhat it does
product_photo_listList product photos
product_photo_upload_url_createStart a product photo upload
product_photo_upload_finalizeFinish a product photo upload
product_photo_deleteDelete a product photo
product_attachment_upload_url_createStart a product attachment upload
product_attachment_upload_finalizeFinish a product attachment upload
product_attachment_deleteDelete a product attachment
product_files_copyCopy a product file to other products
manufacturer_attachment_listList manufacturer attachments
manufacturer_attachment_upload_url_createStart a manufacturer attachment upload
manufacturer_attachment_upload_finalizeFinish a manufacturer attachment upload
manufacturer_attachment_deleteDelete a manufacturer attachment
sales_order_document_upload_url_createStart a sales order document upload
sales_order_document_upload_finalizeFinish a sales order document upload
sales_order_document_deleteDelete a sales order document
settings_logo_upload_url_createStart an organization logo upload
settings_logo_upload_finalizeFinish an organization logo upload
settings_logo_deleteDelete the organization logo
settings_stamp_upload_url_createStart an organization stamp upload
settings_stamp_upload_finalizeFinish an organization stamp upload
settings_stamp_deleteDelete the organization stamp
settings_signature_getGet my signature
settings_signature_upload_url_createStart a signature upload
settings_signature_upload_finalizeFinish a signature upload
settings_signature_deleteDelete my signature
sheet_importCreate records from a spreadsheet

Product photos

A product's photos show in the product list and on the printed quotation lines linked to it. In the app, see Photos and files.

product_photo_list

Lists a product's photos, each with a path and a temporary link to view it. Use it to find the path that product_photo_delete needs, since `products_get` returns the links alone.

Read-only · scope products.read · all roles · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe product's ID, from products_list.

Returns: A list of {path, url} pairs, one per photo; each url is a link valid for about an hour. A product without photos gives an empty list.

  • A product that your organization does not own, or that sits in the trash, answers 404.

product_photo_upload_url_create

Starts adding a photo to a catalog product: checks the product and the declared file, then returns a temporary upload link to send the photo to directly with a PUT. After the upload, call product_photo_upload_finalize. Every photo is added without replacing the others.

Changes data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe product's ID, from products_list.
filenamestringNoThe original file name. Required unless you send staged_ref, in which case the held file's own name is used unless you rename it.
content_typestringNoThe file's MIME type: image/jpeg, image/png, image/webp or image/gif; image/avif and image/tiff are accepted and converted on finalize. Required unless you send staged_ref.
size_bytesintegerNoThe file's size in bytes, if known, so a file over the limit is refused here instead of after the upload.
staged_refstringNoA reference to a file the platform already holds for your organization, such as one sent to the WhatsApp assistant, listed by `whatsapp_attachments_list`. The file is copied into place inside the server, so there is nothing to upload: upload_url comes back empty and you go straight to the finalize tool. Send it instead of filename and content_type.
{
  "id": 1187,
  "filename": "syringe-10ml-front.jpg",
  "content_type": "image/jpeg",
  "size_bytes": 482113
}
Example arguments

Returns: upload_id, upload_url, method (PUT), headers and expires_at (status 201). With staged_ref, upload_url is empty and staged is true.

  • The limit is 5 MB. A product that your organization does not own, or that sits in the trash, answers 404.
  • The link takes one upload and expires at expires_at, two hours after it was created. Nothing is filed before the finalize tool runs, and an upload not finalized within 24 hours is deleted along with its file.
  • An unsupported type answers 400 and a declared size over the limit answers 413; neither creates a link.
  • Each organization, every API key and connector together, may hold at most 20 uploads open at once (started, not yet finalized, link unexpired) and start at most 500 a day (UTC), unless ProcDuck has set the organization other limits. Past either, the start tool answers 429 with code: "upload_limit_reached" and retry_after_seconds: finalize the uploads whose file you sent, or wait that long. A link never used gives its place back when it expires.

product_photo_upload_finalize

Finishes an upload started with product_photo_upload_url_create: checks the photo's real size and type, converts, trims or downscales it where needed, then adds it to the product.

Changes data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
upload_idstringYesThe upload_id the matching start tool returned.
trim_whitespacebooleanNoCrops near-white or transparent borders around the image, leaving padding of about 2% of the content's size. An SVG is left as it is.
max_widthintegerNoDownscales the image to at most this many pixels wide, keeping its proportions; it never enlarges one. From 16 to 10000.
{
  "upload_id": "5b0f3c1e-8a4d-4f6b-9e2a-7c1d3e5f9a02",
  "trim_whitespace": true,
  "max_width": 1600
}
Example arguments

Returns: The new photo's path and url (a link valid for about an hour), with content_type, size_bytes, width, height, converted_from when the format was converted, trimmed and resized. Keep path to delete or copy the photo later.

  • An image in a format outside the allowed ones, such as AVIF or TIFF, is converted to JPEG at quality 85, or to PNG when it has transparency. An image that needs no conversion, trim or downscale is stored exactly as uploaded. HEIC is not accepted; export it as JPEG or PNG.
  • The server reads the file itself rather than trusting the declared type: a file that is not an accepted type, or is over the limit, is deleted and answers 422 or 413 with the reason, so start a new upload.
  • Each upload_id finalizes once. An unknown one, or one from another organization, answers 404; an expired one answers 410; one already used answers 409.
  • Calling finalize before the upload answers 409 without spending it: upload the file to upload_url, then call it again.

product_photo_delete

Permanently deletes one photo of a product. This cannot be undone.

Deletes or overwrites data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe product's ID.
pathstringYesThe photo's path, from product_photo_list or from the reply of product_photo_upload_finalize.

Returns: A confirmation (success: true).

  • A path that is not one of this product's photos answers 400, and a product your organization does not own answers 404.

Product attachments

A product's documents are filed in three categories: sfda_cert, catalog and other. The list of attachments with their paths comes from `products_get`, in its attachments array.

product_attachment_upload_url_create

Starts filing a document on a catalog product: its SFDA certificate, its catalog or another file. Returns a temporary upload link to send the file to with a PUT, or copies a file the platform already holds when you send staged_ref. Then call product_attachment_upload_finalize.

Changes data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe product's ID, from products_list.
categorystringYesThe attachment's category: sfda_cert for the SFDA certificate, catalog for the catalog or datasheet, other for any other file.
filenamestringNoThe original file name. Required unless you send staged_ref, in which case the held file's own name is used unless you rename it.
content_typestringNoThe file's MIME type; required unless you send staged_ref. Accepted: application/pdf, images (image/jpeg, image/png, image/webp, image/gif), Office documents (Word, Excel, PowerPoint, old and OpenXML formats), text/plain and text/csv.
size_bytesintegerNoThe file's size in bytes, if known, so a file over the limit is refused here instead of after the upload.
staged_refstringNoA reference to a file the platform already holds for your organization, such as one sent to the WhatsApp assistant, listed by `whatsapp_attachments_list`. The file is copied into place inside the server, so there is nothing to upload: upload_url comes back empty and you go straight to the finalize tool. Send it instead of filename and content_type.
{
  "id": 1187,
  "category": "catalog",
  "filename": "syringe-catalog-2026.pdf",
  "content_type": "application/pdf",
  "size_bytes": 2864117
}
Example arguments

Returns: upload_id, upload_url, method (PUT), headers and expires_at (status 201). With staged_ref, upload_url is empty and staged is true.

  • The limit is 15 MB for an uploaded file and 100 MB for a referenced one. A product that your organization does not own, or that sits in the trash, answers 404.
  • The link takes one upload and expires at expires_at, two hours after it was created. Nothing is filed before the finalize tool runs, and an upload not finalized within 24 hours is deleted along with its file.
  • An unsupported type answers 400 and a declared size over the limit answers 413; neither creates a link.
  • Each organization, every API key and connector together, may hold at most 20 uploads open at once (started, not yet finalized, link unexpired) and start at most 500 a day (UTC), unless ProcDuck has set the organization other limits. Past either, the start tool answers 429 with code: "upload_limit_reached" and retry_after_seconds: finalize the uploads whose file you sent, or wait that long. A link never used gives its place back when it expires.

product_attachment_upload_finalize

Finishes an upload started with product_attachment_upload_url_create: checks the file's real size and type, then files it on the product under the category chosen at the start.

Changes data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
upload_idstringYesThe upload_id the matching start tool returned.
{
  "upload_id": "0e7b2d94-3c51-4a8f-b6d2-9f4e1a7c3b58"
}
Example arguments

Returns: The new attachment's path, url (a link valid for about an hour) and name, with content_type and size_bytes. The file is stored under the type its contents prove.

  • A staged_ref is used once: after a successful finalize the held copy is discarded, and if the file is refused the reference becomes usable again.
  • The server reads the file itself rather than trusting the declared type: a file that is not an accepted type, or is over the limit, is deleted and answers 422 or 413 with the reason, so start a new upload.
  • Each upload_id finalizes once. An unknown one, or one from another organization, answers 404; an expired one answers 410; one already used answers 409.
  • Calling finalize before the upload answers 409 without spending it: upload the file to upload_url, then call it again.

product_attachment_delete

Permanently deletes one attachment of a product. This cannot be undone.

Deletes or overwrites data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe product's ID.
categorystringYesThe attachment's category, one of the values used when uploading: sfda_cert, catalog or other.
pathstringYesThe attachment's path, from the attachments array of products_get or from the upload reply.
{
  "id": 1187,
  "category": "catalog",
  "path": "9b2f1c0e-4d1a-4f6e-8c1b-3a7d5e2f9a10/products/1187/attachments/catalog/1791281700000-k3x9ab__syringe-catalog-2026.pdf"
}
Example arguments

Returns: A confirmation (success: true).

  • A path that is not among this product's attachments of the given category answers 400, and a product your organization does not own answers 404.

product_files_copy

Puts one product's photo or attachment on other products of your organization by a copy inside storage, at the same cost whatever the file's size. Upload a brochure that covers several products once, then copy it here instead of uploading it for each. Each product gets its own copy, so deleting it from one leaves the others theirs.

Changes data · scope products.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
kindstringYesphoto for a photo, or the attachment category the source file is filed under and the copies will be: sfda_cert, catalog or other.
source_product_idintegerYesThe product the file belongs to now.
pathstringYesThe source file's path: a photo's from `product_photo_list` or its upload reply, an attachment's from the attachments of products_get or its upload reply.
product_idsinteger[]YesThe products to put the file on, from 1 to 500. The source product is skipped if listed.
{
  "kind": "catalog",
  "source_product_id": 1187,
  "path": "9b2f1c0e-4d1a-4f6e-8c1b-3a7d5e2f9a10/products/1187/attachments/catalog/1791281700000-k3x9ab__syringe-catalog-2026.pdf",
  "product_ids": [
    1188,
    1189,
    1190
  ]
}
Example arguments

Returns: An object with copied (each product's product_id and the new path) and failed (each product's product_id and the error).

  • A path that is not in the source product's folder for that kind answers 400, and any product your organization does not own or that is in the trash answers 404 with nothing copied. One copy failing does not stop the rest: retry the products listed in failed alone.

Manufacturer attachments

A manufacturer has four categories of attachments: catalog, sfda_ar, iso and authorization. Attachments and files explains how the app uses them.

manufacturer_attachment_list

Lists every file filed on a manufacturer across its four categories, each with its path, display name, size and a temporary download link.

Read-only · scope manufacturers.read · all roles · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe manufacturer's ID, from manufacturers_list.

Returns: A list of attachments, each with category, path, name, size, uploaded_at and url, a link valid for about an hour.

  • A manufacturer that your organization does not own answers 404.

manufacturer_attachment_upload_url_create

Starts filing a document on a manufacturer: its catalog, its SFDA authorized-representative licence, an ISO certificate or the distributor authorization letter your organization holds from it. Returns a temporary upload link, or copies a file the platform holds when you send staged_ref, which is how a full supplier catalog gets filed. Then call manufacturer_attachment_upload_finalize.

Changes data · scope manufacturers.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe manufacturer's ID, from manufacturers_list.
categorystringYesThe attachment's category: catalog, sfda_ar for the SFDA authorized-representative licence, iso for an ISO certificate, or authorization for the distributor authorization letter your organization holds from the manufacturer, which rides with its products in submission packs. The SFDA licence belongs in sfda_ar, never in authorization.
filenamestringNoThe original file name. Required unless you send staged_ref, in which case the held file's own name is used unless you rename it.
content_typestringNoThe file's MIME type; required unless you send staged_ref. Accepted: application/pdf, images (image/jpeg, image/png, image/webp, image/gif), Office documents (Word, Excel, PowerPoint, old and OpenXML formats), text/plain and text/csv.
size_bytesintegerNoThe file's size in bytes, if known, so a file over the limit is refused here instead of after the upload.
staged_refstringNoA reference to a file the platform already holds for your organization, such as one sent to the WhatsApp assistant, listed by `whatsapp_attachments_list`. The file is copied into place inside the server, so there is nothing to upload: upload_url comes back empty and you go straight to the finalize tool. Send it instead of filename and content_type.
{
  "id": 214,
  "category": "catalog",
  "staged_ref": "wa-staged:3f9a1c52-7b1e-4d2a-9c61-5e0b2d7a8f14",
  "filename": "meditech-catalog-2026.pdf"
}
Example arguments

Returns: upload_id, upload_url, method (PUT), headers and expires_at (status 201). With staged_ref, upload_url is empty and staged is true.

  • The limit is 15 MB for an uploaded file and 100 MB for a referenced one. A manufacturer that your organization does not own answers 404.
  • The link takes one upload and expires at expires_at, two hours after it was created. Nothing is filed before the finalize tool runs, and an upload not finalized within 24 hours is deleted along with its file.
  • An unsupported type answers 400 and a declared size over the limit answers 413; neither creates a link.
  • Each organization, every API key and connector together, may hold at most 20 uploads open at once (started, not yet finalized, link unexpired) and start at most 500 a day (UTC), unless ProcDuck has set the organization other limits. Past either, the start tool answers 429 with code: "upload_limit_reached" and retry_after_seconds: finalize the uploads whose file you sent, or wait that long. A link never used gives its place back when it expires.

manufacturer_attachment_upload_finalize

Finishes an upload started with manufacturer_attachment_upload_url_create: checks the file's real size and type, then files it on the manufacturer under the category chosen at the start.

Changes data · scope manufacturers.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
upload_idstringYesThe upload_id the matching start tool returned.
{
  "upload_id": "a41c9e27-6d03-4b8e-8f15-2c7a9b0d4e61"
}
Example arguments

Returns: The new attachment's path, url (a link valid for about an hour) and name, with content_type and size_bytes.

  • A staged_ref is used once: after a successful finalize the held copy is discarded, and if the file is refused the reference becomes usable again.
  • The server reads the file itself rather than trusting the declared type: a file that is not an accepted type, or is over the limit, is deleted and answers 422 or 413 with the reason, so start a new upload.
  • Each upload_id finalizes once. An unknown one, or one from another organization, answers 404; an expired one answers 410; one already used answers 409.
  • Calling finalize before the upload answers 409 without spending it: upload the file to upload_url, then call it again.

manufacturer_attachment_delete

Permanently deletes one attachment of a manufacturer. This cannot be undone.

Deletes or overwrites data · scope manufacturers.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe manufacturer's ID.
categorystringYesThe attachment's category, one of the values used when uploading: catalog, sfda_ar, iso or authorization.
pathstringYesThe attachment's path, from manufacturer_attachment_list.
{
  "id": 214,
  "category": "iso",
  "path": "9b2f1c0e-4d1a-4f6e-8c1b-3a7d5e2f9a10/manufacturers/214/attachments/iso/1791281700000-p7q2cd__iso-13485-certificate.pdf"
}
Example arguments

Returns: A confirmation (success: true).

  • A path that is not among this manufacturer's attachments of the given category answers 400, and a manufacturer your organization does not own answers 404.

Sales order documents

A sales order keeps its documents, first of all the customer's purchase order as a PDF. They are uploaded with the same pair of tools as every other file: a start, then a finish. The documents are listed in documents of `sales_orders_get`.

sales_order_document_upload_url_create

Starts filing a document on a sales order: the customer's purchase order as a PDF, a scan or photo of it, or a revised copy. Returns a temporary upload link, or copies a file the platform holds when you send staged_ref. Then call sales_order_document_upload_finalize.

Changes data · scope orders.write · roles: owner, admin, sales · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe sales order's ID, from sales_orders_list.
filenamestringNoThe original file name. Required unless you send staged_ref, in which case the held file's own name is used unless you rename it.
content_typestringNoThe file's MIME type; required unless you send staged_ref. Accepted: application/pdf, images (image/jpeg, image/png, image/webp, image/gif), Office documents (Word, Excel, PowerPoint, old and OpenXML formats), text/plain and text/csv.
size_bytesintegerNoThe file's size in bytes, if known, so a file over the limit is refused here instead of after the upload.
staged_refstringNoA reference to a file the platform already holds for your organization, such as one sent to the WhatsApp assistant, listed by `whatsapp_attachments_list`. The file is copied into place inside the server, so there is nothing to upload: upload_url comes back empty and you go straight to the finalize tool. Send it instead of filename and content_type.
{
  "id": 1042,
  "filename": "NUPCO-PO-4300010571.pdf",
  "content_type": "application/pdf",
  "size_bytes": 108281
}
Example arguments

Returns: upload_id, upload_url, method (PUT), headers and expires_at (status 201). With staged_ref, upload_url is empty and staged is true.

  • The limit is 15 MB for an uploaded file and 100 MB for a referenced one. An order keeps any number of documents. An order that your organization does not own answers 404.
  • The link takes one upload and expires at expires_at, two hours after it was created. Nothing is filed before the finalize tool runs, and an upload not finalized within 24 hours is deleted along with its file.
  • An unsupported type answers 400 and a declared size over the limit answers 413; neither creates a link.
  • Each organization, every API key and connector together, may hold at most 20 uploads open at once (started, not yet finalized, link unexpired) and start at most 500 a day (UTC), unless ProcDuck has set the organization other limits. Past either, the start tool answers 429 with code: "upload_limit_reached" and retry_after_seconds: finalize the uploads whose file you sent, or wait that long. A link never used gives its place back when it expires.

sales_order_document_upload_finalize

Finishes an upload started with sales_order_document_upload_url_create: checks the file's real size and type, then files it on the sales order.

Changes data · scope orders.write · roles: owner, admin, sales · Pro and Enterprise plans

ParameterTypeRequiredDescription
upload_idstringYesThe upload_id the matching start tool returned.
{
  "upload_id": "a41c9e27-6d03-4b8e-8f15-2c7a9b0d4e61"
}
Example arguments

Returns: The new document's path, url (a link valid for about an hour) and name, with content_type and size_bytes.

  • The server reads the file itself rather than trusting the declared type: a file that is not an accepted type, or is over the limit, is deleted and answers 422 or 413 with the reason, so start a new upload.
  • Each upload_id finalizes once. An unknown one, or one from another organization, answers 404; an expired one answers 410; one already used answers 409.
  • Calling finalize before the upload answers 409 without spending it: upload the file to upload_url, then call it again.

sales_order_document_delete

Permanently deletes one document of a sales order. This cannot be undone.

Deletes or overwrites data · scope orders.write · roles: owner, admin, sales · Pro and Enterprise plans

ParameterTypeRequiredDescription
idintegerYesThe sales order's ID.
pathstringYesThe document's path, from documents in sales_orders_get, up to 600 characters.
{
  "id": 1042,
  "path": "9b2f1c0e-4d1a-4f6e-8c1b-3a7d5e2f9a10/sales-orders/1042/documents/1791281700000-p7q2cd__NUPCO-PO-4300010571.pdf"
}
Example arguments

Returns: A confirmation (success: true).

  • A path that is not among this order's documents answers 400, and an order your organization does not own answers 404.

Logo, stamp and signature

Three images print on quotations. The logo and the stamp belong to the organization and only the owner changes them; the signature is personal, and the member the connection acts as manages their own. Uploading always replaces the previous image.

settings_logo_upload_url_create

Starts setting the organization's logo, which prints in the header of quotation PDFs and replaces the previous logo once finalized. Returns a temporary upload link to send the image to with a PUT; then call settings_logo_upload_finalize.

Changes data · scope settings.write · roles: owner · Pro and Enterprise plans

ParameterTypeRequiredDescription
filenamestringNoThe original file name. Required unless you send staged_ref, in which case the held file's own name is used unless you rename it.
content_typestringNoThe file's MIME type: image/png, image/jpeg, image/webp or image/svg+xml; image/gif, image/avif and image/tiff are accepted and converted on finalize. Required unless you send staged_ref.
size_bytesintegerNoThe file's size in bytes, if known, so a file over the limit is refused here instead of after the upload.
staged_refstringNoA reference to a file the platform already holds for your organization, such as one sent to the WhatsApp assistant, listed by `whatsapp_attachments_list`. The file is copied into place inside the server, so there is nothing to upload: upload_url comes back empty and you go straight to the finalize tool. Send it instead of filename and content_type.
{
  "filename": "acme-logo.png",
  "content_type": "image/png",
  "size_bytes": 48210
}
Example arguments

Returns: upload_id, upload_url, method (PUT), headers and expires_at (status 201). With staged_ref, upload_url is empty and staged is true.

  • The limit is 2 MB. A connection that does not belong to the organization's owner answers 403.
  • The link takes one upload and expires at expires_at, two hours after it was created. Nothing is filed before the finalize tool runs, and an upload not finalized within 24 hours is deleted along with its file.
  • An unsupported type answers 400 and a declared size over the limit answers 413; neither creates a link.
  • Each organization, every API key and connector together, may hold at most 20 uploads open at once (started, not yet finalized, link unexpired) and start at most 500 a day (UTC), unless ProcDuck has set the organization other limits. Past either, the start tool answers 429 with code: "upload_limit_reached" and retry_after_seconds: finalize the uploads whose file you sent, or wait that long. A link never used gives its place back when it expires.

settings_logo_upload_finalize

Finishes an upload started with settings_logo_upload_url_create: checks the image, converts, trims or downscales it where needed, then sets it as the organization's logo in place of the previous one.

Changes data · scope settings.write · roles: owner · Pro and Enterprise plans

ParameterTypeRequiredDescription
upload_idstringYesThe upload_id the matching start tool returned.
trim_whitespacebooleanNoCrops near-white or transparent borders around the image, leaving padding of about 2% of the content's size. An SVG is left as it is.
max_widthintegerNoDownscales the image to at most this many pixels wide, keeping its proportions; it never enlarges one. From 16 to 10000.
{
  "upload_id": "c2d8f1a0-4b7e-4e39-a5c6-1f0b9d2e7a43",
  "trim_whitespace": true
}
Example arguments

Returns: The saved image's path and url (a link valid for about an hour), with content_type, size_bytes, width, height, converted_from when the format was converted, trimmed and resized.

  • An SVG is stored as uploaded, so trim_whitespace and max_width leave it alone. trim_whitespace is often worth it for a logo or stamp exported with a wide margin.
  • A connection that does not belong to the organization's owner answers 403.
  • An image in a format outside the allowed ones, such as AVIF or TIFF, is converted to JPEG at quality 85, or to PNG when it has transparency. An image that needs no conversion, trim or downscale is stored exactly as uploaded. HEIC is not accepted; export it as JPEG or PNG.
  • The server reads the file itself rather than trusting the declared type: a file that is not an accepted type, or is over the limit, is deleted and answers 422 or 413 with the reason, so start a new upload.
  • Each upload_id finalizes once. An unknown one, or one from another organization, answers 404; an expired one answers 410; one already used answers 409.
  • Calling finalize before the upload answers 409 without spending it: upload the file to upload_url, then call it again.

settings_logo_delete

Removes the organization's logo, so quotation PDFs print without it. This cannot be undone; upload the logo again to restore it. Owner only.

Deletes or overwrites data · scope settings.write · roles: owner · Pro and Enterprise plans

No parameters.

Returns: A confirmation (success: true).

settings_stamp_upload_url_create

Starts setting the organization's stamp, which prints on quotation PDFs and replaces the previous stamp once finalized. Returns a temporary upload link to send the image to with a PUT; then call settings_stamp_upload_finalize.

Changes data · scope settings.write · roles: owner · Pro and Enterprise plans

ParameterTypeRequiredDescription
filenamestringNoThe original file name. Required unless you send staged_ref, in which case the held file's own name is used unless you rename it.
content_typestringNoThe file's MIME type: image/png, image/jpeg, image/webp or image/svg+xml; image/gif, image/avif and image/tiff are accepted and converted on finalize. Required unless you send staged_ref.
size_bytesintegerNoThe file's size in bytes, if known, so a file over the limit is refused here instead of after the upload.
staged_refstringNoA reference to a file the platform already holds for your organization, such as one sent to the WhatsApp assistant, listed by `whatsapp_attachments_list`. The file is copied into place inside the server, so there is nothing to upload: upload_url comes back empty and you go straight to the finalize tool. Send it instead of filename and content_type.
{
  "filename": "acme-stamp.png",
  "content_type": "image/png",
  "size_bytes": 48210
}
Example arguments

Returns: upload_id, upload_url, method (PUT), headers and expires_at (status 201). With staged_ref, upload_url is empty and staged is true.

  • The limit is 2 MB. A connection that does not belong to the organization's owner answers 403.
  • The link takes one upload and expires at expires_at, two hours after it was created. Nothing is filed before the finalize tool runs, and an upload not finalized within 24 hours is deleted along with its file.
  • An unsupported type answers 400 and a declared size over the limit answers 413; neither creates a link.
  • Each organization, every API key and connector together, may hold at most 20 uploads open at once (started, not yet finalized, link unexpired) and start at most 500 a day (UTC), unless ProcDuck has set the organization other limits. Past either, the start tool answers 429 with code: "upload_limit_reached" and retry_after_seconds: finalize the uploads whose file you sent, or wait that long. A link never used gives its place back when it expires.

settings_stamp_upload_finalize

Finishes an upload started with settings_stamp_upload_url_create: checks the image, converts, trims or downscales it where needed, then sets it as the organization's stamp in place of the previous one.

Changes data · scope settings.write · roles: owner · Pro and Enterprise plans

ParameterTypeRequiredDescription
upload_idstringYesThe upload_id the matching start tool returned.
trim_whitespacebooleanNoCrops near-white or transparent borders around the image, leaving padding of about 2% of the content's size. An SVG is left as it is.
max_widthintegerNoDownscales the image to at most this many pixels wide, keeping its proportions; it never enlarges one. From 16 to 10000.
{
  "upload_id": "c2d8f1a0-4b7e-4e39-a5c6-1f0b9d2e7a43",
  "trim_whitespace": true
}
Example arguments

Returns: The saved image's path and url (a link valid for about an hour), with content_type, size_bytes, width, height, converted_from when the format was converted, trimmed and resized.

  • An SVG is stored as uploaded, so trim_whitespace and max_width leave it alone. trim_whitespace is often worth it for a logo or stamp exported with a wide margin.
  • A connection that does not belong to the organization's owner answers 403.
  • An image in a format outside the allowed ones, such as AVIF or TIFF, is converted to JPEG at quality 85, or to PNG when it has transparency. An image that needs no conversion, trim or downscale is stored exactly as uploaded. HEIC is not accepted; export it as JPEG or PNG.
  • The server reads the file itself rather than trusting the declared type: a file that is not an accepted type, or is over the limit, is deleted and answers 422 or 413 with the reason, so start a new upload.
  • Each upload_id finalizes once. An unknown one, or one from another organization, answers 404; an expired one answers 410; one already used answers 409.
  • Calling finalize before the upload answers 409 without spending it: upload the file to upload_url, then call it again.

settings_stamp_delete

Removes the organization's stamp, so quotation PDFs print without it. This cannot be undone; upload the stamp again to restore it. Owner only.

Deletes or overwrites data · scope settings.write · roles: owner · Pro and Enterprise plans

No parameters.

Returns: A confirmation (success: true).

settings_signature_get

Returns the signature image of the member the connection acts as, or nulls when none is stored. The signature belongs to the member, not to the organization.

Read-only · scope settings.read · all roles · Pro and Enterprise plans

No parameters.

Returns: The path and url of the signature, the link valid for about an hour, or null for both when there is none.

settings_signature_upload_url_create

Starts setting the signature image of the member the connection acts as, which prints on approved quotations that name them as sales person and replaces any earlier signature once finalized. Returns a temporary upload link to send the image to with a PUT; then call settings_signature_upload_finalize.

Changes data · scope settings.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
filenamestringNoThe original file name. Required unless you send staged_ref, in which case the held file's own name is used unless you rename it.
content_typestringNoThe file's MIME type: image/png, image/jpeg, image/webp or image/svg+xml; image/gif, image/avif and image/tiff are accepted and converted on finalize. Required unless you send staged_ref.
size_bytesintegerNoThe file's size in bytes, if known, so a file over the limit is refused here instead of after the upload.
staged_refstringNoA reference to a file the platform already holds for your organization, such as one sent to the WhatsApp assistant, listed by `whatsapp_attachments_list`. The file is copied into place inside the server, so there is nothing to upload: upload_url comes back empty and you go straight to the finalize tool. Send it instead of filename and content_type.
{
  "filename": "signature.png",
  "content_type": "image/png",
  "size_bytes": 48210
}
Example arguments

Returns: upload_id, upload_url, method (PUT), headers and expires_at (status 201). With staged_ref, upload_url is empty and staged is true.

  • The limit is 2 MB.
  • The link takes one upload and expires at expires_at, two hours after it was created. Nothing is filed before the finalize tool runs, and an upload not finalized within 24 hours is deleted along with its file.
  • An unsupported type answers 400 and a declared size over the limit answers 413; neither creates a link.
  • Each organization, every API key and connector together, may hold at most 20 uploads open at once (started, not yet finalized, link unexpired) and start at most 500 a day (UTC), unless ProcDuck has set the organization other limits. Past either, the start tool answers 429 with code: "upload_limit_reached" and retry_after_seconds: finalize the uploads whose file you sent, or wait that long. A link never used gives its place back when it expires.

settings_signature_upload_finalize

Finishes an upload started with settings_signature_upload_url_create: checks the image, converts, trims or downscales it where needed, then sets it as the member's signature in place of the previous one.

Changes data · scope settings.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
upload_idstringYesThe upload_id the matching start tool returned.
trim_whitespacebooleanNoCrops near-white or transparent borders around the image, leaving padding of about 2% of the content's size. An SVG is left as it is.
max_widthintegerNoDownscales the image to at most this many pixels wide, keeping its proportions; it never enlarges one. From 16 to 10000.
{
  "upload_id": "c2d8f1a0-4b7e-4e39-a5c6-1f0b9d2e7a43",
  "trim_whitespace": true
}
Example arguments

Returns: The saved image's path and url (a link valid for about an hour), with content_type, size_bytes, width, height, converted_from when the format was converted, trimmed and resized.

  • The signature prints when the sales person linked to the quotation is a team member tied to their account on the sales people list, and approval never waits for a signature. A signature photographed on white paper usually wants trim_whitespace.
  • Only the member who started a signature upload can finalize it.
  • An image in a format outside the allowed ones, such as AVIF or TIFF, is converted to JPEG at quality 85, or to PNG when it has transparency. An image that needs no conversion, trim or downscale is stored exactly as uploaded. HEIC is not accepted; export it as JPEG or PNG.
  • The server reads the file itself rather than trusting the declared type: a file that is not an accepted type, or is over the limit, is deleted and answers 422 or 413 with the reason, so start a new upload.
  • Each upload_id finalizes once. An unknown one, or one from another organization, answers 404; an expired one answers 410; one already used answers 409.
  • Calling finalize before the upload answers 409 without spending it: upload the file to upload_url, then call it again.

settings_signature_delete

Removes the signature of the member the connection acts as. Quotations that name this member as sales person then print without a signature, and approval is never blocked by a missing one. This cannot be undone.

Deletes or overwrites data · scope settings.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

No parameters.

Returns: A confirmation (success: true).

Importing from a spreadsheet

sheet_import reads an Excel or CSV file on the server: it works out which column means what from a small sample, then reads every row itself, so a sheet of 2,000 rows costs what a sheet of 70 does. It has five targets. tender_items creates a manual tender of your organization with the rows as its items. products creates catalog products and links each to the manufacturer named in its row, creating the manufacturer when it is new. quotation_items creates one quotation, and every line needs a price from the file, so a sheet with no price column comes back as needs_confirmation. bid_pricing prices an existing tender's items by matching material codes. manufacturers creates one entry per row and skips names you already have, listing them in skipped_existing. A short list, a PDF or a photo does not need this tool: create the records with `tenders_create`, `products_bulk_create` or `quotations_create`.

sheet_import

Creates records from an Excel or CSV file without you reading its rows. The server works out from a sample which column means what, applies that to every row and returns the mapping it used. When it is unsure, it writes nothing and returns the mapping for you to confirm.

Changes data · scope tenders.write or products.write or quotations.write or bids.write or manufacturers.write · roles: owner, admin, sales, purchasing · Pro and Enterprise plans

ParameterTypeRequiredDescription
sheet_base64stringYesThe content of an .xlsx or .csv file as base64. About 3 MB decoded is the practical ceiling, because the server refuses any request over about 4.5 MB. Send the file's own bytes, not a table rebuilt from its rows. On WhatsApp the assistant passes the attachment's reference in place of the content; the server reads the file and keeps it available for the confirming call.
filenamestringNoThe original file name, up to 300 characters.
targetstringYesWhat the rows become: tender_items for the items of a new manual tender (send tender), products for catalog products, quotation_items for the lines of a new quotation (send quotation), bid_pricing for prices on an existing tender's items (send tender_id), manufacturers for entries in your manufacturer directory.
instructionstringNoWhat the file's owner said about it, in their own words, for example that the description column is the item name and the specification column is the description. It outranks the automatic reading of the columns. Up to 2,000 characters.
sheetstringNoThe worksheet's name. The first worksheet is used when omitted, and the reply lists the others in other_sheets. An unknown name answers 400.
layoutobjectNoThe mapping you state yourself instead of having the server detect it: the header row, the data range and what each column feeds. Use it to correct a column after the file's owner has reviewed the mapping, or when you already know it. Every reply returns the mapping it used in this same shape, so you can send it back after editing it. A mapping you send is taken as stated and needs no confirmation.
layout.sheetstringNoThe worksheet this mapping applies to, up to 200 characters.
layout.header_rowintegerYesThe header row's number, counting from 1 as Excel does, or 0 when the sheet has no headers. Required when layout is sent.
layout.data_start_rowintegerYesThe number of the first real data row, counting from 1. Required when layout is sent.
layout.data_end_rowintegerYesThe number of the last data row, or 0 to run to the end of the sheet. Required when layout is sent.
layout.columnsobject[]YesThe columns and the field each one feeds, from 1 to 60 columns. Required when layout is sent.
layout.columns[].columnstringYesThe column's letter in the sheet: A, B, AA.
layout.columns[].fieldstringYesThe field this column feeds, or ignore to skip it. Fields of tender_items: item_name, description, quantity, unit, estimated_price, material_code. Fields of products, each with the column header the template and the export give it: product_name: Product name; description: Description; nupco_product_code: NUPCO code; catalogue_number: Catalogue number; manufacturer_name: Manufacturer; country_of_origin_code: Country of origin; price_basis: Price per; cost_price: Cost price; cost_currency: Cost currency; sale_price_sar: Sale price (SAR); vat_percent: VAT %; case_type: Case type; units_per_case: Units per case; min_order_qty: Minimum order quantity; is_medical: Medical; mdma_number: MDMA number; mdma_expiry: MDMA expiry; mdma_status: MDMA status; listing_number: Listing number; shelf_life_years: Shelf life (years); first_delivery_days: First delivery (days); order_delivery_days: Order delivery (days); unit_of_measure: Unit of measure; weight_kg: Unit weight (kg); length_cm: Unit length (cm); width_cm: Unit width (cm); height_cm: Unit height (cm); case_weight_kg: Case weight (kg); case_length_cm: Case length (cm); case_width_cm: Case width (cm); case_height_cm: Case height (cm); package_type: Package type; package_size: Package size; inner_length_cm: Pack length (cm); inner_width_cm: Pack width (cm); inner_height_cm: Pack height (cm); volume: Volume; volume_unit: Volume unit; content_quantity: Content per unit; content_unit: Content unit; packing_kind: Product kind; foc_percent: FOC %; additional_info: Additional info; search_query: Search query; photo_files: Photo files; catalog_files: Catalog files. Fields of quotation_items: description, quantity, unit_price, unit, stock_no, catalog_no, vat_rate. Fields of bid_pricing: material_code, unit_price, notes. Fields of manufacturers: name, contact_person, country, email, phone, website, address, notes, agency_type. Two columns may feed the same field (a specification in two languages) and are joined in the order sent. A field that is not one of the target's fields is refused.
layout.columns[].headerstringNoThe column's own header text, shown when the mapping is reviewed.
confirmbooleanNoAccepts a mapping that came back as needs_confirmation. Send it only after the file's owner has seen the mapping.
dry_runbooleanNoReads the file and counts the rows without writing anything, and returns the mapping and a preview with the status preview.
tenderobjectNoThe header of the manual tender that will hold the items. Required with tender_items; take it from what the file's owner said or from the file itself.
tender.tender_namestringYesThe tender's name, up to 500 characters. Required when tender is sent.
tender.agency_namestringYesThe buying agency's name, up to 300 characters. Required when tender is sent.
tender.tender_numberstringNoThe tender number, up to 100 characters. Send null to leave it empty.
tender.reference_numberstringNoThe tender's reference number, up to 100 characters. Send null to leave it empty.
tender.branch_namestringNoThe branch name, up to 200 characters. Send null to leave it empty.
tender.last_offer_datestringNoThe offer submission deadline as an ISO 8601 timestamp, such as 2026-11-02T12:00:00. Send null to leave it empty.
manufacturer_idintegerNoLinks every product created to this manufacturer, with the products target. From manufacturers_list.
quotationobjectNoThe header of the quotation that will hold the lines, with quotation_items: customer, tender link, currency and validity. Take it from what the file's owner said; the line prices come from the sheet.
quotation.tender_idintegerNoLinks the quotation to a tender, and the tender's agency becomes the addressee. Send null for a quotation with no tender.
quotation.customer_namestringNoThe customer's name, up to 200 characters. Send null to leave it empty.
quotation.rfq_nostringNoThe customer's RFQ number, up to 100 characters. Send null to leave it empty.
quotation.quote_validity_daysintegerNoHow long the quotation stays valid, in days, from 1 to 3650. Send null to leave it empty.
quotation.delivery_period_daysintegerNoThe delivery period in days, from 1 to 3650. Send null to leave it empty.
quotation.currencystringNoThe currency code, 1 to 8 characters. Defaults to SAR.
quotation.notesstringNoNotes on the quotation, up to 2,000 characters. Send null to leave them empty.
quotation.sales_person_idintegerNoThe sales person printed on the quotation, from sales_persons_list. Send null to leave it empty.
tender_idintegerNoThe tender whose items a bid_pricing sheet prices. Required with that target, from tenders_list. Each row is matched to a live item by material_code alone, an exact match that ignores letter case. A row with no item, or whose code two items share, is not priced and comes back in unmatched with its row number; price those with `bid_pricing_set_batch`. A row priced 0 keeps its price and clears the item's participation mark, as in `bid_pricing_set_batch`.
constantsobjectNoValues the file's owner stated once for the whole sheet, applied to every row: for example {"cost_currency": "SAR"} when prices are in riyals and no column carries the currency. A constant for a field that a column already feeds is refused, and the reply echoes the constants applied so you can read them back.
filesstring[]NoFor the products target only: the names of the photo and catalog files you hold for this sheet, up to 3,000, names only and never their content. The reply's attachments then says which file goes to which row: by the Photo files and Catalog files cells, else by files named after the row's catalogue number. Without it, attachments lists the names the cells ask for without checking them against files.
{
  "sheet_base64": "UEsDBBQABgAIAAAAIQBi7p1L...",
  "filename": "meditech-price-list.xlsx",
  "target": "products",
  "manufacturer_id": 214,
  "instruction": "The price column is the cost per carton; the prices are in USD",
  "constants": {
    "cost_currency": "USD"
  },
  "dry_run": true
}
Example arguments

Returns: An object with the status (imported, preview or needs_confirmation), the mapping used in mapping, the ignored columns, the row counts in counts, the first 20 problems in issues with their row numbers, a 3-row preview and the layout in a form you can send back. Once something is written it adds created, with tender_id for the tender, quotation_id and quote_number for the quotation, priced and unmatched for prices, and skipped_existing for manufacturers. For products, own_headers: true says the mapping was read from the headers, and attachments carries the file plan: rows (each row's photos and catalogs, with product_id once imported), problems per row and file, shared_catalogue_numbers and unused files.

  • A status of needs_confirmation means the server is unsure of its reading of the columns, or a required field has no column, and nothing is written. Show the mapping to the file's owner in their language, then call again with confirm: true or a corrected layout.
  • Each target has its own scope: tender_items needs tenders.write and the owner or admin role, products needs products.write, manufacturers needs manufacturers.write, quotation_items needs quotations.write and bid_pricing needs bids.write. The last four are open to the owner, admin, sales and purchasing roles.
  • Reading skips empty rows, repeated header rows, merged banner rows such as a page total, and rows missing a required field, and counts them by reason in counts. Rows beyond the target's cap are not written and are counted in counts.truncated: 2,000 for tender_items and bid_pricing, 500 for products, quotation_items and manufacturers.
  • With the products target every product field the app edits can be a column, plus Photo files and Catalog files. A sheet whose headers are exactly the import's own, such as the template from `products_import_template` or the output of `products_export_csv`, is mapped from its headers with no detection step. A column holding a list separates it with a semicolon (;): NUPCO codes with the primary first, manufacturers, and file names.
  • A Price per column says what the row's prices are for: a level word (Piece or Unit for the piece, Pack or Box for the pack, Carton or Case for the case, Content for one unit of what a piece holds, or the Arabic words for them), or the name of one of the row's own levels, such as Vial or Kit. A cell naming no level of the row, or a level the row has no count for, drops the level and both prices together, each with an issue and its row number, because a price without its level says nothing about what unit it is for. A blank cell leaves the level not stated, so the prices are used as they are on every tender line. A Content unit column reads the content unit by any of its names (ml, mL, Tests, or its Arabic name), and an unknown one drops both content cells; a Product kind column reads a type's key or its English or Arabic name.
  • If the write is still refused for packing (price_basis_level_missing or content_incomplete), nothing is written and rows names each refused row by its sheet row, the field and the fix.
  • To attach the files after the import, use the product_id in attachments.rows: `product_photo_upload_url_create` for photos, and `product_attachment_upload_url_create` with category catalog for catalogs, each followed by its finalize tool. Upload a file several rows share once, then copy it with `product_files_copy`.
  • The tool takes spreadsheets only. A PDF, Word or PowerPoint file answers 400, and an old xls file answers 400 too and must be saved as xlsx or csv. Creating products past your plan's limit answers 403, and creating the quotation counts against the plan's monthly limit on new quotations.