Skip to content

Suppliers ​

10 reads and 13 writes, on train 2026-11. Scopes: suppliers:read · suppliers:write.

OperationMethodScopePath
Create a contactPOSTsuppliers:write/api/v1/contacts
Delete a contactDELETEsuppliers:write/api/v1/contacts/{contact_id}
One contact, with the supplier it belongs toGETsuppliers:read/api/v1/contacts/{contact_id}
Change one contactPATCHsuppliers:write/api/v1/contacts/{contact_id}
Every note held against one contactGETsuppliers:read/api/v1/contacts/{contact_id}/notes
Write a note against a contactPOSTsuppliers:write/api/v1/contacts/{contact_id}/notes
Delete a note held against a contactDELETEsuppliers:write/api/v1/contacts/{contact_id}/notes/{note_id}
One note held against a contactGETsuppliers:read/api/v1/contacts/{contact_id}/notes/{note_id}
Change a note held against a contactPATCHsuppliers:write/api/v1/contacts/{contact_id}/notes/{note_id}
The values a contact filter can take in this organisationGETsuppliers:read/api/v1/contacts/filters
A page of supplier contactsGETsuppliers:read/api/v1/contacts/table
Create suppliers and their contacts in one callPOSTsuppliers:write/api/v1/inventory/suppliers
One supplier wholeGETsuppliers:read/api/v1/inventory/suppliers/{supplier_id}
Change one supplier's termsPUTsuppliers:write/api/v1/inventory/suppliers/{supplier_id}
What needs doing about one supplierGETsuppliers:read/api/v1/inventory/suppliers/{supplier_id}/needs-attention
One supplier's five scorecard figuresGETsuppliers:read/api/v1/inventory/suppliers/{supplier_id}/overview
Detach many variants from one supplierDELETEsuppliers:write/api/v1/inventory/suppliers/{supplier_id}/variants
Attach variants to a supplierPOSTsuppliers:write/api/v1/inventory/suppliers/{supplier_id}/variants
Detach one variant from one supplierDELETEsuppliers:write/api/v1/inventory/suppliers/{supplier_id}/variants/{variant_id}
The values a supplier filter can take in this organisationGETsuppliers:read/api/v1/inventory/suppliers/filters
A page of suppliers with their termsGETsuppliers:read/api/v1/inventory/suppliers/table
Promote the vendor names a catalogue sync has staged into suppliersPOSTsuppliers:write/api/v1/inventory/suppliers/transfer-vendors
Import suppliers and their primary contacts from a CSVPOSTsuppliers:write/api/v1/inventory/suppliers/upload

Create a contact ​

POST /api/v1/contacts

Scopes: suppliers:write

Creates one contact and answers 201 with it. name and preferred_contact_method are required; preferred_contact_method is EMAIL or PHONE, and the field it names has to be in the same body (an email for EMAIL, a phone for PHONE), or the call is refused 400.

OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X POST "https://api.app.tightly.io/api/v1/contacts" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "city": "Porto",
  "country": "PT",
  "department": "Sales",
  "email": "alex@portoknits.example",
  "name": "Alex Example",
  "phone": "+1 202 555 0100",
  "preferred_contact_method": "EMAIL",
  "role": "Account manager",
  "supplier_id": "sup_0031"
}

What it answers

json
{
  "data": {
    "city": "Porto",
    "country": "PT",
    "department": "Sales",
    "description": null,
    "email": "alex@portoknits.example",
    "id": "412",
    "is_primary_contact": true,
    "name": "Alex Example",
    "phone": "+1 202 555 0100",
    "preferred_contact_method": "email",
    "role": "Account manager",
    "supplier_id": "sup_0031",
    "supplier_name": "Porto Knits"
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 400 BAD_REQUEST: "Contact already exists. The email must be unique." A required field is missing, the preferred method names a field that is not there, or the email belongs to another contact.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Delete a contact ​

DELETE /api/v1/contacts/{contact_id}

Scopes: suppliers:write

Deletes one contact and answers 204 with no body. The contact's link to its supplier goes with it; the supplier does not.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X DELETE "https://api.app.tightly.io/api/v1/contacts/<contact_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

204, with no body. The contact is gone. No body.

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NO_RESULT_FOUND: "No result found: No row was found when one was required" No contact of this organisation has that id.
  • 429

Try it in the reference

One contact, with the supplier it belongs to ​

GET /api/v1/contacts/{contact_id}

Scopes: suppliers:read

One contact: name, email, phone, city, country, description, role, department, preferred contact method, and the supplier it belongs to by id and name, with is_primary_contact saying whether it is that supplier's main contact.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/contacts/<contact_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "city": "Porto",
    "country": "PT",
    "department": "Sales",
    "description": null,
    "email": "alex@portoknits.example",
    "id": "412",
    "is_primary_contact": true,
    "name": "Alex Example",
    "phone": "+1 202 555 0100",
    "preferred_contact_method": "email",
    "role": "Account manager",
    "supplier_id": "sup_0031",
    "supplier_name": "Porto Knits"
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 400 BAD_REQUEST: "'abc' is not a valid contact id." contact_id is not a whole number.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Contact with id 99999 not found" No contact of this organisation has that id.
  • 429

Try it in the reference

Change one contact ​

PATCH /api/v1/contacts/{contact_id}

Scopes: suppliers:write

Changes one contact and answers it whole. Only the fields in the body move: a field left out is untouched, and a field sent null is cleared. The two are different, which is why this is a PATCH and not a PUT.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X PATCH "https://api.app.tightly.io/api/v1/contacts/<contact_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "description": null,
  "phone": "+1 202 555 0101",
  "role": "Head of sales"
}

What it answers

json
{
  "data": {
    "city": "Porto",
    "country": "PT",
    "department": "Sales",
    "description": null,
    "email": "alex@portoknits.example",
    "id": "412",
    "is_primary_contact": true,
    "name": "Alex Example",
    "phone": "+1 202 555 0101",
    "preferred_contact_method": "email",
    "role": "Head of sales",
    "supplier_id": "sup_0031",
    "supplier_name": "Porto Knits"
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 400 BAD_REQUEST: "Contact already exists. The email must be unique." The preferred method would name a field the contact no longer carries, or the email belongs to another contact.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Every note held against one contact ​

GET /api/v1/contacts/{contact_id}/notes

Scopes: suppliers:read

Every note held against one contact, as an array: id, title, body, and the ISO-8601 instants the note was written and last changed. data is the array itself rather than an object wrapping one, and it is empty where the contact has no notes.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/contacts/<contact_id>/notes" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": [
    {
      "body": "Ana confirmed the knitwear block ships in two drops, the second a week later.",
      "created_at": "2026-09-01T10:04:11.512000+00:00",
      "id": "88",
      "title": "Chased the SS27 confirmation",
      "updated_at": "2026-09-01T10:04:11.512000+00:00"
    }
  ],
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Write a note against a contact ​

POST /api/v1/contacts/{contact_id}/notes

Scopes: suppliers:write

Writes one note against a contact and answers 201 with it, including the id and the two instants.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X POST "https://api.app.tightly.io/api/v1/contacts/<contact_id>/notes" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "body": "Ana confirmed the knitwear block ships in two drops, the second a week later.",
  "title": "Chased the SS27 confirmation"
}

What it answers

json
{
  "data": {
    "body": "Ana confirmed the knitwear block ships in two drops, the second a week later.",
    "created_at": "2026-09-01T10:04:11.512000+00:00",
    "id": "88",
    "title": "Chased the SS27 confirmation",
    "updated_at": "2026-09-01T10:04:11.512000+00:00"
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 400 VALIDATION_ERROR: "Body can have at most 200 words" title is over 200 characters, body is over 200 words, or one of them is missing.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Delete a note held against a contact ​

DELETE /api/v1/contacts/{contact_id}/notes/{note_id}

Scopes: suppliers:write

Deletes one note and answers 204 with no body. The contact is untouched.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
note_idpathThe note's id, a whole number, as Every note held against one contact serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X DELETE "https://api.app.tightly.io/api/v1/contacts/<contact_id>/notes/<note_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

204, with no body. The note is gone. No body.

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NO_RESULT_FOUND: "No result found: No row was found when one was required" No note has that id.
  • 429

Try it in the reference

One note held against a contact ​

GET /api/v1/contacts/{contact_id}/notes/{note_id}

Scopes: suppliers:read

One note: id, title, body, and the ISO-8601 instants it was written and last changed.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
note_idpathThe note's id, a whole number, as Every note held against one contact serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/contacts/<contact_id>/notes/<note_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "body": "Ana confirmed the knitwear block ships in two drops, the second a week later.",
    "created_at": "2026-09-01T10:04:11.512000+00:00",
    "id": "88",
    "title": "Chased the SS27 confirmation",
    "updated_at": "2026-09-01T10:04:11.512000+00:00"
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NO_RESULT_FOUND: "No result found: No row was found when one was required" No note has that id.
  • 429

Try it in the reference

Change a note held against a contact ​

PATCH /api/v1/contacts/{contact_id}/notes/{note_id}

Scopes: suppliers:write

Changes one note and answers it whole, with updated_at moved.

RequiredInWhat it is
contact_idpathThe contact's id, a whole number, as A page of supplier contacts serves it.
note_idpathThe note's id, a whole number, as Every note held against one contact serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X PATCH "https://api.app.tightly.io/api/v1/contacts/<contact_id>/notes/<note_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "body": "Ana confirmed two drops; the second slipped a further week on 4 September."
}

What it answers

json
{
  "data": {
    "body": "Ana confirmed two drops; the second slipped a further week on 4 September.",
    "created_at": "2026-09-01T10:04:11.512000+00:00",
    "id": "88",
    "title": "Chased the SS27 confirmation",
    "updated_at": "2026-09-04T08:31:02.190000+00:00"
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 400 VALIDATION_ERROR: "Title must be less than 200 characters" title is over 200 characters or body is over 200 words.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NO_RESULT_FOUND: "No result found: No row was found when one was required" No note has that id.
  • 429

Try it in the reference

The values a contact filter can take in this organisation ​

GET /api/v1/contacts/filters

Scopes: suppliers:read

The values a contact filter can take, read off the contacts this organisation actually has: roles, departments, countries, cities, suppliers, each {id, name}, and missing_fields, the field names for which at least one contact has nothing on file.

OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/contacts/filters" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "cities": [
      "Porto",
      "Prato"
    ],
    "countries": [
      "IT",
      "PT"
    ],
    "departments": [
      "Sales"
    ],
    "missing_fields": [
      "department",
      "phone"
    ],
    "preferred_contact_methods": [
      "email",
      "phone"
    ],
    "roles": [
      "Account manager",
      "Production"
    ],
    "suppliers": [
      {
        "id": "sup_0031",
        "name": "Porto Knits"
      },
      {
        "id": "sup_0044",
        "name": "Prato Wovens"
      }
    ]
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
    …
  }
}

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

A page of supplier contacts ​

GET /api/v1/contacts/table

Scopes: suppliers:read

A page of contacts across every supplier: name, email, phone, city, country, role, department, preferred contact method, the supplier each belongs to by id and name, and is_primary_contact. filtered_max_size is the size of the filtered set and max_size the size of the book.

OptionalInWhat it is
limitqueryRows per page. Out of range is refused 400.
offsetqueryRows to skip. Out of range is refused 400.
filter_argsqueryA JSON array of filter conditions, each {key, operation, value} with an optional group of and (the default) or or.
sort_argsqueryComma-separated sort columns, - for descending and + or nothing for ascending.
searchqueryFree text matched against the contact's name and email.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/contacts/table" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "filtered_max_size": 12,
    "max_size": 340,
    "offset": 0,
    "rows": [
      {
        "city": "Porto",
        "country": "PT",
        "department": "Sales",
        "description": null,
        "email": "alex@portoknits.example",
        "id": "412",
        "is_primary_contact": true,
        "name": "Alex Example",
        "phone": "+1 202 555 0100",
        "preferred_contact_method": "email",
        "role": "Account manager",
        "supplier_id": "sup_0031",
        "supplier_name": "Porto Knits"
      }
    ],
    "size": 1
  },
  "message": {
    "desc": "",
    "service": "contacts",
    "severity": "INFO"
  }
}

What it refuses

  • 400 VALIDATION_ERROR: "limit must be an integer between 1 and 10000" A pagination bound, a filter key, an operation or a value is not one this table takes.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Create suppliers and their contacts in one call ​

POST /api/v1/inventory/suppliers

Scopes: suppliers:write

Creates suppliers and answers 201 with each one as One supplier whole serves it. The body is {"suppliers": [...]}; every entry needs a name, and may carry the address, currency, min_order_value, lead_time, payment_terms_days, domains, a contacts list and a primary_contact.

OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X POST "https://api.app.tightly.io/api/v1/inventory/suppliers" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "suppliers": [
    {
      "address1": "Rua do Bolhao 114",
      "city": "Porto",
      "contacts": [
        {
          "department": "Sales",
          "email": "alex@portoknits.example",
          "name": "Alex Example",
          "phone": "+1 202 555 0100",
          "preferred_contact_method": "EMAIL",
          "role": "Account manager"
        }
      ],
      "country": "PT",
      "currency": "EUR",
      "domains": [
        "portoknits.example"
      ],
      "lead_time": 21,
      "min_order_value": 5000,
      "name": "Porto Knits",
      "payment_terms_days": 45,
      "postal_code": "4000-112"
    }
  ]
}

What it answers

json
{
  "data": {
    "suppliers": [
      {
        "address1": "Rua do Bolhao 114",
        "address2": null,
        "city": "Porto",
        "contacts": [
          {
            "city": null,
            "country": null,
            "department": "Sales",
            "description": null,
            "email": "alex@portoknits.example",
            "id": "412",
            "name": "Alex Example",
            "phone": "+1 202 555 0100",
            "preferred_contact_method": "email",
            "role": "Account manager",
            "supplier_id": "sup_0031"
          }
        ],
        "country": "PT",
        "currency": "EUR",
        "domains": [
          "portoknits.example"
        ],
        "id": "sup_0031",
        "lead_time": 21,
        "min_order_value": 5000,
        "name": "Porto Knits",
        "postal_code": "4000-112",
        "province": null
      }
    ]
  },
  "message": {
    "desc": "",
    "service": "UNKNOWN",
    "severity": "INFO"
    …
  }
}

What it refuses

  • 400 VALIDATION_ERROR: "Enter a domain like 'acme.example', or a full address like 'orders@acme.example'." An entry has no name, or a domains entry is neither a domain nor an address.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

One supplier whole ​

GET /api/v1/inventory/suppliers/{supplier_id}

Scopes: suppliers:read

One supplier, whole: identity and address, currency, minimum order value, payment terms in days, the resolved lead time and its source, every contact with the primary one named separately, the email domains that route this supplier's mail, the OTIF, on-time and in-full rates, and two views of its warehouses.

RequiredInWhat it is
supplier_idpathThe supplier's id, as A page of suppliers with their terms serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/inventory/suppliers/<supplier_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "address1": "Rua do Bolhao 114",
    "address2": null,
    "average_lead_time": {
      "info": "Average lead time",
      "key": "average_lead_time",
      "unit": "days",
      "value": 19
    },
    "city": "Porto",
    "contacts": [
      {
        "city": "Porto",
        "country": "PT",
        "department": "Sales",
        "description": null,
        "email": "alex@portoknits.example",
        "id": "412",
        "is_primary_contact": true,
        "name": "Alex Example",
        "phone": "+1 202 555 0100",
        "preferred_contact_method": "email",
        "role": "Account manager",
        "supplier_id": null,
        "supplier_name": null
      }
    ],
    "container_type_id": 2,
    "country": "PT",
    "currency": "EUR",
    "domains": [
      "portoknits.example"
    ],
    "id": "sup_0031",
    "in_full": 96,
    "integrations": {},
    "is_archived": false,
    "lanes": [
      {
        …
      }
    ]
  }
}

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Supplier with id sup_9999 not found" No supplier of this organisation has that id.
  • 429

Try it in the reference

Change one supplier's terms ​

PUT /api/v1/inventory/suppliers/{supplier_id}

Scopes: suppliers:write

Changes one supplier and answers it whole, as One supplier whole serves it. Send only the fields that move: omitted or null is left alone, with the exceptions below.

RequiredInWhat it is
supplier_idpathThe supplier's id, as A page of suppliers with their terms serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X PUT "https://api.app.tightly.io/api/v1/inventory/suppliers/<supplier_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "container_type_id": 2,
  "lanes": [
    {
      "container_type_id": null,
      "freight_per_container_cents": 412000,
      "lead_time": 21,
      "location_id": "loc_0004",
      "road_limit_kg": 24000
    }
  ],
  "min_order_value": 6000,
  "payment_terms_days": 60
}

What it answers

json
{
  "data": {
    "address1": "Rua do Bolhao 114",
    "address2": null,
    "average_lead_time": {
      "info": "Average lead time",
      "key": "average_lead_time",
      "unit": "days",
      "value": 19
    },
    "city": "Porto",
    "contacts": [],
    "container_type_id": 2,
    "country": "PT",
    "currency": "EUR",
    "domains": [
      "portoknits.example"
    ],
    "id": "sup_0031",
    "in_full": 96,
    "integrations": {},
    "is_archived": false,
    "lanes": [
      {
        "basis": "supplier",
        "container_name": "40ft high cube",
        "container_type_id": null,
        "freight_per_container_cents": 412000,
        "internal_cbm": 76.3,
        "lead_time": 21,
        "lead_time_source": "supplier_location",
        "location_id": "loc_0004",
        "location_name": "Leeds DC",
        "pallet_positions": 25,
        "payload_kg": 26700,
        "resolved_container_type_id": 2,
        "road_limit_kg": 24000,
        "usable_pct": 85
      }
    ]
    …
  }
}

What it refuses

  • 400 VALIDATION_ERROR: "Warehouse loc_0004 is in both 'lanes' and 'location_lead_times'. A lane carries its own lead time, so send each warehouse once." A warehouse is in both lanes and location_lead_times, or a named container type does not exist.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Supplier with id sup_9999 not found" No supplier of this organisation has that id.
  • 429

Try it in the reference

What needs doing about one supplier ​

GET /api/v1/inventory/suppliers/{supplier_id}/needs-attention

Scopes: suppliers:read

What is wrong with one supplier right now, in four sections.

RequiredInWhat it is
supplier_idpathThe supplier's id, as A page of suppliers with their terms serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/inventory/suppliers/<supplier_id>/needs-attention" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "purchase_orders": {
      "items": [
        {
          "email": null,
          "message_key": "expected_delivery_date_passed",
          "po_external_id": "PO-0000376",
          "po_id": "376",
          "status": "FullyConfirmed"
        },
        {
          "email": {
            "attachments": [],
            "body": null,
            "body_withheld": "Not shared with you",
            "clean_body": null,
            "date": "2026-09-02T08:41:00+00:00",
            "decision": null,
            "files": null,
            "html_body": null,
            "id": "90311",
            "is_read": true,
            "label": "received",
            "message_id": "AAMkAGI2TG93AAA=",
            "quoted_body": null,
            "read_at": "2026-09-02T08:42:11+00:00",
            "says": null,
            "sender_email": "alex@textiles.example",
            "sender_name": "Alex Example",
            "subject": "PO-0000412: sailing pushed to the 19th",
            "tag": null,
            "thread_id": "t_7c1f2ab0",
            "waits_on_you": false,
            "with_party": null
          },
          "message_key": "delayed",
          "po_external_id": "PO-0000412",
          "po_id": "412",
          "status": "Confirmed"
          …
        }
      ]
    }
  }
}

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Supplier with id sup_9999 not found" No supplier of this organisation has that id.
  • 429

Try it in the reference

One supplier's five scorecard figures ​

GET /api/v1/inventory/suppliers/{supplier_id}/overview

Scopes: suppliers:read

Five figures for one supplier, as an array: average_lead_time in days, total_pos_cost, every purchase order placed with them, in the organisation's currency, and the otif_score, on_time_score and in_full_score rates. Each carries key, unit, info and value.

RequiredInWhat it is
supplier_idpathThe supplier's id, as A page of suppliers with their terms serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/inventory/suppliers/<supplier_id>/overview" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": [
    {
      "info": "Average lead time",
      "key": "average_lead_time",
      "unit": "days",
      "value": 19
    },
    {
      "info": "Total spent money",
      "key": "total_pos_cost",
      "unit": "dollar",
      "value": 486320
    },
    {
      "info": "OTIF score",
      "key": "otif_score",
      "unit": "percentage",
      "value": 91.4
    },
    {
      "info": "On time score",
      "key": "on_time_score",
      "unit": "percentage",
      "value": 94.2
    },
    {
      "info": "In full score",
      "key": "in_full_score",
      "unit": "percentage",
      "value": 96
    }
  ],
  "message": {
    "desc": "",
    "service": "UNKNOWN",
    "severity": "INFO"
  }
}

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Supplier with id sup_9999 not found" No supplier of this organisation has that id.
  • 429

Try it in the reference

Detach many variants from one supplier ​

DELETE /api/v1/inventory/suppliers/{supplier_id}/variants

Scopes: suppliers:write

Detaches many variants from one supplier and answers 204 with no body.

RequiredInWhat it is
supplier_idpathThe supplier's id, as A page of suppliers with their terms serves it.
OptionalInWhat it is
variant_idsqueryComma-separated variant ids, added to any variant_ids in the body. Omit it when the body carries the list.
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X DELETE "https://api.app.tightly.io/api/v1/inventory/suppliers/<supplier_id>/variants" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "variant_ids": [
    "var_88120",
    "var_88121"
  ]
}

What it answers

204, with no body. The pairs are gone. No body.

What it refuses

  • 400 VALIDATION_ERROR: "Unsupported operation 'between' for key 'unit_cost'." A filter key, operation or value is not one the variants table takes.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Supplier with id sup_9999 not found" No supplier of this organisation has that id.
  • 429

Try it in the reference

Attach variants to a supplier ​

POST /api/v1/inventory/suppliers/{supplier_id}/variants

Scopes: suppliers:write

Attaches variants to a supplier and answers 201 with the variant-supplier rows that now exist for the ids named: unit_cost, currency, min_order_quantity, batch_size, lead_time, where the lead time came from, and whether this supplier is that variant's default.

RequiredInWhat it is
supplier_idpathThe supplier's id, as A page of suppliers with their terms serves it.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X POST "https://api.app.tightly.io/api/v1/inventory/suppliers/<supplier_id>/variants" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

What to send, as body.json

json
{
  "variant_ids": [
    "var_88120",
    "var_88121"
  ]
}

What it answers

json
{
  "data": {
    "variant_suppliers": [
      {
        "batch_size": null,
        "currency": "EUR",
        "is_default": true,
        "lead_time": 21,
        "lead_time_source": "supplier",
        "min_order_quantity": 24,
        "supplier_id": "sup_0031",
        "unit_cost": 11.4,
        "variant_id": "var_88120"
      }
    ]
  },
  "message": {
    "desc": "",
    "service": "UNKNOWN",
    "severity": "INFO"
  }
}

What it refuses

  • 400 VALIDATION_ERROR: "Unsupported operation 'between' for key 'unit_cost'." A filter key, operation or value is not one the variants table takes.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Supplier with id sup_9999 not found" No supplier of this organisation has that id.
  • 429

Try it in the reference

Detach one variant from one supplier ​

DELETE /api/v1/inventory/suppliers/{supplier_id}/variants/{variant_id}

Scopes: suppliers:write

Detaches one variant from one supplier and answers 204 with no body. The variant's other suppliers are untouched, and if the pair removed was that variant's default, the first supplier it still has becomes the default rather than the variant being left with none.

RequiredInWhat it is
supplier_idpathThe supplier's id, as A page of suppliers with their terms serves it.
variant_idpathThe variant to detach from this supplier.
OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X DELETE "https://api.app.tightly.io/api/v1/inventory/suppliers/<supplier_id>/variants/<variant_id>" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

204, with no body. The pair is detached. No body.

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 404 NOT_FOUND: "Variant with id var_0007 not found for supplier sup_0031" No supplier of this organisation has that id, or this supplier does not carry that variant. The second is what a repeated delete answers.
  • 429

Try it in the reference

The values a supplier filter can take in this organisation ​

GET /api/v1/inventory/suppliers/filters

Scopes: suppliers:read

The values a supplier filter can take, read off the suppliers this organisation actually has: countries, currencies, cities, supplier_names, and missing_fields, the field names for which at least one supplier has nothing on file.

OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/inventory/suppliers/filters" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "cities": [
      "Izmir",
      "Porto",
      "Prato"
    ],
    "countries": [
      "IT",
      "PT",
      "TR"
    ],
    "currencies": [
      "EUR",
      "GBP"
    ],
    "missing_fields": [
      "min_order_value",
      "postal_code"
    ],
    "supplier_names": [
      "Porto Knits",
      "Prato Wovens",
      "Izmir Jersey"
    ]
  },
  "message": {
    "desc": "",
    "service": "inventory",
    "severity": "INFO"
  }
}

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

A page of suppliers with their terms ​

GET /api/v1/inventory/suppliers/table

Scopes: suppliers:read

A page of suppliers: identity and address, currency, minimum order value, the resolved lead time and where it came from, the primary contact, the per-warehouse lead times, and the OTIF, on-time and in-full rates. filtered_max_size is the size of the filtered set and max_size the size of the book, so a client can page without counting.

OptionalInWhat it is
counts_onlyqueryReturn filtered_max_size and max_size without rows, delivery scoring or warehouse enrichment.
limitqueryRows per page. Out of range is refused 400.
offsetqueryRows to skip. Out of range is refused 400.
filter_argsqueryA JSON array of filter conditions, each {key, operation, value} with an optional group of and (the default) or or.
sort_argsqueryComma-separated sort columns, - for descending and + or nothing for ascending.
searchqueryFree text matched against the supplier's name and address.
Tightly-VersionheaderThe date train to answer on.
bash
curl "https://api.app.tightly.io/api/v1/inventory/suppliers/table" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

json
{
  "data": {
    "filtered_max_size": 38,
    "max_size": 214,
    "offset": 0,
    "rows": [
      {
        "address1": "Rua do Bolhao 114",
        "address2": null,
        "average_lead_time": null,
        "city": "Porto",
        "contacts": [],
        "container_type_id": null,
        "country": "PT",
        "currency": "EUR",
        "domains": [],
        "id": "sup_0031",
        "in_full": 96,
        "integrations": {},
        "is_archived": false,
        "lanes": null,
        "lead_time": 21,
        "lead_time_source": "supplier",
        "location_lead_times": [
          {
            "lead_time": 21,
            "location_id": "loc_0004",
            "location_name": "Leeds DC",
            "source": "supplier_location"
          }
        ],
        "min_order_value": 5000,
        "name": "Porto Knits",
        "on_time": 94.2,
        "otif_score": 91.4,
        "payment_terms_days": null,
        "postal_code": "4000-112",
        "primary_contact": {
          "city": null,
          "country": null
          …
        }
      }
    ]
  }
}

What it refuses

  • 400 VALIDATION_ERROR: "limit must be an integer between 1 and 10000" A pagination bound, a filter key, an operation or a value is not one this table takes.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot read Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Promote the vendor names a catalogue sync has staged into suppliers ​

POST /api/v1/inventory/suppliers/transfer-vendors

Scopes: suppliers:write

Promotes the vendor names an e-commerce catalogue sync has staged into real suppliers, and answers 200 with the plain body ok, not JSON, and not the {message, data} envelope. It takes no body and no parameters.

OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X POST "https://api.app.tightly.io/api/v1/inventory/suppliers/transfer-vendors" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY"

What it answers

200, text/html. The staged vendors were promoted. The body is the two characters ok.

text
ok

What it refuses

  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Import suppliers and their primary contacts from a CSV ​

POST /api/v1/inventory/suppliers/upload

Scopes: suppliers:write

Uploads a CSV of suppliers and their primary contacts. Send it as multipart/form-data under the field name file, with a content type of text/csv or text/plain; the delimiter is sniffed rather than assumed, so a semicolon or a tab file is read as readily as a comma one.

OptionalInWhat it is
Tightly-VersionheaderThe date train to answer on.
Idempotency-KeyheaderA string of your own, up to 255 characters, that makes this write safe to retry.
bash
curl -X POST "https://api.app.tightly.io/api/v1/inventory/suppliers/upload" \
  -H "Authorization: Bearer $TIGHTLY_API_KEY" \
  -H "Content-Type: multipart/form-data" \
  --data-binary @body.bin

What it answers

json
{
  "status": "success"
}

What it refuses

  • 400 No file, a content type that is not text/csv or text/plain, a missing required column, a file that cannot be parsed, or a file with no usable row. The first two answer {"error": ...} from the route itself; the rest answer the platform's error envelope.
  • 401 key_invalid: "The API key is not valid." No usable key: malformed, unknown, revoked, expired or stopped. One refusal covers all five, telling a caller which is which maps the surface for them. A request with no Authorization header at all is refused 400 before any key is looked for.
  • 403 scope_missing: "This key cannot write Suppliers." The key may not reach this operation. scope_missing when the key does not hold Suppliers; ip_not_allowed when the caller's address is outside the key's allowlist. There is no organization_mismatch on this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with.
  • 429

Try it in the reference

Tightly API, version 2026-11.