API reference
Interchange API reference
Version 1.0.0-draft.1 of the specification, in OpenAPI 3.1.0. This page is generated from the specification when the site is built, so it cannot disagree with it.
Overview
Interchange maps every brand's part number for the same specification to one canonical item with a permanent IX ID, for example IX-CAM-C-200-AL. This API returns those mappings, the specification behind each, who stocks them, and the confidence and review history of every claim.
- Answers are the same for everyone. Plan level changes how much you may call and which extra features you can use. It never changes which parts match, in what order suppliers appear, or how a correction is handled.
- We would rather return nothing than a wrong match. When no reviewed mapping exists the API says so (
no_verified_match) and does not return the nearest part. - Nothing machine-suggested is returned. Only reviewed, published mappings are visible.
- Safety information is data. Known confusions, such as BSPP against NPT threads, come back as
safety_flags. If you show a result to a person, show its safety flags and itsusage.disclaimer. - You build and run your own integration. Interchange publishes this specification, the documentation and a sandbox. See the support boundary and the API terms.
Authentication
Read endpoints work without a key, at a low per-address rate. Send an API key as a bearer token for higher limits and for the keyed endpoints. Keys beginning ix_test_ reach the sandbox data set only; keys beginning ix_live_ reach reviewed data.
Data sets
Every response carries meta.data_set. While Interchange is being built it is sandbox: illustrative data that must not be relied on for a purchase. It becomes reviewed when human-reviewed data is published.
Versioning and change
The version is in the path (/v1). Within a version we only add: new endpoints, new optional parameters, new response fields and new enum values. Treat unknown fields as ignorable and unknown enum values as "other". Response enumerations are marked x-extensible-enum: they list the values that exist today and may gain members. Only request bodies reject unknown fields. Removing or changing the meaning of anything needs a new version, with the notice period in the support boundary document.
Caching
Responses carry Cache-Control. You may cache a response for up to 12 hours. A correction or withdrawal must reach what you show within 24 hours of the change, so honour the header and, if you subscribe to the change feed or webhooks, apply events as they arrive. The 12-hour limit leaves the other 12 hours for the change to reach your cache and your screens.
Request IDs
Every response has an Interchange-Request-Id header. Quote it when you contact support.
Rate limits
Calls without a key are limited per address. Calls with a key are limited per key, and the limit depends on the plan. A plan changes how much you may call, never what an answer says.
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Every response carries the headers below, and a 429 also carries Retry-After. Read the limits from the headers and do not hard-code them: the numbers are not fixed yet, and they will be published here with the sandbox.
Rate-Limit-Limit- Requests allowed in the current window for your address or key.
Rate-Limit-Remaining- Requests left in the current window.
Rate-Limit-Reset- Seconds until the window resets.
Retry-After- Seconds to wait. Sent with a 429.
Servers
- Production will be at
https://api.interchangeparts.com.au. Reviewed data once published, sandbox data before that. - Sandbox will be at
https://sandbox.api.interchangeparts.com.au. Illustrative data only, safe to build against.
Use the sandbox while you build.
Errors
Errors are RFC 9457 problem details with the content type application/problem+json. Branch on code, which is stable, not on title or detail, which are wording. Treat a code you do not know as an error of its status class.
| Code | Status | Meaning | What to do |
|---|---|---|---|
invalid_request | 400 | The request is malformed: a missing or badly formed parameter or body. | Fix the request. Retrying the same request will fail the same way. |
unknown_attribute | 400 | An attribute key is not in the family's vocabulary. | Read the vocabulary from the family endpoint and use its keys. |
invalid_attribute_value | 400 | An attribute value is outside what the family accepts. | Use one of the values the family endpoint lists for that attribute. |
not_found | 404 | No item has this ID. | Check the ID. An ID that has never existed is a 404. A withdrawn one is a 410. |
withdrawn | 410 | The item was withdrawn, usually because its mapping was found to be wrong or unsafe. | Stop showing the item. If replaced_by is present, look that item up instead. |
unauthorized | 401 | A key is required, or the key sent is not valid. | Send a valid key as a bearer token. If it was valid before, it may have been revoked. |
plan_does_not_include | 403 | The key is valid but its plan does not include this endpoint. | Do not retry. The plan decides which keyed endpoints you may call, never what an answer says. |
rate_limited | 429 | Over the limit for your address or key. | Wait for the number of seconds in Retry-After, then try again. |
method_not_allowed | 405 | That address does not accept this HTTP method. | Use the method the reference lists for this operation. The Allow header names the ones it accepts. |
payload_too_large | 413 | The request body is larger than the limit for this operation. | Send fewer rows per request and repeat the call for the rest. |
internal_error | 500 | Something went wrong on our side. | Retry with backoff. If it persists, send us the Interchange-Request-Id. |
Every error carries request_id, the same value as the Interchange-Request-Id header. Quote it when you write to us.
Search
Find items from a part number or a description.
Search by part number or description
GET /v1/search operationId searchParts
Finds candidate items. An exact manufacturer or supplier part number outranks a pattern match, and a pattern match outranks a text match, and identified_by says which applied. Candidates are not equivalences: call getEquivalents for those.
Works without a key. A key raises the limits.
Parameters
| Name | In | Required | Type | Description | Example |
|---|---|---|---|---|---|
q | query | Yes | string | A part number from any brand, or a short description. Length 2 to 200. | ALDC200C |
brand | query | No | string | Restrict part-number matching to one brand. Length 0 to 80. | |
limit | query | No | integer | Page size. 1 to 25. Default 10. |
Responses
200 Success
Candidates, or an explicit no verified match.
Body: SearchResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
query | string | Yes | |
status | string: matched, no_verified_match, and others may be added | Yes | |
reason | string | No | Present when status is no_verified_match. |
results | array of SearchResult | Yes | |
usage | Usage | Yes |
{
"meta": {
"api_version": "v1",
"data_set": "sandbox"
},
"query": "ALDC200C",
"status": "matched",
"results": [
{
"ix_id": "IX-CAM-C-200-AL",
"name": "Camlock Type C, 2\" (50 mm), aluminium",
"identified_by": "exact_code",
"matched_on": {
"brand": "Dixon",
"part_number": "ALDC200C"
},
"confidence": "verified",
"safety_flags": []
}
],
"usage": {
"attribution": "Source: Interchange (interchangeparts.com.au)",
"disclaimer": "Cross-reference only. Check the manufacturer's current specification, rating and media compatibility before use."
}
}{
"meta": {
"api_version": "v1",
"data_set": "sandbox"
},
"query": "ZZ-9999",
"status": "no_verified_match",
"reason": "No reviewed item has this part number or description.",
"results": [],
"usage": {
"attribution": "Source: Interchange (interchangeparts.com.au)",
"disclaimer": "Cross-reference only. Check the manufacturer's current specification, rating and media compatibility before use."
}
}400 Error
The request is malformed or a value is outside what the family accepts.
Body: Problem (as application/problem+json)
429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s "https://api.interchangeparts.com.au/v1/search?q=ALDC200C"const response = await fetch("https://api.interchangeparts.com.au/v1/search?q=ALDC200C");
const body = await response.json();
console.log(response.status, body);import requests
response = requests.get(
"https://api.interchangeparts.com.au/v1/search",
params={
"q": "ALDC200C",
},
)
print(response.status_code, response.json())Items
One canonical item, its equivalents and its suppliers.
Get one item
GET /v1/items/{ix_id} operationId getItem
The specification, attributes and safety flags of one canonical item. A retired item still resolves and points to its successor in replaced_by. A withdrawn item does not resolve: it answers 410 Gone and must no longer be shown.
Works without a key. A key raises the limits.
Parameters
| Name | In | Required | Type | Description | Example |
|---|---|---|---|---|---|
ix_id | path | Yes | IxId | An Interchange ID. Treat it as an opaque key: store it and send it back, but never parse it to make decisions. Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ | IX-CAM-C-200-AL |
Responses
200 Success
The item.
Body: ItemResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
{
"meta": {
"api_version": "v1",
"data_set": "sandbox"
},
"item": {
"ix_id": "IX-CAM-C-200-AL",
"name": "Camlock Type C, 2\" (50 mm), aluminium",
"family": "camlock",
"status": "published",
"attributes": {
"type": {
"value": "C",
"label": "Type",
"detail": "Female coupler x hose tail"
},
"size": {
"value": 50,
"unit": "mm",
"label": "Nominal size"
},
"bodyMaterial": {
"value": "aluminium",
"label": "Body material"
},
"grooveStandard": {
"value": "A-A-59326",
"label": "Groove standard"
}
},
"safety_flags": [],
"reviewed": "2026-10-28",
"url": "https://interchangeparts.com.au/items/IX-CAM-C-200-AL"
},
"usage": {
"attribution": "Source: Interchange (interchangeparts.com.au)",
"disclaimer": "Cross-reference only. Check the manufacturer's current specification, rating and media compatibility before use."
}
}404 Error
No item has this ID.
Body: Problem (as application/problem+json)
{
"type": "https://interchangeparts.com.au/problems/not-found",
"title": "Item not found",
"status": 404,
"code": "not_found",
"detail": "No item has the ID IX-NOPE-X-000-ZZ.",
"request_id": "req_7c1f3a"
}410 Error
The item was withdrawn, usually because its mapping was found to be wrong or unsafe. It is not returned and must no longer be shown. If a corrected item replaces it, replaced_by names it.
Body: Problem (as application/problem+json)
{
"type": "https://interchangeparts.com.au/problems/withdrawn",
"title": "Item withdrawn",
"status": 410,
"code": "withdrawn",
"detail": "This item was withdrawn after a correction.",
"replaced_by": "IX-CAM-C-200-AL",
"request_id": "req_7c1f3b"
}429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s "https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL"const response = await fetch("https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL");
const body = await response.json();
console.log(response.status, body);import requests
response = requests.get("https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL")
print(response.status_code, response.json())Every brand's part mapped to an item
GET /v1/items/{ix_id}/equivalents operationId getEquivalents
Exact equivalents are parts that map to this item. Near matches are different items that fit the same way but differ in a stated attribute, such as material, and are always listed apart from exact equivalents. Equivalents are sorted by brand name, A to Z. They are never ordered by plan or payment.
Works without a key. A key raises the limits.
Parameters
| Name | In | Required | Type | Description | Example |
|---|---|---|---|---|---|
ix_id | path | Yes | IxId | An Interchange ID. Treat it as an opaque key: store it and send it back, but never parse it to make decisions. Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ | IX-CAM-C-200-AL |
include_near | query | No | boolean | Also return near matches. Default false. |
Responses
200 Success
Equivalents and, if asked for, near matches.
Body: EquivalentsResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
name | string | Yes | |
status | string: matched, no_verified_match, and others may be added | Yes | no_verified_match when the item exists but has no reviewed equivalents yet. |
reason | string | No | |
replaced_by | IxId or null | No | |
equivalents | array of Equivalent | Yes | Sorted by brand, A to Z. |
near_matches | array of NearMatch | No | Present only when include_near is true. |
safety_flags | array of SafetyFlag | Yes | |
suppliers_url | string (uri) | No | |
usage | Usage | Yes |
{
"meta": {
"api_version": "v1",
"data_set": "sandbox"
},
"ix_id": "IX-CAM-C-200-AL",
"name": "Camlock Type C, 2\" (50 mm), aluminium",
"status": "matched",
"equivalents": [
{
"brand": "Alfagomma",
"part_number": "CC-200-AL",
"confidence": "verified",
"reviewed": "2026-10-28",
"evidence": "standard"
},
{
"brand": "Dixon",
"part_number": "ALDC200C",
"confidence": "verified",
"reviewed": "2026-10-28",
"evidence": "standard"
}
],
"near_matches": [
{
"ix_id": "IX-CAM-C-200-SS6",
"name": "Camlock Type C, 2\" (50 mm), 316 stainless",
"same_fit": true,
"differences": [
{
"attribute": "bodyMaterial",
"this_value": "aluminium",
"other_value": "stainless-316"
}
]
}
],
"safety_flags": [],
"suppliers_url": "https://interchangeparts.com.au/items/IX-CAM-C-200-AL#suppliers",
"usage": {
"attribution": "Source: Interchange (interchangeparts.com.au)",
"disclaimer": "Cross-reference only. Check the manufacturer's current specification, rating and media compatibility before use."
}
}404 Error
No item has this ID.
Body: Problem (as application/problem+json)
{
"type": "https://interchangeparts.com.au/problems/not-found",
"title": "Item not found",
"status": 404,
"code": "not_found",
"detail": "No item has the ID IX-NOPE-X-000-ZZ.",
"request_id": "req_7c1f3a"
}410 Error
The item was withdrawn, usually because its mapping was found to be wrong or unsafe. It is not returned and must no longer be shown. If a corrected item replaces it, replaced_by names it.
Body: Problem (as application/problem+json)
{
"type": "https://interchangeparts.com.au/problems/withdrawn",
"title": "Item withdrawn",
"status": 410,
"code": "withdrawn",
"detail": "This item was withdrawn after a correction.",
"replaced_by": "IX-CAM-C-200-AL",
"request_id": "req_7c1f3b"
}429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s "https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL/equivalents"const response = await fetch("https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL/equivalents");
const body = await response.json();
console.log(response.status, body);import requests
response = requests.get("https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL/equivalents")
print(response.status_code, response.json())Suppliers who list an item
GET /v1/items/{ix_id}/suppliers operationId listSuppliers
Suppliers are always sorted by name, A to Z, and a supplier's plan is never exposed or used for ordering. Stock and price are per branch, so a branch in one state never shows stock held in another. They appear only where the supplier chooses to share them.
Works without a key. A key raises the limits.
Parameters
| Name | In | Required | Type | Description | Example |
|---|---|---|---|---|---|
ix_id | path | Yes | IxId | An Interchange ID. Treat it as an opaque key: store it and send it back, but never parse it to make decisions. Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ | IX-CAM-C-200-AL |
state | query | No | AuState | Restrict to suppliers with a branch in this state or territory. | |
limit | query | No | integer | Page size. 1 to 25. Default 10. | |
cursor | query | No | string | The next_cursor of the previous page. Length 0 to 500. |
Responses
200 Success
One page of suppliers.
Body: SuppliersResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
404 Error
No item has this ID.
Body: Problem (as application/problem+json)
{
"type": "https://interchangeparts.com.au/problems/not-found",
"title": "Item not found",
"status": 404,
"code": "not_found",
"detail": "No item has the ID IX-NOPE-X-000-ZZ.",
"request_id": "req_7c1f3a"
}410 Error
The item was withdrawn, usually because its mapping was found to be wrong or unsafe. It is not returned and must no longer be shown. If a corrected item replaces it, replaced_by names it.
Body: Problem (as application/problem+json)
{
"type": "https://interchangeparts.com.au/problems/withdrawn",
"title": "Item withdrawn",
"status": 410,
"code": "withdrawn",
"detail": "This item was withdrawn after a correction.",
"replaced_by": "IX-CAM-C-200-AL",
"request_id": "req_7c1f3b"
}429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s "https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL/suppliers"const response = await fetch("https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL/suppliers");
const body = await response.json();
console.log(response.status, body);import requests
response = requests.get("https://api.interchangeparts.com.au/v1/items/IX-CAM-C-200-AL/suppliers")
print(response.status_code, response.json())Matching
Match what you know about a part, in bulk or one at a time.
Match what you know about a part
POST /v1/match/attributes operationId matchAttributes
For a client that has read a fitting, for example from a photo, and wants the item it could be. Send the attributes you are sure of. The response never fills a gap by guessing: where the answer depends on an attribute you did not send, status is needs_confirmation and confirm holds the questions to put to the person. Ask them and call again with the answers.
Works without a key. A key raises the limits.
Request body
application/json, MatchAttributesRequest, required
| Field | Type | Required | Description |
|---|---|---|---|
family | string | Yes | Pattern ^[a-z][a-z0-9-]{1,30}$ |
attributes | object of string or number or boolean | Yes | Only what you are sure of. Keys and values are listed at /v1/families/{family}. |
limit | integer | No | 1 to 25. Default 10. |
{
"family": "camlock",
"attributes": {
"type": "C",
"size": 50,
"bodyMaterial": "aluminium"
}
}Responses
200 Success
Candidates, questions, or an explicit no verified match.
Body: MatchAttributesResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
status | string: matched, needs_confirmation, no_verified_match, and others may be added | Yes | matched: one item fits everything sent and nothing else is needed. needs_confirmation: the answer depends on attributes in confirm. no_verified_match: nothing reviewed fits. reason says why. |
reason | string | No | |
candidates | array of Candidate | Yes | |
confirm | array of Question | Yes | |
safety_flags | array of SafetyFlag | Yes | |
usage | Usage | Yes |
{
"meta": {
"api_version": "v1",
"data_set": "sandbox"
},
"status": "needs_confirmation",
"candidates": [
{
"ix_id": "IX-CAM-C-200-AL",
"name": "Camlock Type C, 2\" (50 mm), aluminium",
"safety_flags": [],
"depends_on": [
"grooveStandard",
"endSize"
]
}
],
"confirm": [
{
"key": "grooveStandard",
"question": "Is the groove standard (A-A-59326) or 68 degree?",
"options": [
{
"value": "A-A-59326",
"label": "Standard"
},
{
"value": "68deg",
"label": "68 degree"
}
]
},
{
"key": "endSize",
"question": "What is the nominal size of the other end, the hose tail for a Type C?",
"options": [
{
"value": 50,
"label": "50 mm"
}
]
}
],
"safety_flags": [],
"usage": {
"attribution": "Source: Interchange (interchangeparts.com.au)",
"disclaimer": "Cross-reference only. Check the manufacturer's current specification, rating and media compatibility before use."
}
}400 Error
The request is malformed or a value is outside what the family accepts.
Body: Problem (as application/problem+json)
429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s -X POST "https://api.interchangeparts.com.au/v1/match/attributes" \
-H "Content-Type: application/json" \
-d '{
"family": "camlock",
"attributes": {
"type": "C",
"size": 50,
"bodyMaterial": "aluminium"
}
}'const response = await fetch("https://api.interchangeparts.com.au/v1/match/attributes", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"family": "camlock",
"attributes": {
"type": "C",
"size": 50,
"bodyMaterial": "aluminium"
}
}),
});
const body = await response.json();
console.log(response.status, body);import requests
response = requests.post(
"https://api.interchangeparts.com.au/v1/match/attributes",
json={
"family": "camlock",
"attributes": {
"type": "C",
"size": 50,
"bodyMaterial": "aluminium",
},
},
)
print(response.status_code, response.json())Match a list of your part numbers
POST /v1/match operationId matchBatch
Maps up to 1,000 of your own part numbers or descriptions to IX IDs in one call. Needs an API key on a plan that includes batch matching. Each row has its own status, so one unmatched row does not fail the batch. Your rows are used to answer this request and are not added to the public catalogue.
Needs an API key on a plan that includes it.
Request body
application/json, MatchBatchRequest, required
| Field | Type | Required | Description |
|---|---|---|---|
rows | array of object | Yes | 1 to 1000 items. |
{
"rows": [
{
"ref": "row-1",
"text": "ALDC200C"
},
{
"ref": "row-2",
"text": "2 inch type C aluminium camlock",
"brand": "Dixon"
}
]
}Responses
200 Success
One result per row, in the order sent.
Body: MatchBatchResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
400 Error
The request is malformed or a value is outside what the family accepts.
Body: Problem (as application/problem+json)
401 Error
A key is required, or the key sent is not valid.
Body: Problem (as application/problem+json)
403 Error
The key is valid but its plan does not include this endpoint.
Body: Problem (as application/problem+json)
429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s -X POST "https://api.interchangeparts.com.au/v1/match" \
-H "Authorization: Bearer $INTERCHANGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{
"ref": "row-1",
"text": "ALDC200C"
},
{
"ref": "row-2",
"text": "2 inch type C aluminium camlock",
"brand": "Dixon"
}
]
}'const response = await fetch("https://api.interchangeparts.com.au/v1/match", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.INTERCHANGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"rows": [
{
"ref": "row-1",
"text": "ALDC200C"
},
{
"ref": "row-2",
"text": "2 inch type C aluminium camlock",
"brand": "Dixon"
}
]
}),
});
const body = await response.json();
console.log(response.status, body);import os
import requests
response = requests.post(
"https://api.interchangeparts.com.au/v1/match",
headers={"Authorization": f"Bearer {os.environ['INTERCHANGE_API_KEY']}"},
json={
"rows": [
{
"ref": "row-1",
"text": "ALDC200C",
},
{
"ref": "row-2",
"text": "2 inch type C aluminium camlock",
"brand": "Dixon",
},
],
},
)
print(response.status_code, response.json())Reference
The vocabulary the data uses.
Product families
GET /v1/families operationId listFamilies
The families that exist. Use the keys with getFamily and matchAttributes.
Works without a key. A key raises the limits.
Responses
200 Success
Families.
Body: FamiliesResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s "https://api.interchangeparts.com.au/v1/families"const response = await fetch("https://api.interchangeparts.com.au/v1/families");
const body = await response.json();
console.log(response.status, body);import requests
response = requests.get("https://api.interchangeparts.com.au/v1/families")
print(response.status_code, response.json())A family's attribute vocabulary
GET /v1/families/{family} operationId getFamily
The attributes of a family and the values each accepts, with the role each plays in matching. Use these terms and values when you call matchAttributes.
Works without a key. A key raises the limits.
Parameters
| Name | In | Required | Type | Description | Example |
|---|---|---|---|---|---|
family | path | Yes | string | Pattern ^[a-z][a-z0-9-]{1,30}$ | camlock |
Responses
200 Success
The family.
Body: FamilyResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
404 Error
No item has this ID.
Body: Problem (as application/problem+json)
{
"type": "https://interchangeparts.com.au/problems/not-found",
"title": "Item not found",
"status": 404,
"code": "not_found",
"detail": "No item has the ID IX-NOPE-X-000-ZZ.",
"request_id": "req_7c1f3a"
}429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s "https://api.interchangeparts.com.au/v1/families/camlock"const response = await fetch("https://api.interchangeparts.com.au/v1/families/camlock");
const body = await response.json();
console.log(response.status, body);import requests
response = requests.get("https://api.interchangeparts.com.au/v1/families/camlock")
print(response.status_code, response.json())Changes
Keep your copy current.
What has changed since you last looked
GET /v1/changes operationId listChanges
Items that were published, corrected, retired or withdrawn after a point in time. Use this, or webhooks, to keep a copy current. Needs an API key.
First call: send since. Every response has a next_cursor. Keep it and send it as cursor on the next call, without since. When changes is empty you have caught up, and the cursor is still the place to resume from.
Needs an API key on a plan that includes it.
Parameters
| Name | In | Required | Type | Description | Example |
|---|---|---|---|---|---|
since | query | No | string (date-time) | Return changes after this time. Send it on your first call. Afterwards send cursor instead. One of the two is required. | 2026-10-28T00:00:00Z |
limit | query | No | integer | Page size. 1 to 25. Default 10. | |
cursor | query | No | string | The next_cursor of the previous page. Length 0 to 500. |
Responses
200 Success
One page of changes, oldest first.
Body: ChangesResponse
Headers: Interchange-Request-Id, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
usage | Usage | Yes | |
changes | array of ChangeEvent | Yes | |
next_cursor | string | Yes | Always present. Keep it and send it as cursor on your next call, instead of since. |
400 Error
The request is malformed or a value is outside what the family accepts.
Body: Problem (as application/problem+json)
401 Error
A key is required, or the key sent is not valid.
Body: Problem (as application/problem+json)
403 Error
The key is valid but its plan does not include this endpoint.
Body: Problem (as application/problem+json)
429 Error
Over the limit for your address or key. Wait for Retry-After seconds. Limits and what remains are in the RateLimit-* headers on every response.
Body: Problem (as application/problem+json)
Headers: Retry-After
Code examples
These calls will work when the sandbox opens. Until then they show the shape of a request and the answer you will get. Examples use the values from this operation’s specification.
curl -s "https://api.interchangeparts.com.au/v1/changes?since=2026-10-28T00%3A00%3A00Z" \
-H "Authorization: Bearer $INTERCHANGE_API_KEY"const response = await fetch("https://api.interchangeparts.com.au/v1/changes?since=2026-10-28T00%3A00%3A00Z", {
headers: {
Authorization: `Bearer ${process.env.INTERCHANGE_API_KEY}`,
},
});
const body = await response.json();
console.log(response.status, body);import os
import requests
response = requests.get(
"https://api.interchangeparts.com.au/v1/changes",
params={
"since": "2026-10-28T00:00:00Z",
},
headers={"Authorization": f"Bearer {os.environ['INTERCHANGE_API_KEY']}"},
)
print(response.status_code, response.json())Webhooks
A webhook tells you an item changed, so you do not have to poll. It is sent to an endpoint you register, for a key on a plan that includes webhooks. Verify the signature on every delivery.
item.updated: An item you follow changed
POST to your endpoint
Sent to the endpoint you register for a key on a plan that includes webhooks.
Verify every delivery before you trust it. The Interchange-Signature header looks like t=1761874000,v1=3f9a…. t is the Unix time we sent it. v1 is the hex HMAC-SHA256, with your signing secret, of the string t, a full stop, and the raw request body. Recompute it over the exact bytes you received, compare in constant time, and reject the delivery if it does not match or if t is more than 5 minutes from your clock. The timestamp is inside the signature, so a captured delivery cannot be replayed later.
Respond with any 2xx within 10 seconds. Failed deliveries are retried with backoff for 24 hours, and a retry carries a fresh t and signature, so handlers must be idempotent: use event_id.
Headers
| Name | In | Required | Type | Description | Example |
|---|---|---|---|---|---|
Interchange-Signature | header | Yes | string | t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> | t=1761874000,v1=3f9a0c5e8b1d4a7f92c6e03d5b8a1f4c7e9d2b6a0c3f5e8d1b4a7c9e2f6d0a3b |
Body
| Field | Type | Required | Description |
|---|---|---|---|
event_id | string | Yes | Unique and stable. Use it to ignore a delivery you have already handled. |
type | string: item.published, item.updated, item.retired, item.withdrawn, and others may be added | Yes | |
occurred_at | string (date-time) | Yes | |
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
replaced_by | IxId or null | No | |
reason | string: new_item, correction, specification_change, safety, other, and others may be added | Yes | |
url | string (uri) | No |
{
"event_id": "evt_01J9ZQ3K8M",
"type": "item.withdrawn",
"occurred_at": "2026-10-30T02:14:00Z",
"ix_id": "IX-CAM-C-200-AL-RETIRED",
"replaced_by": "IX-CAM-C-200-AL",
"reason": "correction",
"url": "https://interchangeparts.com.au/items/IX-CAM-C-200-AL"
}Your response
Respond with any 2xx within 10 seconds.
Verify a delivery
The Interchange-Signature header carries a timestamp and a signature, such as t=1761874000,v1=3f9a…. The timestamp is part of what is signed, so a captured delivery cannot be replayed later. For each delivery:
- Read the raw request body as bytes, before any JSON parsing.
- Split the header into
tandv1. - Compute the hex HMAC-SHA256, with your signing secret, of
t, a full stop, then the raw body. - Compare it with
v1in constant time. - Reject the delivery if it does not match, or if
tis more than 5 minutes from your clock. - Only then act on it, and use
event_idto ignore a delivery you have already handled: a retry carries a freshtand signature, so the same event can arrive more than once.
import { createHmac, timingSafeEqual } from "node:crypto";
const TOLERANCE_SECONDS = 5 * 60;
// rawBody is the exact bytes received, as a Buffer or string. Do not parse and re-serialise it first.
export function verifyInterchangeSignature(rawBody, header, secret, nowSeconds = Math.floor(Date.now() / 1000)) {
const parts = {};
for (const piece of header.split(",")) {
const i = piece.indexOf("=");
if (i > 0) parts[piece.slice(0, i).trim()] = piece.slice(i + 1).trim();
}
if (!/^\d+$/.test(parts.t ?? "") || !parts.v1) return false;
if (Math.abs(nowSeconds - Number(parts.t)) > TOLERANCE_SECONDS) return false;
const expected = createHmac("sha256", secret).update(parts.t + ".").update(rawBody).digest();
const given = Buffer.from(parts.v1, "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 5 * 60
def verify_interchange_signature(raw_body: bytes, header: str, secret: str, now=None) -> bool:
# raw_body is the exact bytes received. Do not parse and re-serialise it first.
parts = {}
for piece in header.split(","):
key, sep, value = piece.partition("=")
if sep:
parts[key.strip()] = value.strip()
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or not v1:
return False
if abs((time.time() if now is None else now) - int(t)) > TOLERANCE_SECONDS:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
Schemas
Every object in a request or a response. Field types link to the schema they refer to.
IxId
Interchange ID. Upper-case letters, digits and hyphens, at most 32 characters. Permanent and never reused.
Type: string Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$
Meta
| Field | Type | Required | Description |
|---|---|---|---|
api_version | string | Yes | |
data_set | string: sandbox, reviewed, and others may be added | Yes | sandbox is illustrative and must not be relied on. reviewed has been checked by a person. |
Usage
Carried on every answer so that it survives being passed along.
| Field | Type | Required | Description |
|---|---|---|---|
attribution | string | Yes | |
disclaimer | string | Yes |
Confidence
verified: reviewed by Interchange against a standard or full specification. supplier_confirmed: confirmed by the supplier in its match report. under_review: a correction has been reported and the mapping is being checked. Machine-suggested mappings are never returned.
Type: string: verified, supplier_confirmed, under_review, and others may be added
Evidence
The kind of evidence behind a mapping, strongest first.
Type: string: standard, specification, supplier_data, manufacturer_table, and others may be added
AuState
Type: string: ACT, NSW, NT, QLD, SA, TAS, VIC, WA
SafetyFlag
| Field | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Stable machine-readable code. New codes may be added. |
severity | string: info, caution, warning, and others may be added | Yes | |
message | string | Yes | Plain-language text you can show to a person as written. |
attributes | array of string | No | The attributes the flag concerns. |
related | array of IxId | No | Other items the flag refers to, for example the part it is commonly confused with. |
AttributeValue
| Field | Type | Required | Description |
|---|---|---|---|
value | string or number or boolean | Yes | |
unit | string | No | |
label | string | Yes | |
detail | string | No |
Item
| Field | Type | Required | Description |
|---|---|---|---|
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
name | string | Yes | |
family | string | Yes | |
status | string: published, retired, and others may be added | Yes | A retired item was replaced after a correction. See replaced_by. |
replaced_by | IxId or null | No | |
attributes | object of AttributeValue | Yes | Attributes by key. The vocabulary for a family is at /v1/families/{family}. |
safety_flags | array of SafetyFlag | Yes | |
reviewed | string (date) | Yes | When a person last reviewed this item. |
url | string (uri) | Yes | The item's permanent page. Link to it when you show a result. |
ItemResponse
SearchResult
| Field | Type | Required | Description |
|---|---|---|---|
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
name | string | Yes | |
identified_by | string: exact_code, pattern, text, and others may be added | Yes | How the query led here. exact_code means a mapped part number matched. text is the weakest. |
matched_on | object | No | The mapped part that matched, when identified_by is exact_code. |
confidence | Confidence | Yes | |
safety_flags | array of SafetyFlag | Yes | Known hazards for this item. Show them wherever a person sees this result. |
SearchResponse
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
query | string | Yes | |
status | string: matched, no_verified_match, and others may be added | Yes | |
reason | string | No | Present when status is no_verified_match. |
results | array of SearchResult | Yes | |
usage | Usage | Yes |
Equivalent
| Field | Type | Required | Description |
|---|---|---|---|
brand | string | Yes | |
part_number | string | Yes | |
confidence | Confidence | Yes | |
reviewed | string (date) | Yes | |
evidence | Evidence | Yes |
AttributeDifference
| Field | Type | Required | Description |
|---|---|---|---|
attribute | string | Yes | |
this_value | string or number or boolean | Yes | |
other_value | string or number or boolean | Yes |
NearMatch
| Field | Type | Required | Description |
|---|---|---|---|
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
name | string | Yes | |
same_fit | boolean | Yes | True when the two parts connect the same way and differ only in a non-fit attribute such as material. |
differences | array of AttributeDifference | Yes |
EquivalentsResponse
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
name | string | Yes | |
status | string: matched, no_verified_match, and others may be added | Yes | no_verified_match when the item exists but has no reviewed equivalents yet. |
reason | string | No | |
replaced_by | IxId or null | No | |
equivalents | array of Equivalent | Yes | Sorted by brand, A to Z. |
near_matches | array of NearMatch | No | Present only when include_near is true. |
safety_flags | array of SafetyFlag | Yes | |
suppliers_url | string (uri) | No | |
usage | Usage | Yes |
Money
| Field | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Decimal string, to avoid rounding. Pattern ^\d+(\.\d{1,2})?$ |
currency | string: AUD, and others may be added | Yes | |
ex_gst | boolean, always true | Yes |
Stock
| Field | Type | Required | Description |
|---|---|---|---|
status | string: in_stock, low, out_of_stock, unknown, and others may be added | Yes | |
quantity | integer | No | 0 to any. |
price | Money | No | |
as_of | string (date-time) | Yes |
Supplier
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | |
name | string | Yes | |
profile_url | string (uri) | Yes | |
quote_url | string (uri) | No | Where a person asks this supplier for a quote. Link to it when you show a result. |
branches | array of object | Yes |
SuppliersResponse
MatchAttributesRequest
| Field | Type | Required | Description |
|---|---|---|---|
family | string | Yes | Pattern ^[a-z][a-z0-9-]{1,30}$ |
attributes | object of string or number or boolean | Yes | Only what you are sure of. Keys and values are listed at /v1/families/{family}. |
limit | integer | No | 1 to 25. Default 10. |
Question
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The attribute to send back with the answer. |
question | string | Yes | Plain-language question to put to the person. |
options | array of object | No |
Candidate
| Field | Type | Required | Description |
|---|---|---|---|
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
name | string | Yes | |
safety_flags | array of SafetyFlag | Yes | Known hazards for this item. Show them wherever a person sees this candidate. |
depends_on | array of string | No | Attributes that must be confirmed before this candidate can be treated as the answer. |
MatchAttributesResponse
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
status | string: matched, needs_confirmation, no_verified_match, and others may be added | Yes | matched: one item fits everything sent and nothing else is needed. needs_confirmation: the answer depends on attributes in confirm. no_verified_match: nothing reviewed fits. reason says why. |
reason | string | No | |
candidates | array of Candidate | Yes | |
confirm | array of Question | Yes | |
safety_flags | array of SafetyFlag | Yes | |
usage | Usage | Yes |
MatchBatchRequest
| Field | Type | Required | Description |
|---|---|---|---|
rows | array of object | Yes | 1 to 1000 items. |
MatchBatchResponse
AttributeDef
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | |
label | string | Yes | |
role | string: identity, variant, info, and others may be added | Yes | identity: must match exactly for two parts to be the same item. variant: splits variants within one item and can be filtered on. info: shown only, never matched. |
required | boolean | Yes | |
type | string: enum, number, boolean, text, and others may be added | Yes | |
values | array of string | No | Accepted values, when type is enum. |
unit | string | No |
Family
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | |
label | string | Yes | |
attributes | array of AttributeDef | No |
FamiliesResponse
FamilyResponse
ChangeEvent
| Field | Type | Required | Description |
|---|---|---|---|
event_id | string | Yes | Unique and stable. Use it to ignore a delivery you have already handled. |
type | string: item.published, item.updated, item.retired, item.withdrawn, and others may be added | Yes | |
occurred_at | string (date-time) | Yes | |
ix_id | IxId | Yes | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
replaced_by | IxId or null | No | |
reason | string: new_item, correction, specification_change, safety, other, and others may be added | Yes | |
url | string (uri) | No |
ChangesResponse
| Field | Type | Required | Description |
|---|---|---|---|
meta | Meta | Yes | |
usage | Usage | Yes | |
changes | array of ChangeEvent | Yes | |
next_cursor | string | Yes | Always present. Keep it and send it as cursor on your next call, instead of since. |
Problem
An error, as RFC 9457 problem details.
| Field | Type | Required | Description |
|---|---|---|---|
type | string (uri) | Yes | |
title | string | Yes | |
status | integer | Yes | |
detail | string | No | |
code | string: invalid_request, unknown_attribute, invalid_attribute_value, not_found, withdrawn, unauthorized, plan_does_not_include, rate_limited, method_not_allowed, payload_too_large, internal_error, and others may be added | Yes | Stable machine-readable code. |
request_id | string | No | |
replaced_by | IxId | No | Length 0 to 32. Pattern ^IX(-[A-Z0-9]+)+$ |
errors | array of object | No |