Customers
The companies you invoice. A customer carries terms, credit limit, tax group, and any number of ship-to addresses.
Customers are never removed. DELETE /customers/{id} sets the cancel flag and keeps history intact.
The object#
| Field | Type | Description |
|---|---|---|
| idread-only | integer | NolaPro id. |
| companyname | string(100) | The customer name. Required on create. Not unique, and not a lookup key - use customercode. |
| companyname2 | string(50) | Second name line. |
| customercode | string(30) | Your short code for this customer, and the key the customer reference field resolves against. About a quarter of customers have none. |
| chargecode | string(30) | Charge code shown beside the name in some screens. |
| displaynameread-only | string(255) | Generated by the database from the name and code. Read-only. |
| fullnameread-only | string(255) | Generated by the database. Read-only. |
| address1 | string(100) | Street address. |
| address2 | string(100) | Second address line. |
| city | string(50) | City of the billing address. |
| state | string(20) | State or province. |
| zip | string(15) | Postal code. |
| country | string(20) | Country of the billing address. |
| phone1 | string(20) | Primary phone. |
| email1 | string(255) | Primary email. Where invoices go unless email2sendinvoices says otherwise. |
| email2 | string(255) | Secondary email. |
| website | string(100) | Customer’s website URL. |
| creditlimitmoney | string | Zero means no limit. Stored as a double, not DECIMAL(19,4) - see Conventions on money. |
| invoicetermsid | integer | Terms id. Defaulted from the company setting when omitted on create. |
| invoiceterms | string(30) | Terms code, matched against invoiceterms.verbal. Send this or invoicetermsid, not both unless they agree. |
| currency | string(10) | Currency code for this customer. |
| nocredit | boolean | No credit allowed - this customer pays up front. |
| taxgroupid | integer | Tax group applied to the customer’s sales. |
| taxgroup | string(20) | Tax group code instead of the id. |
| taxexemptid | integer | Tax exemption id, 0 for none. |
| taxexempt | string(32) | Tax exemption name (taxexempt.exemptname) instead of the id. |
| salestaxnum | string(30) | Sales tax registration number. |
| salesmanid | integer | Salesperson id. |
| salesman | string(50) | Salesperson name (salesman.name) instead of the id. |
| salesglacctid | integer | Sales GL account id, 0 to use the company default. |
| salesglacct | string(20) | GL account NUMBER (glaccount.name) instead of the id. |
| groupid | integer | Customer group id. Id only - the table this points at is not identifiable from the column name, so there is no human key to send instead. |
| cancel | boolean | True once cancelled by DELETE. Hidden from lists unless you ask for it. |
| closed | boolean | The relationship has ended. Not the same as cancelled. |
| on_hold | boolean | Trading is suspended, usually for credit. Check this before shipping - an on-hold customer is not cancelled and will otherwise accept orders. |
| on_hold_dateread-only | string | When the hold was applied. |
| default_po | string(30) | Default PO number applied to this customer’s orders. |
| notes | text | Free-text notes. Stored as longtext, so there is no practical length limit. |
| externalid | string(100) | Your own key for this customer. Scoped to your company. |
| lastchangedateread-only | string | Last modification. Drives modifiedsince. |
Endpoints#
List customers.
Filters are a fixed allow-list. Anything not listed here returns 400 naming the parameter.
Requires scope customers:read.
| Name | In | Type | Notes |
|---|---|---|---|
| companyname | query | string | Company name. Partial match, case-insensitive. |
| customercode | query | string | Customer code. Partial match. |
| city | query | string | Billing city. Partial match. |
| state | query | string | Billing state. Partial match. |
| zip | query | string | Billing postal code. Partial match. |
| salesmanid | query | string | Restrict to one salesperson. |
| groupid | query | string | Restrict to one customer group. |
| closed | query | string | true or false. Omitted means both - closed customers still appear, because closed is a status, not a cancellation (D23). |
| on_hold | query | string | true or false. Omitted means both. On hold blocks new orders; it does not hide the customer. |
| cancel | query | string | Defaults to false, which hides cancelled records. Pass true for only cancelled, or any for both. |
| page | query | integer | 1-based. Defaults to 1. |
| perpage | query | integer | Defaults to 50, capped at 250. |
| externalid | query | string | Look up by your own key instead of by id. Unique per company and per system tag. |
| modifiedsince | query | string | RFC 3339 UTC timestamp. Returns only records changed since then, including cancelled ones - so cancel defaults to any rather than false when this is used. The response carries _meta.synced_through; store it and send it back next time. |
| include | query | string | Comma-separated extras to embed. Only custom_fields is available: the extra fields this install has defined on the record. Off by default, and an unrecognised value is refused rather than ignored. See Conventions. |
| Status | Code | Meaning |
|---|---|---|
| 400 | unknown_parameter | An unknown filter parameter was sent. |
| 401 | unauthorized | Missing, malformed, revoked, or expired token. |
| 403 | forbidden | The token lacks customers:read, or the user has no rights to customers. |
curl \ 'https://acme.nolapro.com/!/api/v2/customers' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/customers", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/customers"); res.EnsureSuccessStatusCode();
{
"data": [
{
"id": 1187,
"companyname": "Summit Retail LLC",
"companyname2": "",
"customercode": "SUMMIT-01",
"chargecode": "",
"displayname": "Summit Retail LLC (SUMMIT-01)",
"fullname": "Summit Retail LLC",
"address1": "900 Industrial Pkwy",
"address2": "",
"city": "Fostoria",
"state": "OH",
"zip": "44830",
"country": "US",
"phone1": "419-435-0404",
"email1": "ap@summitretail.example",
"email2": "",
"website": "",
"creditlimit": "25000.0000",
"invoicetermsid": 112,
"invoiceterms": "Net 30",
"currency": "USD",
"nocredit": false,
"taxgroupid": 4,
"taxgroup": "OH-SEN",
"taxexemptid": 0,
"taxexempt": "Resale",
"salestaxnum": "",
"salesmanid": 3,
"salesman": "Sales Person",
"salesglacctid": 0,
"salesglacct": "4000",
"groupid": 0,
"cancel": false,
"closed": false,
"on_hold": false,
"default_po": "",
"notes": "",
"externalid": "crm-8842",
"lastchangedate": "2026-07-31T14:02:11Z"
}
],
"page": 1,
"perpage": 50,
"total": 214
}
Create a customer.
Requires scope customers:write.
| Name | In | Type | Notes |
|---|---|---|---|
| Idempotency-Key | header | string | Makes the write safely retryable. Any unique string per logical operation; a UUID is fine.
The failure this prevents is specific: the request succeeds, the response is lost to a timeout, you retry, and the customer is billed twice. With a key, the retry returns the original response - same status, same body, same id - and creates nothing.
The body is part of the key's identity. Reusing a key with a different body is a bug on your side, not a retry, and answers 409 idempotency_replay rather than silently handing back the old response. Keys are remembered for 24 hours. |
| Field | Type | Description |
|---|---|---|
| companynamerequired | string(100) | The customer name. Required on create. Not unique, and not a lookup key - use customercode. |
| companyname2 | string(50) | Second name line. |
| customercode | string(30) | Your short code for this customer, and the key the customer reference field resolves against. About a quarter of customers have none. |
| address1 | string(100) | Street address. |
| address2 | string(100) | Second address line. |
| city | string(50) | City of the billing address. |
| state | string(20) | State or province. |
| zip | string(15) | Postal code. |
| country | string(20) | Country of the billing address. |
| phone1 | string(20) | Primary phone. |
| email1 | string(255) | Primary email. Where invoices go unless email2sendinvoices says otherwise. |
| creditlimitmoney | string | Zero means no limit. Stored as a double, not DECIMAL(19,4) - see Conventions on money. |
| invoicetermsid | integer | Terms id. Defaulted from the company setting when omitted on create. |
| invoiceterms | string(30) | Terms code, matched against invoiceterms.verbal. Send this or invoicetermsid, not both unless they agree. |
| currency | string(10) | Currency code for this customer. |
| taxgroupid | integer | Tax group applied to the customer’s sales. |
| taxgroup | string(20) | Tax group code instead of the id. |
| salesmanid | integer | Salesperson id. |
| salesman | string(50) | Salesperson name instead of the id. |
| default_po | string(30) | Default PO number applied to this customer’s orders. |
| notes | text | Free-text notes. Stored as longtext, so there is no practical length limit. |
| externalid | string(100) | Your own key for this customer. Scoped to your company. |
| chargecode | string(30) | Charge code shown beside the name in some screens. |
| email2 | string(255) | Secondary email. |
| website | string(100) | Customer’s website URL. |
| nocredit | boolean | No credit allowed - this customer pays up front. |
| taxexemptid | integer | Tax exemption id, 0 for none. |
| taxexempt | string(32) | Tax exemption name (taxexempt.exemptname) instead of the id. |
| salestaxnum | string(30) | Sales tax registration number. |
| salesglacctid | integer | Sales GL account id, 0 to use the company default. |
| salesglacct | string(20) | GL account NUMBER (glaccount.name) instead of the id. |
| groupid | integer | Customer group id. Id only - the table this points at is not identifiable from the column name, so there is no human key to send instead. |
| cancel | boolean | True once cancelled by DELETE. Hidden from lists unless you ask for it. |
| closed | boolean | The relationship has ended. Not the same as cancelled. |
| on_hold | boolean | Trading is suspended, usually for credit. Check this before shipping - an on-hold customer is not cancelled and will otherwise accept orders. |
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A field is missing or the wrong type. |
| 409 | duplicate | A customer with this name already exists in the company. |
| 422 | rule_violation | The values are well-formed but break an accounting rule, for example an unknown terms code. |
curl -X POST \ 'https://acme.nolapro.com/!/api/v2/customers' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: your-unique-key' \ -d '{ "companyname": "Summit Retail LLC", "companyname2": "", "customercode": "SUMMIT-01", "address1": "900 Industrial Pkwy", "address2": "", "city": "Fostoria", "state": "OH", "zip": "44830", "country": "US", "phone1": "419-435-0404", "email1": "ap@summitretail.example", "creditlimit": "25000.0000", "invoicetermsid": 112, "invoiceterms": "Net 30", "currency": "USD", "taxgroupid": 4, "taxgroup": "OH-SEN", "salesmanid": 3, "salesman": "Sales Person", "default_po": "", "notes": "", "externalid": "crm-8842", "chargecode": "", "email2": "", "website": "", "nocredit": false, "taxexemptid": 0, "taxexempt": "Resale", "salestaxnum": "", "salesglacctid": 0, "salesglacct": "4000", "groupid": 0, "cancel": false, "closed": false, "on_hold": false }'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => json_encode([ 'companyname' => 'Summit Retail LLC', 'companyname2' => '', 'customercode' => 'SUMMIT-01', 'address1' => '900 Industrial Pkwy', 'address2' => '', 'city' => 'Fostoria', 'state' => 'OH', 'zip' => '44830', 'country' => 'US', 'phone1' => '419-435-0404', 'email1' => 'ap@summitretail.example', 'creditlimit' => '25000.0000', 'invoicetermsid' => 112, 'invoiceterms' => 'Net 30', 'currency' => 'USD', 'taxgroupid' => 4, 'taxgroup' => 'OH-SEN', 'salesmanid' => 3, 'salesman' => 'Sales Person', 'default_po' => '', 'notes' => '', 'externalid' => 'crm-8842', 'chargecode' => '', 'email2' => '', 'website' => '', 'nocredit' => false, 'taxexemptid' => 0, 'taxexempt' => 'Resale', 'salestaxnum' => '', 'salesglacctid' => 0, 'salesglacct' => '4000', 'groupid' => 0, 'cancel' => false, 'closed' => false, 'on_hold' => false, ]), ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.post( "https://acme.nolapro.com/!/api/v2/customers", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, json={ "companyname": "Summit Retail LLC", "companyname2": "", "customercode": "SUMMIT-01", "address1": "900 Industrial Pkwy", "address2": "", "city": "Fostoria", "state": "OH", "zip": "44830", "country": "US", "phone1": "419-435-0404", "email1": "ap@summitretail.example", "creditlimit": "25000.0000", "invoicetermsid": 112, "invoiceterms": "Net 30", "currency": "USD", "taxgroupid": 4, "taxgroup": "OH-SEN", "salesmanid": 3, "salesman": "Sales Person", "default_po": "", "notes": "", "externalid": "crm-8842", "chargecode": "", "email2": "", "website": "", "nocredit": False, "taxexemptid": 0, "taxexempt": "Resale", "salestaxnum": "", "salesglacctid": 0, "salesglacct": "4000", "groupid": 0, "cancel": False, "closed": False, "on_hold": False, }, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, body: JSON.stringify({ companyname: "Summit Retail LLC", companyname2: "", customercode: "SUMMIT-01", address1: "900 Industrial Pkwy", address2: "", city: "Fostoria", state: "OH", zip: "44830", country: "US", phone1: "419-435-0404", email1: "ap@summitretail.example", creditlimit: "25000.0000", invoicetermsid: 112, invoiceterms: "Net 30", currency: "USD", taxgroupid: 4, taxgroup: "OH-SEN", salesmanid: 3, salesman: "Sales Person", default_po: "", notes: "", externalid: "crm-8842", chargecode: "", email2: "", website: "", nocredit: false, taxexemptid: 0, taxexempt: "Resale", salestaxnum: "", salesglacctid: 0, salesglacct: "4000", groupid: 0, cancel: false, closed: false, on_hold: false, }), } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var body = new { companyname = "Summit Retail LLC", companyname2 = "", customercode = "SUMMIT-01", address1 = "900 Industrial Pkwy", address2 = "", city = "Fostoria", state = "OH", zip = "44830", country = "US", phone1 = "419-435-0404", email1 = "ap@summitretail.example", creditlimit = "25000.0000", invoicetermsid = 112, invoiceterms = "Net 30", currency = "USD", taxgroupid = 4, taxgroup = "OH-SEN", salesmanid = 3, salesman = "Sales Person", default_po = "", notes = "", externalid = "crm-8842", chargecode = "", email2 = "", website = "", nocredit = false, taxexemptid = 0, taxexempt = "Resale", salestaxnum = "", salesglacctid = 0, salesglacct = "4000", groupid = 0, cancel = false, closed = false, on_hold = false, }; var req = new HttpRequestMessage(HttpMethod.Post, "https://acme.nolapro.com/!/api/v2/customers") { Content = JsonContent.Create(body), }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
{
"id": 1187,
"companyname": "Summit Retail LLC",
"companyname2": "",
"customercode": "SUMMIT-01",
"chargecode": "",
"displayname": "Summit Retail LLC (SUMMIT-01)",
"fullname": "Summit Retail LLC",
"address1": "900 Industrial Pkwy",
"address2": "",
"city": "Fostoria",
"state": "OH",
"zip": "44830",
"country": "US",
"phone1": "419-435-0404",
"email1": "ap@summitretail.example",
"email2": "",
"website": "",
"creditlimit": "25000.0000",
"invoicetermsid": 112,
"invoiceterms": "Net 30",
"currency": "USD",
"nocredit": false,
"taxgroupid": 4,
"taxgroup": "OH-SEN",
"taxexemptid": 0,
"taxexempt": "Resale",
"salestaxnum": "",
"salesmanid": 3,
"salesman": "Sales Person",
"salesglacctid": 0,
"salesglacct": "4000",
"groupid": 0,
"cancel": false,
"closed": false,
"on_hold": false,
"default_po": "",
"notes": "",
"externalid": "crm-8842",
"lastchangedate": "2026-07-31T14:02:11Z"
}
Retrieve one customer.
Requires scope customers:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
| include | query | string | Comma-separated extras to embed. Only custom_fields is available: the extra fields this install has defined on the record. Off by default, and an unrecognised value is refused rather than ignored. See Conventions. |
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No customer with that id in this company. |
curl \ 'https://acme.nolapro.com/!/api/v2/customers/104' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/customers/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/customers/104"); res.EnsureSuccessStatusCode();
{
"id": 1187,
"companyname": "Summit Retail LLC",
"companyname2": "",
"customercode": "SUMMIT-01",
"chargecode": "",
"displayname": "Summit Retail LLC (SUMMIT-01)",
"fullname": "Summit Retail LLC",
"address1": "900 Industrial Pkwy",
"address2": "",
"city": "Fostoria",
"state": "OH",
"zip": "44830",
"country": "US",
"phone1": "419-435-0404",
"email1": "ap@summitretail.example",
"email2": "",
"website": "",
"creditlimit": "25000.0000",
"invoicetermsid": 112,
"invoiceterms": "Net 30",
"currency": "USD",
"nocredit": false,
"taxgroupid": 4,
"taxgroup": "OH-SEN",
"taxexemptid": 0,
"taxexempt": "Resale",
"salestaxnum": "",
"salesmanid": 3,
"salesman": "Sales Person",
"salesglacctid": 0,
"salesglacct": "4000",
"groupid": 0,
"cancel": false,
"closed": false,
"on_hold": false,
"default_po": "",
"notes": "",
"externalid": "crm-8842",
"lastchangedate": "2026-07-31T14:02:11Z"
}
Update a customer.
Only the fields you send are changed. There is no PUT - with 168 fields a full replace would blank everything you omitted.
Requires scope customers:write.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
| Idempotency-Key | header | string | Makes the write safely retryable. Any unique string per logical operation; a UUID is fine.
The failure this prevents is specific: the request succeeds, the response is lost to a timeout, you retry, and the customer is billed twice. With a key, the retry returns the original response - same status, same body, same id - and creates nothing.
The body is part of the key's identity. Reusing a key with a different body is a bug on your side, not a retry, and answers 409 idempotency_replay rather than silently handing back the old response. Keys are remembered for 24 hours. |
| Field | Type | Description |
|---|---|---|
| companynamerequired | string(100) | The customer name. Required on create. Not unique, and not a lookup key - use customercode. |
| companyname2 | string(50) | Second name line. |
| customercode | string(30) | Your short code for this customer, and the key the customer reference field resolves against. About a quarter of customers have none. |
| address1 | string(100) | Street address. |
| address2 | string(100) | Second address line. |
| city | string(50) | City of the billing address. |
| state | string(20) | State or province. |
| zip | string(15) | Postal code. |
| country | string(20) | Country of the billing address. |
| phone1 | string(20) | Primary phone. |
| email1 | string(255) | Primary email. Where invoices go unless email2sendinvoices says otherwise. |
| creditlimitmoney | string | Zero means no limit. Stored as a double, not DECIMAL(19,4) - see Conventions on money. |
| invoicetermsid | integer | Terms id. Defaulted from the company setting when omitted on create. |
| invoiceterms | string(30) | Terms code, matched against invoiceterms.verbal. Send this or invoicetermsid, not both unless they agree. |
| currency | string(10) | Currency code for this customer. |
| taxgroupid | integer | Tax group applied to the customer’s sales. |
| taxgroup | string(20) | Tax group code instead of the id. |
| salesmanid | integer | Salesperson id. |
| salesman | string(50) | Salesperson name instead of the id. |
| default_po | string(30) | Default PO number applied to this customer’s orders. |
| notes | text | Free-text notes. Stored as longtext, so there is no practical length limit. |
| externalid | string(100) | Your own key for this customer. Scoped to your company. |
| chargecode | string(30) | Charge code shown beside the name in some screens. |
| email2 | string(255) | Secondary email. |
| website | string(100) | Customer’s website URL. |
| nocredit | boolean | No credit allowed - this customer pays up front. |
| taxexemptid | integer | Tax exemption id, 0 for none. |
| taxexempt | string(32) | Tax exemption name (taxexempt.exemptname) instead of the id. |
| salestaxnum | string(30) | Sales tax registration number. |
| salesglacctid | integer | Sales GL account id, 0 to use the company default. |
| salesglacct | string(20) | GL account NUMBER (glaccount.name) instead of the id. |
| groupid | integer | Customer group id. Id only - the table this points at is not identifiable from the column name, so there is no human key to send instead. |
| cancel | boolean | True once cancelled by DELETE. Hidden from lists unless you ask for it. |
| closed | boolean | The relationship has ended. Not the same as cancelled. |
| on_hold | boolean | Trading is suspended, usually for credit. Check this before shipping - an on-hold customer is not cancelled and will otherwise accept orders. |
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No customer with that id. |
| 409 | stale_record | If-Match did not match. |
curl -X PATCH \ 'https://acme.nolapro.com/!/api/v2/customers/104' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: your-unique-key' \ -d '{ "companyname": "Summit Retail LLC", "companyname2": "", "customercode": "SUMMIT-01", "address1": "900 Industrial Pkwy", "address2": "", "city": "Fostoria", "state": "OH", "zip": "44830", "country": "US", "phone1": "419-435-0404", "email1": "ap@summitretail.example", "creditlimit": "25000.0000", "invoicetermsid": 112, "invoiceterms": "Net 30", "currency": "USD", "taxgroupid": 4, "taxgroup": "OH-SEN", "salesmanid": 3, "salesman": "Sales Person", "default_po": "", "notes": "", "externalid": "crm-8842", "chargecode": "", "email2": "", "website": "", "nocredit": false, "taxexemptid": 0, "taxexempt": "Resale", "salestaxnum": "", "salesglacctid": 0, "salesglacct": "4000", "groupid": 0, "cancel": false, "closed": false, "on_hold": false }'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'PATCH', CURLOPT_POSTFIELDS => json_encode([ 'companyname' => 'Summit Retail LLC', 'companyname2' => '', 'customercode' => 'SUMMIT-01', 'address1' => '900 Industrial Pkwy', 'address2' => '', 'city' => 'Fostoria', 'state' => 'OH', 'zip' => '44830', 'country' => 'US', 'phone1' => '419-435-0404', 'email1' => 'ap@summitretail.example', 'creditlimit' => '25000.0000', 'invoicetermsid' => 112, 'invoiceterms' => 'Net 30', 'currency' => 'USD', 'taxgroupid' => 4, 'taxgroup' => 'OH-SEN', 'salesmanid' => 3, 'salesman' => 'Sales Person', 'default_po' => '', 'notes' => '', 'externalid' => 'crm-8842', 'chargecode' => '', 'email2' => '', 'website' => '', 'nocredit' => false, 'taxexemptid' => 0, 'taxexempt' => 'Resale', 'salestaxnum' => '', 'salesglacctid' => 0, 'salesglacct' => '4000', 'groupid' => 0, 'cancel' => false, 'closed' => false, 'on_hold' => false, ]), ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.patch( "https://acme.nolapro.com/!/api/v2/customers/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, json={ "companyname": "Summit Retail LLC", "companyname2": "", "customercode": "SUMMIT-01", "address1": "900 Industrial Pkwy", "address2": "", "city": "Fostoria", "state": "OH", "zip": "44830", "country": "US", "phone1": "419-435-0404", "email1": "ap@summitretail.example", "creditlimit": "25000.0000", "invoicetermsid": 112, "invoiceterms": "Net 30", "currency": "USD", "taxgroupid": 4, "taxgroup": "OH-SEN", "salesmanid": 3, "salesman": "Sales Person", "default_po": "", "notes": "", "externalid": "crm-8842", "chargecode": "", "email2": "", "website": "", "nocredit": False, "taxexemptid": 0, "taxexempt": "Resale", "salestaxnum": "", "salesglacctid": 0, "salesglacct": "4000", "groupid": 0, "cancel": False, "closed": False, "on_hold": False, }, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104', { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, body: JSON.stringify({ companyname: "Summit Retail LLC", companyname2: "", customercode: "SUMMIT-01", address1: "900 Industrial Pkwy", address2: "", city: "Fostoria", state: "OH", zip: "44830", country: "US", phone1: "419-435-0404", email1: "ap@summitretail.example", creditlimit: "25000.0000", invoicetermsid: 112, invoiceterms: "Net 30", currency: "USD", taxgroupid: 4, taxgroup: "OH-SEN", salesmanid: 3, salesman: "Sales Person", default_po: "", notes: "", externalid: "crm-8842", chargecode: "", email2: "", website: "", nocredit: false, taxexemptid: 0, taxexempt: "Resale", salestaxnum: "", salesglacctid: 0, salesglacct: "4000", groupid: 0, cancel: false, closed: false, on_hold: false, }), } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var body = new { companyname = "Summit Retail LLC", companyname2 = "", customercode = "SUMMIT-01", address1 = "900 Industrial Pkwy", address2 = "", city = "Fostoria", state = "OH", zip = "44830", country = "US", phone1 = "419-435-0404", email1 = "ap@summitretail.example", creditlimit = "25000.0000", invoicetermsid = 112, invoiceterms = "Net 30", currency = "USD", taxgroupid = 4, taxgroup = "OH-SEN", salesmanid = 3, salesman = "Sales Person", default_po = "", notes = "", externalid = "crm-8842", chargecode = "", email2 = "", website = "", nocredit = false, taxexemptid = 0, taxexempt = "Resale", salestaxnum = "", salesglacctid = 0, salesglacct = "4000", groupid = 0, cancel = false, closed = false, on_hold = false, }; var req = new HttpRequestMessage(HttpMethod.Patch, "https://acme.nolapro.com/!/api/v2/customers/104") { Content = JsonContent.Create(body), }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
{
"id": 1187,
"companyname": "Summit Retail LLC",
"companyname2": "",
"customercode": "SUMMIT-01",
"chargecode": "",
"displayname": "Summit Retail LLC (SUMMIT-01)",
"fullname": "Summit Retail LLC",
"address1": "900 Industrial Pkwy",
"address2": "",
"city": "Fostoria",
"state": "OH",
"zip": "44830",
"country": "US",
"phone1": "419-435-0404",
"email1": "ap@summitretail.example",
"email2": "",
"website": "",
"creditlimit": "25000.0000",
"invoicetermsid": 112,
"invoiceterms": "Net 30",
"currency": "USD",
"nocredit": false,
"taxgroupid": 4,
"taxgroup": "OH-SEN",
"taxexemptid": 0,
"taxexempt": "Resale",
"salestaxnum": "",
"salesmanid": 3,
"salesman": "Sales Person",
"salesglacctid": 0,
"salesglacct": "4000",
"groupid": 0,
"cancel": false,
"closed": false,
"on_hold": false,
"default_po": "",
"notes": "",
"externalid": "crm-8842",
"lastchangedate": "2026-07-31T14:02:11Z"
}
Cancel one customer.
Sets the cancel flag through the same path the application uses, so whatever bookkeeping cancelling this record implies happens with it. Nothing is removed. Cancelling an already cancelled customer succeeds.
Requires scope customers:cancel.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
| Idempotency-Key | header | string | Makes the write safely retryable. Any unique string per logical operation; a UUID is fine.
The failure this prevents is specific: the request succeeds, the response is lost to a timeout, you retry, and the customer is billed twice. With a key, the retry returns the original response - same status, same body, same id - and creates nothing.
The body is part of the key's identity. Reusing a key with a different body is a bug on your side, not a retry, and answers 409 idempotency_replay rather than silently handing back the old response. Keys are remembered for 24 hours. |
| Status | Code | Meaning |
|---|---|---|
| 403 | insufficient_scope | The token has customers:write but not customers:cancel. |
| 404 | not_found | No customer with that id in this company. |
| 409 | cannot_cancel | Cannot be cancelled in its current state. The message says why. |
curl -X DELETE \ 'https://acme.nolapro.com/!/api/v2/customers/104' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'DELETE', ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.delete( "https://acme.nolapro.com/!/api/v2/customers/104", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104', { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var req = new HttpRequestMessage(HttpMethod.Delete, "https://acme.nolapro.com/!/api/v2/customers/104"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
{
"id": 1187,
"companyname": "Summit Retail LLC",
"companyname2": "",
"customercode": "SUMMIT-01",
"chargecode": "",
"displayname": "Summit Retail LLC (SUMMIT-01)",
"fullname": "Summit Retail LLC",
"address1": "900 Industrial Pkwy",
"address2": "",
"city": "Fostoria",
"state": "OH",
"zip": "44830",
"country": "US",
"phone1": "419-435-0404",
"email1": "ap@summitretail.example",
"email2": "",
"website": "",
"creditlimit": "25000.0000",
"invoicetermsid": 112,
"invoiceterms": "Net 30",
"currency": "USD",
"nocredit": false,
"taxgroupid": 4,
"taxgroup": "OH-SEN",
"taxexemptid": 0,
"taxexempt": "Resale",
"salestaxnum": "",
"salesmanid": 3,
"salesman": "Sales Person",
"salesglacctid": 0,
"salesglacct": "4000",
"groupid": 0,
"cancel": false,
"closed": false,
"on_hold": false,
"default_po": "",
"notes": "",
"externalid": "crm-8842",
"lastchangedate": "2026-07-31T14:02:11Z"
}
Open balance, per currency.
Replaces v1's customer-balance action. Computed by Customer_model::getAgingSummary() - the AR Aging report's own code, so this and the report cannot disagree.
For the same figure split by age, use /customers/{id}/aging.
Requires scope customers:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
| asofdate | query | string | Defaults to today. |
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No customer with that id. |
curl \ 'https://acme.nolapro.com/!/api/v2/customers/104/balance' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104/balance'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/customers/104/balance", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104/balance', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/customers/104/balance"); res.EnsureSuccessStatusCode();
{
"balances": [
{
"currency": "Example currency",
"balance": "125.0000"
}
]
}
Create many.
Always answers 207, one result per item.
Requires scope customers:write.
| Name | In | Type | Notes |
|---|---|---|---|
| Idempotency-Key | header | string | Makes the write safely retryable. Any unique string per logical operation; a UUID is fine.
The failure this prevents is specific: the request succeeds, the response is lost to a timeout, you retry, and the customer is billed twice. With a key, the retry returns the original response - same status, same body, same id - and creates nothing.
The body is part of the key's identity. Reusing a key with a different body is a bug on your side, not a retry, and answers 409 idempotency_replay rather than silently handing back the old response. Keys are remembered for 24 hours. |
| Field | Type | Description |
|---|---|---|
| idread-only | integer | NolaPro id. |
| companyname | string(100) | The customer name. Required on create. Not unique, and not a lookup key - use customercode. |
| companyname2 | string(50) | Second name line. |
| customercode | string(30) | Your short code for this customer, and the key the customer reference field resolves against. About a quarter of customers have none. |
| chargecode | string(30) | Charge code shown beside the name in some screens. |
| displaynameread-only | string(255) | Generated by the database from the name and code. Read-only. |
| fullnameread-only | string(255) | Generated by the database. Read-only. |
| address1 | string(100) | Street address. |
| address2 | string(100) | Second address line. |
| city | string(50) | City of the billing address. |
| state | string(20) | State or province. |
| zip | string(15) | Postal code. |
| country | string(20) | Country of the billing address. |
| phone1 | string(20) | Primary phone. |
| email1 | string(255) | Primary email. Where invoices go unless email2sendinvoices says otherwise. |
| email2 | string(255) | Secondary email. |
| website | string(100) | Customer’s website URL. |
| creditlimitmoney | string | Zero means no limit. Stored as a double, not DECIMAL(19,4) - see Conventions on money. |
| invoicetermsid | integer | Terms id. Defaulted from the company setting when omitted on create. |
| invoiceterms | string(30) | Terms code, matched against invoiceterms.verbal. Send this or invoicetermsid, not both unless they agree. |
| currency | string(10) | Currency code for this customer. |
| nocredit | boolean | No credit allowed - this customer pays up front. |
| taxgroupid | integer | Tax group applied to the customer’s sales. |
| taxgroup | string(20) | Tax group code instead of the id. |
| taxexemptid | integer | Tax exemption id, 0 for none. |
| taxexempt | string(32) | Tax exemption name (taxexempt.exemptname) instead of the id. |
| salestaxnum | string(30) | Sales tax registration number. |
| salesmanid | integer | Salesperson id. |
| salesman | string(50) | Salesperson name (salesman.name) instead of the id. |
| salesglacctid | integer | Sales GL account id, 0 to use the company default. |
| salesglacct | string(20) | GL account NUMBER (glaccount.name) instead of the id. |
| groupid | integer | Customer group id. Id only - the table this points at is not identifiable from the column name, so there is no human key to send instead. |
| cancel | boolean | True once cancelled by DELETE. Hidden from lists unless you ask for it. |
| closed | boolean | The relationship has ended. Not the same as cancelled. |
| on_hold | boolean | Trading is suspended, usually for credit. Check this before shipping - an on-hold customer is not cancelled and will otherwise accept orders. |
| on_hold_dateread-only | string | When the hold was applied. |
| default_po | string(30) | Default PO number applied to this customer’s orders. |
| notes | text | Free-text notes. Stored as longtext, so there is no practical length limit. |
| externalid | string(100) | Your own key for this customer. Scoped to your company. |
| lastchangedateread-only | string | Last modification. Drives modifiedsince. |
curl -X POST \ 'https://acme.nolapro.com/!/api/v2/customers/batch' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: your-unique-key' \ -d '[ { "id": 1187, "companyname": "Summit Retail LLC", "companyname2": "", "customercode": "SUMMIT-01", "chargecode": "", "displayname": "Summit Retail LLC (SUMMIT-01)", "fullname": "Summit Retail LLC", "address1": "900 Industrial Pkwy", "address2": "", "city": "Fostoria", "state": "OH", "zip": "44830", "country": "US", "phone1": "419-435-0404", "email1": "ap@summitretail.example", "email2": "", "website": "", "creditlimit": "25000.0000", "invoicetermsid": 112, "invoiceterms": "Net 30", "currency": "USD", "nocredit": false, "taxgroupid": 4, "taxgroup": "OH-SEN", "taxexemptid": 0, "taxexempt": "Resale", "salestaxnum": "", "salesmanid": 3, "salesman": "Sales Person", "salesglacctid": 0, "salesglacct": "4000", "groupid": 0, "cancel": false, "closed": false, "on_hold": false, "default_po": "", "notes": "", "externalid": "crm-8842", "lastchangedate": "2026-07-31T14:02:11Z" } ]'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/batch'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => json_encode([ [ 'id' => 1187, 'companyname' => 'Summit Retail LLC', 'companyname2' => '', 'customercode' => 'SUMMIT-01', 'chargecode' => '', 'displayname' => 'Summit Retail LLC (SUMMIT-01)', 'fullname' => 'Summit Retail LLC', 'address1' => '900 Industrial Pkwy', 'address2' => '', 'city' => 'Fostoria', 'state' => 'OH', 'zip' => '44830', 'country' => 'US', 'phone1' => '419-435-0404', 'email1' => 'ap@summitretail.example', 'email2' => '', 'website' => '', 'creditlimit' => '25000.0000', 'invoicetermsid' => 112, 'invoiceterms' => 'Net 30', 'currency' => 'USD', 'nocredit' => false, 'taxgroupid' => 4, 'taxgroup' => 'OH-SEN', 'taxexemptid' => 0, 'taxexempt' => 'Resale', 'salestaxnum' => '', 'salesmanid' => 3, 'salesman' => 'Sales Person', 'salesglacctid' => 0, 'salesglacct' => '4000', 'groupid' => 0, 'cancel' => false, 'closed' => false, 'on_hold' => false, 'default_po' => '', 'notes' => '', 'externalid' => 'crm-8842', 'lastchangedate' => '2026-07-31T14:02:11Z', ], ]), ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.post( "https://acme.nolapro.com/!/api/v2/customers/batch", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, json=[ { "id": 1187, "companyname": "Summit Retail LLC", "companyname2": "", "customercode": "SUMMIT-01", "chargecode": "", "displayname": "Summit Retail LLC (SUMMIT-01)", "fullname": "Summit Retail LLC", "address1": "900 Industrial Pkwy", "address2": "", "city": "Fostoria", "state": "OH", "zip": "44830", "country": "US", "phone1": "419-435-0404", "email1": "ap@summitretail.example", "email2": "", "website": "", "creditlimit": "25000.0000", "invoicetermsid": 112, "invoiceterms": "Net 30", "currency": "USD", "nocredit": False, "taxgroupid": 4, "taxgroup": "OH-SEN", "taxexemptid": 0, "taxexempt": "Resale", "salestaxnum": "", "salesmanid": 3, "salesman": "Sales Person", "salesglacctid": 0, "salesglacct": "4000", "groupid": 0, "cancel": False, "closed": False, "on_hold": False, "default_po": "", "notes": "", "externalid": "crm-8842", "lastchangedate": "2026-07-31T14:02:11Z", }, ], ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/batch', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, body: JSON.stringify([ { id: 1187, companyname: "Summit Retail LLC", companyname2: "", customercode: "SUMMIT-01", chargecode: "", displayname: "Summit Retail LLC (SUMMIT-01)", fullname: "Summit Retail LLC", address1: "900 Industrial Pkwy", address2: "", city: "Fostoria", state: "OH", zip: "44830", country: "US", phone1: "419-435-0404", email1: "ap@summitretail.example", email2: "", website: "", creditlimit: "25000.0000", invoicetermsid: 112, invoiceterms: "Net 30", currency: "USD", nocredit: false, taxgroupid: 4, taxgroup: "OH-SEN", taxexemptid: 0, taxexempt: "Resale", salestaxnum: "", salesmanid: 3, salesman: "Sales Person", salesglacctid: 0, salesglacct: "4000", groupid: 0, cancel: false, closed: false, on_hold: false, default_po: "", notes: "", externalid: "crm-8842", lastchangedate: "2026-07-31T14:02:11Z", }, ]), } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var body = new[] { new { id = 1187, companyname = "Summit Retail LLC", companyname2 = "", customercode = "SUMMIT-01", chargecode = "", displayname = "Summit Retail LLC (SUMMIT-01)", fullname = "Summit Retail LLC", address1 = "900 Industrial Pkwy", address2 = "", city = "Fostoria", state = "OH", zip = "44830", country = "US", phone1 = "419-435-0404", email1 = "ap@summitretail.example", email2 = "", website = "", creditlimit = "25000.0000", invoicetermsid = 112, invoiceterms = "Net 30", currency = "USD", nocredit = false, taxgroupid = 4, taxgroup = "OH-SEN", taxexemptid = 0, taxexempt = "Resale", salestaxnum = "", salesmanid = 3, salesman = "Sales Person", salesglacctid = 0, salesglacct = "4000", groupid = 0, cancel = false, closed = false, on_hold = false, default_po = "", notes = "", externalid = "crm-8842", lastchangedate = "2026-07-31T14:02:11Z", }, }; var req = new HttpRequestMessage(HttpMethod.Post, "https://acme.nolapro.com/!/api/v2/customers/batch") { Content = JsonContent.Create(body), }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();
Open balance split into aging buckets.
Replaces v1's customer-aging action. Computed by Customer_model::getAgingSummary() - the AR Aging report's own code, so this and the report cannot disagree.
Requires scope customers:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
| asofdate | query | string | Defaults to today. |
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No customer with that id. |
curl \ 'https://acme.nolapro.com/!/api/v2/customers/104/aging' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104/aging'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/customers/104/aging", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104/aging', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/customers/104/aging"); res.EnsureSuccessStatusCode();
{
"aging": [
{
"buckets": [
{
"label": "Example label",
"fromdays": 1,
"todays": 1,
"amount": "125.0000"
}
]
}
]
}
Customer statement as a PDF
The account statement, covering the last twelve months.
Answers application/pdf, not JSON. Because the body is binary it cannot be carried inside a /batch results array; asking for it there returns 415 not_batchable.
Requires scope customers:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
curl \ 'https://acme.nolapro.com/!/api/v2/customers/104/statement' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104/statement'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/customers/104/statement", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104/statement', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/customers/104/statement"); res.EnsureSuccessStatusCode();
Customer invoice status as a PDF
Where each of the customer’s orders and invoices currently stands.
Answers application/pdf, not JSON. Because the body is binary it cannot be carried inside a /batch results array; asking for it there returns 415 not_batchable.
Requires scope customers:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
curl \ 'https://acme.nolapro.com/!/api/v2/customers/104/invoicestatus' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104/invoicestatus'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/customers/104/invoicestatus", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104/invoicestatus', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/customers/104/invoicestatus"); res.EnsureSuccessStatusCode();
List files attached to a customer.
The attachments on this customer, newest first. Accepted filedefinitionid values: 5, 60, 51, 52 (default 5). Metadata only - each file's bytes are at GET /attachments/{id}/content.
Requires scope customers:read.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
| filedefinitionid | query | integer | Only files in this list. Accepted filedefinitionid values: 5, 60, 51, 52 (default 5). |
| filecategoryid | query | integer | Only files filed under this category. |
| filename | query | string | Partial match on the file name, case insensitive. |
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found | No customer with that id in this company. |
curl \ 'https://acme.nolapro.com/!/api/v2/customers/104/attachments' \ -H 'Authorization: Bearer $NP_TOKEN'
$ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104/attachments'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN')], CURLOPT_RETURNTRANSFER => true, ]); $res = json_decode(curl_exec($ch), true);
import os, requests r = requests.get( "https://acme.nolapro.com/!/api/v2/customers/104/attachments", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}"}, ) r.raise_for_status()
const res = await fetch( 'https://acme.nolapro.com/!/api/v2/customers/104/attachments', { method: 'GET', headers: { Authorization: `Bearer ${process.env.NP_TOKEN}` }, } ); const data = await res.json();
using System.Net.Http.Json; var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); var res = await http.GetAsync( "https://acme.nolapro.com/!/api/v2/customers/104/attachments"); res.EnsureSuccessStatusCode();
Attach a file to a customer.
multipart/form-data with one file part; see the AttachmentUpload schema for the other parts. Accepted filedefinitionid values: 5, 60, 51, 52 (default 5). Answers 201 with the attachment's metadata and Location: /attachments/{id}. Check _meta.warnings for datatype_truncated and stored_under_parent_company.
Requires scope customers:write.
| Name | In | Type | Notes |
|---|---|---|---|
| idrequired | path | integer | The customer id. |
| Idempotency-Key | header | string | Makes the write safely retryable. Any unique string per logical operation; a UUID is fine.
The failure this prevents is specific: the request succeeds, the response is lost to a timeout, you retry, and the customer is billed twice. With a key, the retry returns the original response - same status, same body, same id - and creates nothing.
The body is part of the key's identity. Reusing a key with a different body is a bug on your side, not a retry, and answers 409 idempotency_replay rather than silently handing back the old response. Keys are remembered for 24 hours. |
| Field | Type | Description |
|---|---|---|
| filerequired | string | The file. Exactly one part named file; a second one, or file[], is 400 invalid_request so an idempotent retry can never half-succeed. Empty is refused. Over the cap (the smaller of APIV2_MAX_UPLOAD_BYTES, 64 MB by default, and PHP's upload_max_filesize / post_max_size) is 413 payload_too_large naming the limit that applied. |
| filename | string(255) | Overrides the part's file name. |
| notes | text | Free text shown beside the file. |
| filedefinitionid | integer | Only when the parent accepts more than one list. Anything the parent does not accept is 422 unknown_value naming the accepted ids. |
| filecategoryid | integer | Category by id. |
| filecategory | string(255) | Category by name. Sent with filecategoryid and disagreeing is 422 conflicting_reference. |
| Status | Code | Meaning |
|---|---|---|
| 400 | unknown_parameter | A form field that is not on the AttachmentUpload schema. |
| 400 | invalid_type | filedefinitionid or filecategoryid that is not an integer. |
| 404 | not_found | No customer with that id in this company. |
| 409 | upload_limit_reached | This file list allows a fixed number of files on a customer and it is full. Remove one first. |
| 413 | payload_too_large | The file is over the cap. The body names the limit that applied. |
| 415 | multipart_required | The body was not multipart/form-data. |
| 422 | unknown_value | filedefinitionid is not one this customer accepts, or the category does not exist in this company. |
curl -X POST \ 'https://acme.nolapro.com/!/api/v2/customers/104/attachments' \ -H 'Authorization: Bearer $NP_TOKEN' \ -H 'Idempotency-Key: your-unique-key' \ -F 'file=@grant-letter-signed.pdf' \ -F 'filename=grant-letter-signed.pdf' \ -F 'notes=Signed copy received 21 Sep' \ -F 'filedefinitionid=8' \ -F 'filecategory=Receipts'
$fields = [ 'filename' => 'grant-letter-signed.pdf', 'notes' => 'Signed copy received 21 Sep', 'filedefinitionid' => '8', 'filecategory' => 'Receipts', ]; $fields['file'] = new CURLFile('grant-letter-signed.pdf'); $ch = curl_init('https://acme.nolapro.com/!/api/v2/customers/104/attachments'); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('NP_TOKEN'), 'Idempotency-Key: your-unique-key'], CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $fields, ]); $res = json_decode(curl_exec($ch), true);
import os, requests with open("grant-letter-signed.pdf", "rb") as fh: r = requests.post( "https://acme.nolapro.com/!/api/v2/customers/104/attachments", headers={"Authorization": f"Bearer {os.environ['NP_TOKEN']}", "Idempotency-Key": "your-unique-key"}, files={"file": fh}, data={ "filename": "grant-letter-signed.pdf", "notes": "Signed copy received 21 Sep", "filedefinitionid": "8", "filecategory": "Receipts", }, ) r.raise_for_status()
import { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('file', await openAsBlob('grant-letter-signed.pdf'), 'grant-letter-signed.pdf');
form.append('filename', "grant-letter-signed.pdf");
form.append('notes', "Signed copy received 21 Sep");
form.append('filedefinitionid', "8");
form.append('filecategory', "Receipts");
const res = await fetch(
'https://acme.nolapro.com/!/api/v2/customers/104/attachments',
{
method: 'POST',
headers: { Authorization: `Bearer ${process.env.NP_TOKEN}`, 'Idempotency-Key': 'your-unique-key' },
body: form,
}
);
const data = await res.json();
var token = Environment.GetEnvironmentVariable("NP_TOKEN"); using var http = new HttpClient(); http.DefaultRequestHeaders.Add("Authorization", "Bearer " + token); using var form = new MultipartFormDataContent(); form.Add(new StreamContent(File.OpenRead("grant-letter-signed.pdf")), "file", "grant-letter-signed.pdf"); form.Add(new StringContent("grant-letter-signed.pdf"), "filename"); form.Add(new StringContent("Signed copy received 21 Sep"), "notes"); form.Add(new StringContent("8"), "filedefinitionid"); form.Add(new StringContent("Receipts"), "filecategory"); var req = new HttpRequestMessage(HttpMethod.Post, "https://acme.nolapro.com/!/api/v2/customers/104/attachments") { Content = form }; req.Headers.Add("Idempotency-Key", "your-unique-key"); var res = await http.SendAsync(req); res.EnsureSuccessStatusCode();