Appearance
Suppliers
10 reads and 13 writes, on train 2026-11. Scopes: suppliers:read · suppliers:write.
| Operation | Method | Scope | Path |
|---|---|---|---|
| Create a contact | POST | suppliers:write | /api/v1/contacts |
| Delete a contact | DELETE | suppliers:write | /api/v1/contacts/{contact_id} |
| One contact, with the supplier it belongs to | GET | suppliers:read | /api/v1/contacts/{contact_id} |
| Change one contact | PATCH | suppliers:write | /api/v1/contacts/{contact_id} |
| Every note held against one contact | GET | suppliers:read | /api/v1/contacts/{contact_id}/notes |
| Write a note against a contact | POST | suppliers:write | /api/v1/contacts/{contact_id}/notes |
| Delete a note held against a contact | DELETE | suppliers:write | /api/v1/contacts/{contact_id}/notes/{note_id} |
| One note held against a contact | GET | suppliers:read | /api/v1/contacts/{contact_id}/notes/{note_id} |
| Change a note held against a contact | PATCH | suppliers:write | /api/v1/contacts/{contact_id}/notes/{note_id} |
| The values a contact filter can take in this organisation | GET | suppliers:read | /api/v1/contacts/filters |
| A page of supplier contacts | GET | suppliers:read | /api/v1/contacts/table |
| Create suppliers and their contacts in one call | POST | suppliers:write | /api/v1/inventory/suppliers |
| One supplier whole | GET | suppliers:read | /api/v1/inventory/suppliers/{supplier_id} |
| Change one supplier's terms | PUT | suppliers:write | /api/v1/inventory/suppliers/{supplier_id} |
| What needs doing about one supplier | GET | suppliers:read | /api/v1/inventory/suppliers/{supplier_id}/needs-attention |
| One supplier's five scorecard figures | GET | suppliers:read | /api/v1/inventory/suppliers/{supplier_id}/overview |
| Detach many variants from one supplier | DELETE | suppliers:write | /api/v1/inventory/suppliers/{supplier_id}/variants |
| Attach variants to a supplier | POST | suppliers:write | /api/v1/inventory/suppliers/{supplier_id}/variants |
| Detach one variant from one supplier | DELETE | suppliers:write | /api/v1/inventory/suppliers/{supplier_id}/variants/{variant_id} |
| The values a supplier filter can take in this organisation | GET | suppliers:read | /api/v1/inventory/suppliers/filters |
| A page of suppliers with their terms | GET | suppliers:read | /api/v1/inventory/suppliers/table |
| Promote the vendor names a catalogue sync has staged into suppliers | POST | suppliers:write | /api/v1/inventory/suppliers/transfer-vendors |
| Import suppliers and their primary contacts from a CSV | POST | suppliers: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.
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_idis 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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"titleis over 200 characters,bodyis 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
note_id | path | The note's id, a whole number, as Every note held against one contact serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
note_id | path | The note's id, a whole number, as Every note held against one contact serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
contact_id | path | The contact's id, a whole number, as A page of supplier contacts serves it. |
note_id | path | The note's id, a whole number, as Every note held against one contact serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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"titleis over 200 characters orbodyis 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Optional | In | What it is |
|---|---|---|
limit | query | Rows per page. Out of range is refused 400. |
offset | query | Rows to skip. Out of range is refused 400. |
filter_args | query | A JSON array of filter conditions, each {key, operation, value} with an optional group of and (the default) or or. |
sort_args | query | Comma-separated sort columns, - for descending and + or nothing for ascending. |
search | query | Free text matched against the contact's name and email. |
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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 noname, or adomainsentry 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Required | In | What it is |
|---|---|---|
supplier_id | path | The supplier's id, as A page of suppliers with their terms serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
supplier_id | path | The supplier's id, as A page of suppliers with their terms serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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 bothlanesandlocation_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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
supplier_id | path | The supplier's id, as A page of suppliers with their terms serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
supplier_id | path | The supplier's id, as A page of suppliers with their terms serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
supplier_id | path | The supplier's id, as A page of suppliers with their terms serves it. |
| Optional | In | What it is |
|---|---|---|
variant_ids | query | Comma-separated variant ids, added to any variant_ids in the body. Omit it when the body carries the list. |
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
supplier_id | path | The supplier's id, as A page of suppliers with their terms serves it. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.jsonWhat 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Required | In | What it is |
|---|---|---|
supplier_id | path | The supplier's id, as A page of suppliers with their terms serves it. |
variant_id | path | The variant to detach from this supplier. |
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon 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
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.
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Optional | In | What it is |
|---|---|---|
counts_only | query | Return filtered_max_size and max_size without rows, delivery scoring or warehouse enrichment. |
limit | query | Rows per page. Out of range is refused 400. |
offset | query | Rows to skip. Out of range is refused 400. |
filter_args | query | A JSON array of filter conditions, each {key, operation, value} with an optional group of and (the default) or or. |
sort_args | query | Comma-separated sort columns, - for descending and + or nothing for ascending. |
search | query | Free text matched against the supplier's name and address. |
Tightly-Version | header | The 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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
okWhat 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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429
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.
| Optional | In | What it is |
|---|---|---|
Tightly-Version | header | The date train to answer on. |
Idempotency-Key | header | A 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.binWhat it answers
json
{
"status": "success"
}What it refuses
- 400 No file, a content type that is not
text/csvortext/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_missingwhen the key does not hold Suppliers;ip_not_allowedwhen the caller's address is outside the key's allowlist. There is noorganization_mismatchon this resource: no supplier or contact route names an organisation in its path, so there is nothing for a key to disagree with. - 429