Integration API · v1
Build with your inventory.
Connect FindEZ Team Spaces to reports, scripts, and external tools. Start with a read-only request, then explore the complete endpoint reference.
https://api.findez.ai/api/v1Public documentation · No sign-in needed · Examples use fictional data
Your first connection
- Sign in to FindEZ and open Teams. Create or use a Team you own, then link a Space containing inventory. Being a team member alone does not allow key creation.
- Open Settings → API keys. Name the integration, choose the team, keep the default read permissions, and choose an expiration. The web form defaults to 90 days.
- Create the key and copy it to your integration’s private credential settings. The full value is shown once. Use Test connection before dismissing it: read keys run an inventory count; workspace-only keys check the summary; write-only keys check authentication without writing.
- For the examples below, use macOS Terminal (zsh or bash). Run this command on its own, paste the key at the hidden prompt, then press Return:
read -rs FINDEZ_API_KEYExport it for the Python and Node.js examples. Keep the same terminal open; never paste the actual key into the example source.
export FINDEZ_API_KEYcurl --silent --show-error --fail-with-body --max-time 30 --request GET \
--url 'https://api.findez.ai/api/v1/items?page=1&page_size=50' \
--header "Authorization: Bearer $FINDEZ_API_KEY"A successful response contains items, page, page_size, and total. An empty array is a valid result: check that the selected Team has a linked Space with items. Response examples below are illustrative; your IDs, dates, and values will differ.
When finished, run unset FINDEZ_API_KEY. The cURL examples use --fail-with-body (cURL 7.76+); Python uses the standard library, and JavaScript runs in Node.js 18+ with no packages. On Windows, use your tool’s private credential settings or run the shell instructions in WSL.
Authentication & permissions
Send Authorization: Bearer <credential> on every API request over HTTPS. Use the API host above; findez.ai serves the web app. Credentials belong in the header, never the URL. Production keys start with findez_live_sk_, followed by 32 URL-safe characters; development keys start with findez_test_sk_. Each environment only accepts its own keys. A test prefix does not create a production sandbox, and the public service does not offer a documented test-data environment.
Integration requests use your API key. Key management uses the signed-in owner’s Supabase user access_token. An API key cannot create, list, or revoke keys, and a user session cannot replace the API key on inventory integration endpoints. Most users should manage keys in Settings; programmatic management examples assume you already have a valid user session. Load that access token privately into FINDEZ_USER_ACCESS_TOKEN, using the same separate read -rs and export steps. Never substitute an anonymous project key, a refresh token, or a service-role credential.
| Scope | Key type | Allows |
|---|---|---|
items:read | Workspace | List items and run structured queries |
workspace:read | Workspace | Read location counts and workspace summaries |
items:write | Workspace | Create and patch items |
import:write | Workspace | Bulk upsert items |
org:read | Organization | Item queries, lists, locations, and summaries across owned teams |
org:write | Organization | Create, patch, and bulk upsert across owned teams |
Read and write are independent. items:write does not include bulk import or reads. import:write does not include single-item create/patch. workspace:read does not allow item reads. Organization read/write scopes provide the corresponding operations across teams owned by the issuing account. Workspace and organization scopes cannot be mixed in one key. Every valid key can call GET /whoami.
Use one key per integration and grant only the required permissions. Keep it in a server-side secret manager or your automation tool’s private credentials, outside source control, browser code, public spreadsheets, and logs. Arbitrary browser origins are not enabled for CORS; call the API from your own server, which keeps the secret private. A key does not install an integration or schedule jobs by itself.
To rotate a key: create a replacement, test the required access, update the integration, verify it works, then revoke the old key. Revocation is permanent. Lost keys cannot be retrieved; exposed keys should be revoked and replaced. No endpoint updates an issued key’s scopes or expiry.
Teams, Spaces & visibility
A workspace is a FindEZ Team: workspace_id is its team_id UUID. A Space is a place containing inventory. The integration API sees inventory in Spaces linked to the Team. Personal Spaces outside Teams, and Teams you only belong to, are outside this API’s access.
A workspace key is fixed to one Team. An organization key covers all Teams currently owned by the same account, including newly owned teams. Here, “organization” means that owner account, not a separate company record. Organization writes need a workspace_id in every item; organization lists and queries can omit it to read across owned teams.
API writes use the exact linked Space name as location. Create and link the Space in FindEZ first. If two linked Spaces have the same name, rename one before writing through the API. The API preserves the destination Space’s actual owner and places the item in that Space. Moving an item by PATCH is limited to its existing workspace.
New app items in linked Spaces become visible through the API. Detaching a Space removes that Team’s API access while preserving its inventory; moving it changes which Team can see it. Ownership changes and Team deletion also remove data access. The key may still authenticate at /whoami, so use a read/query to check actual visibility.
GET /spaces groups item locations and omits empty Spaces. space_count in a summary counts distinct location strings with inventory. Neither is a complete directory of linked Space records. Use FindEZ to manage the actual links and Space names.
Requests & responses
Use JSON for POST/PATCH bodies and set Content-Type: application/json. Unknown JSON properties are rejected, including nested item/filter properties. Use the exact field names in the tables. Unknown URL query parameters are currently ignored; only parameters explicitly documented on an endpoint are supported.
UUIDs are strings. Quantities are whole numbers between 0 and 100000. Send text identifiers as strings so leading zeroes survive. Name, category, and location are trimmed and cannot be blank. Optional text can be null where specified. For PATCH, omitted fields stay unchanged; null clears only nullable fields. For creates and bulk upserts, send the intended values explicitly, especially quantity and category.
Pages start at 1 with a default size of 50 and a maximum of 100. GET /items accepts any page ≥ 1; POST /query additionally caps page at 1000000. Both sort newest first, then by item ID. total counts all matching records. Pagination is offset based with no snapshot or cursor; a changing inventory can cause repeats or omissions across requests.
created_at is the creation timestamp, not a modification checkpoint. The API returns the stored timestamp representation; clients should handle ISO 8601 dates without assuming an offset is always present in older inventory records. There is no updated_at filter or incremental change feed.
The readable item representation contains only the fields in the response reference. It does not expose user IDs, Space IDs, reservations, available-to-build stock, tags, checkout state, or document contents. An image URL is a stored reference, not an uploaded file or a guarantee of long-term accessibility.
Download the OpenAPI 3.1 document to inspect schemas or import requests into a compatible API client. Its server URL is the API origin, and its paths already include /api/v1; do not add the version prefix twice. Backend /docs, /redoc, and /openapi.json are disabled in production; use this public reference.
Integration recipes
How many units of a part do we have?
Use the exact stored part_number and sum_quantity. Three records holding 4, 2, and 2 units total 8, while count returns 3. Totals include all matching pages and return 0 for no matches. They reflect stored quantity, without subtracting checkouts or reservations.
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/query' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"resource": "items",
"filters": [
{
"field": "part_number",
"op": "eq",
"value": "5202"
}
],
"aggregate": "sum_quantity"
}'Low-stock report for one location
Combine filters with AND. This finds records at or below five units in Shelf B. It does not read device-specific thresholds or send alerts; your integration schedules checks and decides what to do.
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/query' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"filters": [
{
"field": "location",
"op": "eq",
"value": "Shelf B"
},
{
"field": "quantity",
"op": "lte",
"value": 5
}
],
"page": 1,
"page_size": 50
}'Export all visible records
Fetch each page until the number collected reaches total, or a page is empty. The example stops on empty pages and deduplicates item IDs, but concurrent writes can still move rows; run during a quiet period if you need a consistent report. The export includes fields such as notes, so store it only where intended readers have access.
import json
import os
from urllib.request import Request, urlopen
items = {}
page = 1
while True:
request = Request(
"https://api.findez.ai/api/v1/items?page=" + str(page) + "&page_size=100",
headers={"Authorization": "Bearer " + os.environ["FINDEZ_API_KEY"]},
)
with urlopen(request, timeout=30) as response:
result = json.load(response)
for item in result["items"]:
items[item["item_id"]] = item
if not result["items"] or page * result["page_size"] >= result["total"]:
break
page += 1
print(json.dumps(list(items.values()), indent=2))Connect a spreadsheet, automation, or AI tool
Configure an HTTP action with the API URL, method, Bearer credential, and JSON body from the reference. Use item reads for reporting and bulk upsert for a source-of-truth sync. Keep the credential in private connection settings; a visible spreadsheet cell is not private storage. Map items for lists or value for totals, and handle non-2xx responses before using data. An AI tool must call this API with supported structured filters; the API has no natural-language or SQL execution endpoint. There is no bundled SDK, MCP server, webhook subscription, or automatic third-party connection in this API release.
Reliable writes & sync
- Choose which system owns each field to avoid overwriting edits made in FindEZ.
- Link the destination Spaces and verify their exact names. For organization keys, map each record to an owned Team UUID.
- Choose a stable
source_systemnamespace and anexternal_idfor each source record. The same identity in a different workspace is a different record. - Send the full intended quantity and category every time. Bulk upsert uses create defaults for omitted fields; it is not a partial PATCH. Keep optional columns consistent across a batch, and use PATCH for explicit null clearing.
- Split into batches within the server cap, remove duplicate identities, and record which batches succeeded. Each batch is atomic; a multi-batch job is not one transaction.
- Reconcile results with a read-enabled key.
processedacknowledges row processing; it does not include item IDs or inserted/updated counts.
Repeating the same bulk identities avoids duplicate rows, but a retry still overwrites the values it supplies. New external IDs create new records, and omitting a record does not delete it. There is no delete-item endpoint for integration keys. PATCH can clear or change external identities, which affects future matching; avoid changing them after a sync is established.
Single-item POST always creates a row and has no idempotency header. After a timeout, inspect existing inventory before creating again. Absolute PATCH values can be resent, but may overwrite a newer edit. There are no compare-and-set preconditions, atomic increments, transactions across HTTP requests, or concurrency locks exposed to clients.
Limits & retries
| Limit | Default | Applies to |
|---|---|---|
| Standard requests | 120 / minute | All API-key endpoints except bulk, including /whoami |
| Bulk requests | 10 / minute | POST /items/bulk, in a separate budget |
| Bulk items | 500 / request | At least 1; schema hard maximum 500 |
These are server defaults and can be configured. Limits are shared by all clients using the same key. Standard and bulk budgets are independent, use fixed minute windows, and are checked after authentication and scope validation. Requests that pass those checks can consume budget even if later validation or database work fails. Key-management endpoints use a user session and do not use these per-key budgets.
The per-key limiter responds with HTTP 429 and Retry-After: 60. Wait at least that long, add jitter, and cap retry attempts. There are no remaining-budget or reset-time headers. A proxy may also reject a request, so handle absent headers and non-JSON responses. HTTP 401/403 normally needs a credential or permission change, not repeated retries; 400/404/409/413/422 needs request or state correction.
Use a client timeout; examples use 30 seconds. For transient network or 5xx failures, retry reads with bounded exponential backoff. For writes, first determine whether the operation may have committed. Prefer a queued sync with stable identities and a single writer over uncontrolled parallel writes. No uptime SLA, retry guarantee, or rate-limit increase is promised by this reference.
Errors & troubleshooting
Integration errors normally contain detail.code, a safe detail.message, and detail.correlation_id. Branch on status and code; messages are human-readable. Validation errors are generic and do not list failing fields. Key-management authentication uses the shared session handler and can return a string detail; middleware and gateway failures can also return a different shape or non-JSON body. Check status and content before parsing.
{
"detail": {
"code": "insufficient_scope",
"message": "This endpoint requires items:read.",
"correlation_id": "example-request-id"
}
}The app normally returns X-Correlation-ID. You may supply your own value (truncated to 64 characters); keep it free of secrets and personal information. If an error occurs before normal response handling, this header may be absent. Retain the ID with your request time and method for support.
| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_scope | Scopes are empty after normalization or contain an unknown scope. Use the permission table. |
| 400 | scope_type_mismatch | Workspace and organization scope types do not match workspace_id. |
| 400 | invalid_expiration | Choose an expires_at in the future. |
| 400 | workspace_required | Add workspace_id to each organization-key item write. |
| 400 | empty_update | Supply at least one supported PATCH field. |
| 400 | duplicate_external_id | Remove repeated external identities within a bulk request. |
| 400 | invalid_inventory_request | Check the query and exact linked Space name; resolve ambiguous names in FindEZ. |
| 401 | invalid_api_key | Supply the complete integration key in the Bearer header; check for copy errors. |
| 401 | wrong_environment | Use a key issued by this environment. Production uses findez_live_sk_. |
| 401 | revoked_api_key | Replace the revoked key; it cannot be restored. |
| 401 | expired_api_key | Create a replacement key with an appropriate expiry. |
| 403 | insufficient_scope | Create a key with the required permission; writing does not imply reading. |
| 403 | workspace_access_denied | Check the team owner and key workspace. A workspace key cannot select another team. |
| 403 | organization_required | Create or own a Team before issuing an organization key. |
| 404 | item_not_found | Check item_id and current workspace visibility. |
| 404 | key_not_found | Check the key metadata id and issuing account; already-revoked keys return this too. |
| 409 | duplicate_external_id | The identity already exists. Use bulk upsert or a distinct identity. |
| 413 | bulk_payload_too_large | Split the batch below the configured server cap. More than 500 rows fails schema validation with 422. |
| 422 | invalid_request | Check JSON structure, unknown properties, UUIDs, required fields, types, lengths, and numeric limits. Field-level details are not returned. |
| 429 | rate_limit_exceeded | Wait for Retry-After (the per-key limiter sends 60 seconds), then retry with bounded backoff. |
| 503 | authentication_unavailable | Authentication storage is temporarily unavailable. |
| 503 | rate_limit_unavailable | The request-budget check is temporarily unavailable. |
| 503 | key_creation_unavailable | Key issuance failed. Check the saved key list before trying again. |
| 503 | key_list_unavailable | Key metadata could not be loaded. |
| 503 | workspace_list_unavailable | Owned teams could not be loaded. |
| 503 | key_revocation_unavailable | Revocation could not be completed; check status before retrying. |
| 503 | database_unavailable | The inventory operation failed. Reconcile ambiguous writes before retrying. |
| 500 | internal_error | An unexpected application error occurred. Keep the correlation ID for support. |
Connected, but the inventory is empty
Check the key’s team, current ownership, and linked Spaces in FindEZ. Personal unlinked items are excluded. Check exact filter case and part-number formatting. A workspace key cannot override its team; an organization query targeting an unowned team returns no visible rows. A passing /whoami check only confirms the credential.
A Space is missing, or a location write fails
Empty Spaces do not appear in /spaces. Verify the actual linked Space exists and use its exact name. Resolve duplicate names within the Team. Setting location cannot create or attach a Space.
A quantity or sync result looks wrong
Use sum_quantity for units and count for records. Quantity is an absolute value, not an increment or available-stock calculation. Check stable external IDs and whether omitted bulk quantity/category values applied defaults. A repeated single-item POST can create another record.
Complete reference
Every integration endpoint
Each operation includes authentication, parameters, request fields, runnable examples, a success response, and expandable response fields. The same schemas are available in the OpenAPI download. Write examples change real inventory when used with a live key; replace the fictional IDs and Space names before running them.
/api/v1/whoamiTest authentication
Any valid integration key · HTTP 200 · standard request budget
Returns the key ID, its fixed workspace (null for an organization key), and its explicit scopes. Any valid key can call this endpoint. Success verifies authentication only: it does not prove inventory is visible, that the team is still owned, or that a write will succeed. It consumes the standard request budget.
No URL parameters.
No request body.
curl --silent --show-error --fail-with-body --max-time 30 --request GET \
--url 'https://api.findez.ai/api/v1/whoami' \
--header "Authorization: Bearer $FINDEZ_API_KEY"{
"key_id": "40000000-0000-0000-0000-000000000001",
"workspace_id": "10000000-0000-0000-0000-000000000001",
"scopes": [
"items:read",
"workspace:read"
]
}Response fields
| Field | Type / presence | Meaning & constraints |
|---|---|---|
key_id | uuidRequired | |
workspace_id | string | nullRequired | |
scopes | string[]Required |
/api/v1/itemsList inventory
Permission: items:read or org:read · HTTP 200 · standard request budget
Returns visible item records, newest created_at first, then item_id ascending for ties. Pagination starts at 1. total counts all matching records, not units. An out-of-range page returns an empty items array. Only page, page_size, and workspace_id are supported query parameters; use POST /query for filters. Unknown URL query parameters are currently ignored, so misspelled filters will not filter results. Organization reads of an unowned workspace return no visible rows. Pagination is not a snapshot: concurrent inventory changes can move records between pages.
| Parameter | Type / location | Meaning & constraints |
|---|---|---|
pageOptional | integer · query | One-based page number.Minimum: 1 · Default: 1 |
page_sizeOptional | integer · query | Maximum records in a page, from 1 to 100.Minimum: 1 · Maximum: 100 · Default: 50 |
workspace_idOptional | uuid | null · query | Team UUID. A workspace key may omit it; if supplied it must match the key. Organization keys must supply it for each item write and may supply it to narrow item reads or queries. |
No request body.
curl --silent --show-error --fail-with-body --max-time 30 --request GET \
--url 'https://api.findez.ai/api/v1/items?page=1&page_size=50' \
--header "Authorization: Bearer $FINDEZ_API_KEY"{
"items": [
{
"item_id": "30000000-0000-0000-0000-000000000001",
"workspace_id": "10000000-0000-0000-0000-000000000001",
"name": "Motor",
"category": "Hardware",
"quantity": 4,
"location": "Shelf B",
"image_url": null,
"barcode": null,
"purchase_source": null,
"notes": null,
"brand": null,
"part_number": "5202",
"source_system": "erp",
"external_id": "motor-001",
"created_at": "2026-09-10T12:00:00Z"
}
],
"page": 1,
"page_size": 50,
"total": 1
}Response fields
| Field | Type / presence | Meaning & constraints | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
items | Item[]Required | Nested fields
| ||||||||||||||||||||||||||||||||||||||||||||||||
page | integerRequired | |||||||||||||||||||||||||||||||||||||||||||||||||
page_size | integerRequired | |||||||||||||||||||||||||||||||||||||||||||||||||
total | integerRequired |
/api/v1/itemsCreate an item
Permission: items:write or org:write · HTTP 201 · standard request budget
Creates a new record in an existing linked Space. Does not merge matching names. The response echoes normalized submitted fields, defaults, the resolved workspace_id, and the new item_id. It is not a database read-back: created_at and omitted optional fields are not returned. To get the stored representation, use a read-enabled key and list/query the inventory. The new item belongs to the Space's actual owner. A location does not create a Space. Organization keys must include workspace_id. This operation has no idempotency-key support; retrying after an ambiguous timeout can create duplicates. Use bulk upsert with stable external identities for repeatable imports.
No URL parameters.
| Field | Type / presence | Meaning & constraints |
|---|---|---|
workspace_id | uuid | nullOptional | Team UUID. A workspace key may omit it; if supplied it must match the key. Organization keys must supply it for each item write and may supply it to narrow item reads or queries. |
name | stringRequired | Item name. Leading/trailing whitespace is removed; blank values are rejected.Min characters: 1 · Max characters: 200 |
category | stringOptional | Category label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.Min characters: 1 · Max characters: 100 · Default: "Other" |
quantity | integerOptional | Stored unit quantity, as a whole number from 0 to 100000 inclusive. Writes set the absolute quantity; they do not increment it.Minimum: 0 · Maximum: 100000 · Default: 1 |
location | stringRequired | Exact, case-sensitive name of one existing Space linked to the target Team. Surrounding whitespace is trimmed. Unknown or ambiguous names are rejected; create, link, or rename Spaces in FindEZ first.Min characters: 1 · Max characters: 200 |
image_url | string | nullOptional | Image reference string; this endpoint does not upload a file or return a newly signed image URL. Use a URL your intended readers can access.Max characters: 2000 |
barcode | string | nullOptional | Barcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.Max characters: 100 |
purchase_source | string | nullOptional | Purchase source or vendor text.Max characters: 200 |
notes | string | nullOptional | Free-text notes.Max characters: 2000 |
brand | string | nullOptional | Brand or manufacturer text.Max characters: 100 |
part_number | string | nullOptional | Part identifier string. Preserve formatting and leading zeroes for exact matching.Max characters: 100 |
source_system | string | nullOptional | External integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert.Max characters: 100 |
external_id | string | nullOptional | Stable record identifier in the source system. Reuse the same value for later syncs; do not generate a new ID each run.Max characters: 200 |
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/items' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"name": "Motor",
"category": "Hardware",
"quantity": 4,
"location": "Shelf B",
"part_number": "5202"
}'{
"item": {
"name": "Motor",
"category": "Hardware",
"quantity": 4,
"location": "Shelf B",
"part_number": "5202",
"workspace_id": "10000000-0000-0000-0000-000000000001",
"item_id": "30000000-0000-0000-0000-000000000001"
}
}Response fields
| Field | Type / presence | Meaning & constraints | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
item | objectRequired | Nested fields
|
/api/v1/items/bulkSync items by external identity
Permission: import:write or org:write · HTTP 200 · bulk request budget
Accepts a JSON object containing 1–500 items (the server may configure a lower cap). Every item requires name, location, source_system, and external_id. The last two are trimmed and must not be blank. Matching is by (workspace_id, source_system, external_id); repeating an identity updates that row and preserves its item_id. Duplicate identities within one request return 400 before writing. Workspace keys cannot mix workspaces; organization keys provide workspace_id on every item. The batch is one database upsert: a validation, permission, or location failure prevents a partial batch commit. processed is the number of submitted rows, not units, inserted rows, or returned IDs. Omitted category and quantity take their create defaults (Other and 1), even on an existing row. Supply the complete intended state on every sync; omitted nullable values are not a documented preservation or clearing mechanism. Use PATCH to explicitly clear a field. Rows absent from the batch remain in inventory. There is no deletion, dry-run, file upload, or background job in this endpoint.
No URL parameters.
| Field | Type / presence | Meaning & constraints | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
items | APIBulkItem[]Required | 1–500 records with stable external identities. A configured server cap can be lower.Min entries: 1 · Max entries: 500Nested fields
|
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/items/bulk' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"items": [
{
"name": "Motor",
"category": "Hardware",
"quantity": 4,
"location": "Shelf B",
"part_number": "5202",
"source_system": "erp",
"external_id": "motor-001"
}
]
}'{
"processed": 1
}Response fields
| Field | Type / presence | Meaning & constraints |
|---|---|---|
processed | integerRequired |
/api/v1/items/{item_id}Update an item
Permission: items:write or org:write · HTTP 200 · standard request budget
Updates only supplied fields. At least one field is required. name, category, quantity, and location cannot be null; nullable optional fields can be cleared with null. Quantity replaces the stored total, rather than adding a delta. Changing location moves the item to one unambiguous linked Space within the same workspace. workspace_id, space_id, user_id, item_id, and created_at are not writable here. A missing or inaccessible item returns 404. The response is an acknowledgement, not the updated record. There is no ETag, version precondition, or atomic increment: concurrent updates may overwrite one another.
| Parameter | Type / location | Meaning & constraints |
|---|---|---|
item_idRequired | uuid · path | Item UUID from a create response or inventory read. |
| Field | Type / presence | Meaning & constraints |
|---|---|---|
name | stringOptional | Item name. Leading/trailing whitespace is removed; blank values are rejected.Min characters: 1 · Max characters: 200 |
category | stringOptional | Category label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.Min characters: 1 · Max characters: 100 |
quantity | integerOptional | Stored unit quantity, as a whole number from 0 to 100000 inclusive. Writes set the absolute quantity; they do not increment it.Minimum: 0 · Maximum: 100000 |
location | stringOptional | Exact, case-sensitive name of one existing Space linked to the target Team. Surrounding whitespace is trimmed. Unknown or ambiguous names are rejected; create, link, or rename Spaces in FindEZ first.Min characters: 1 · Max characters: 200 |
image_url | string | nullOptional | Image reference string; this endpoint does not upload a file or return a newly signed image URL. Use a URL your intended readers can access.Max characters: 2000 |
barcode | string | nullOptional | Barcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.Max characters: 100 |
purchase_source | string | nullOptional | Purchase source or vendor text.Max characters: 200 |
notes | string | nullOptional | Free-text notes.Max characters: 2000 |
brand | string | nullOptional | Brand or manufacturer text.Max characters: 100 |
part_number | string | nullOptional | Part identifier string. Preserve formatting and leading zeroes for exact matching.Max characters: 100 |
source_system | string | nullOptional | External integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert.Max characters: 100 |
external_id | string | nullOptional | Stable record identifier in the source system. Reuse the same value for later syncs; do not generate a new ID each run.Max characters: 200 |
curl --silent --show-error --fail-with-body --max-time 30 --request PATCH \
--url 'https://api.findez.ai/api/v1/items/30000000-0000-0000-0000-000000000001' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"quantity": 6,
"notes": null
}'{
"updated": true,
"item_id": "30000000-0000-0000-0000-000000000001"
}Response fields
| Field | Type / presence | Meaning & constraints |
|---|---|---|
updated | trueRequired | |
item_id | uuidRequired |
/api/v1/queryFilter inventory and total quantities
Permission: items:read or org:read · HTTP 200 · standard request budget
Structured queries over items only. Filters are ANDed; at most 10 are accepted. Text equality and inequality are exact and case-sensitive, with no wildcard expansion; null fields match neither eq nor neq. Quantity comparisons require JSON integers, not strings, decimals, or booleans. Omit aggregate (or send null) for a paginated list in the same order as GET /items. count counts records; sum_quantity totals stored units across every matching record regardless of page or page_size. Both aggregates return 0 for no matches. Aggregate calls still validate pagination fields. SQL, OR groups, joins, arbitrary fields, text search, and custom sorting are unavailable.
No URL parameters.
| Field | Type / presence | Meaning & constraints | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
resource | "items"Optional | Only items is supported.Default: "items" | ||||||||||||
workspace_id | uuid | nullOptional | Team UUID. A workspace key may omit it; if supplied it must match the key. Organization keys must supply it for each item write and may supply it to narrow item reads or queries. | ||||||||||||
filters | InventoryFilter[]Optional | Up to 10 ANDed filters; empty array matches all visible rows.Max entries: 10Nested fields
| ||||||||||||
aggregate | string | nullOptional | count = item records; sum_quantity = stored unit total across all matches. Omit/null to return an item page.Allowed: "count", "sum_quantity" | ||||||||||||
page | integerOptional | One-based page; maximum 1000000. Validated even for aggregates.Minimum: 1 · Maximum: 1000000 · Default: 1 | ||||||||||||
page_size | integerOptional | 1–100 records; default 50. Does not bound aggregate totals.Minimum: 1 · Maximum: 100 · Default: 50 |
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/query' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"resource": "items",
"filters": [
{
"field": "part_number",
"op": "eq",
"value": "5202"
}
],
"aggregate": "sum_quantity"
}'{
"resource": "items",
"aggregate": "sum_quantity",
"value": 4
}Response fields
| Field | Type / presence | Meaning & constraints | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
resource | "items"Required | |||||||||||||||||||||||||||||||||||||||||||||||||
items | Item[]Required | Nested fields
| ||||||||||||||||||||||||||||||||||||||||||||||||
page | integerRequired | |||||||||||||||||||||||||||||||||||||||||||||||||
page_size | integerRequired | |||||||||||||||||||||||||||||||||||||||||||||||||
total | integerRequired |
| Field | Type / presence | Meaning & constraints |
|---|---|---|
resource | "items"Required | |
aggregate | stringRequired | Allowed: "count", "sum_quantity" |
value | integerRequired |
Low stock list
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/query' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"filters": [
{
"field": "quantity",
"op": "lte",
"value": 5
}
],
"page": 1,
"page_size": 50
}'{
"resource": "items",
"items": [
{
"item_id": "30000000-0000-0000-0000-000000000001",
"workspace_id": "10000000-0000-0000-0000-000000000001",
"name": "Motor",
"category": "Hardware",
"quantity": 4,
"location": "Shelf B",
"image_url": null,
"barcode": null,
"purchase_source": null,
"notes": null,
"brand": null,
"part_number": "5202",
"source_system": "erp",
"external_id": "motor-001",
"created_at": "2026-09-10T12:00:00Z"
}
],
"page": 1,
"page_size": 50,
"total": 1
}Count location records
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/query' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"filters": [
{
"field": "location",
"value": "Shelf B"
}
],
"aggregate": "count"
}'{
"resource": "items",
"aggregate": "count",
"value": 1
}No matching units
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/query' \
--header "Authorization: Bearer $FINDEZ_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"filters": [
{
"field": "part_number",
"value": "NO-MATCH"
}
],
"aggregate": "sum_quantity"
}'{
"resource": "items",
"aggregate": "sum_quantity",
"value": 0
}/api/v1/spacesList inventory locations
Permission: workspace:read or org:read · HTTP 200 · standard request budget
Returns distinct (workspace_id, location) pairs visible to the key, ordered by location. item_count counts records, not units. This is a view of item locations, not the full application Space directory: empty Spaces are absent, and there is no Space UUID. A row with quantity 0 still counts as a record. An organization key receives all visible workspaces; this endpoint has no workspace filter or pagination. Use /keys/workspaces with a user session to discover owned Team IDs, including empty teams.
No URL parameters.
No request body.
curl --silent --show-error --fail-with-body --max-time 30 --request GET \
--url 'https://api.findez.ai/api/v1/spaces' \
--header "Authorization: Bearer $FINDEZ_API_KEY"{
"spaces": [
{
"workspace_id": "10000000-0000-0000-0000-000000000001",
"location": "Shelf B",
"item_count": 1
}
]
}Response fields
| Field | Type / presence | Meaning & constraints | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
spaces | Location[]Required | Nested fields
|
/api/v1/workspaces/summarySummarize workspaces
Permission: workspace:read or org:read · HTTP 200 · standard request budget
Returns visible owned Teams, ordered by name, including Teams with no inventory. item_count is the number of item records. space_count is the number of distinct location strings represented by inventory, not the number of linked Spaces. Empty Spaces do not contribute; identically named locations in a team count once. A workspace key sees its fixed team; an organization key sees currently owned teams. No pagination or workspace query filter is supported.
No URL parameters.
No request body.
curl --silent --show-error --fail-with-body --max-time 30 --request GET \
--url 'https://api.findez.ai/api/v1/workspaces/summary' \
--header "Authorization: Bearer $FINDEZ_API_KEY"{
"workspaces": [
{
"workspace_id": "10000000-0000-0000-0000-000000000001",
"name": "Robotics team",
"item_count": 1,
"space_count": 1
}
]
}Response fields
| Field | Type / presence | Meaning & constraints | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
workspaces | Workspace[]Required | Nested fields
|
/api/v1/keysList key metadata
Owner user session required · HTTP 200
Requires a Supabase user access token. Returns that user's keys newest first, including expired and revoked keys. Neither the raw key nor its hash is returned. No pagination or status filter is supported. Cache-Control is no-store. last_used_at is approximate: authentication schedules an update at most once per five minutes, and a successful authentication may precede a later permission or request failure. A null timestamp means no recorded use, not proof of no requests. It is not an audit log or successful-write receipt.
No URL parameters.
No request body.
curl --silent --show-error --fail-with-body --max-time 30 --request GET \
--url 'https://api.findez.ai/api/v1/keys' \
--header "Authorization: Bearer $FINDEZ_USER_ACCESS_TOKEN"{
"keys": [
{
"id": "40000000-0000-0000-0000-000000000001",
"workspace_id": "10000000-0000-0000-0000-000000000001",
"name": "Team reporting",
"key_prefix": "findez_live_sk_EXAMPL",
"scopes": [
"items:read",
"workspace:read"
],
"created_at": "2026-09-10T12:00:00Z",
"last_used_at": null,
"expires_at": null,
"revoked_at": null
}
]
}Response fields
| Field | Type / presence | Meaning & constraints | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
keys | KeyMetadata[]Required | Nested fields
|
/api/v1/keysCreate an API key
Owner user session required · HTTP 201
Requires an owner's Supabase user access token, not an API key. With workspace_id, only workspace scopes are accepted and the caller must own that team. With workspace_id omitted or null, only org scopes are accepted and the caller must own at least one team. Scope names are trimmed and deduplicated. expires_at must be in the future; omitted/null means no expiration. Prefer an ISO 8601 timestamp with Z or an offset; a timezone-free value is interpreted as UTC. The web form defaults to 90 days, while this HTTP endpoint defaults to no expiration. Returns metadata plus the complete key once, with Cache-Control: no-store. FindEZ stores an Argon2 hash and cannot recover the secret. No endpoint edits an issued key's name, scopes, workspace, or expiration; replace it and revoke the old key. Do not automatically retry issuance after a timeout: it may have created a key whose secret was not received; review the key list and revoke that key before replacing it.
No URL parameters.
| Field | Type / presence | Meaning & constraints |
|---|---|---|
name | stringRequired | Integration label, 1–100 characters before trimming; must not be blank.Min characters: 1 · Max characters: 100 |
workspace_id | uuid | nullOptional | Owned Team UUID for a workspace key. Omit or send null to create an organization key covering the issuing account's owned Teams. Use workspace scopes with a Team UUID and organization scopes without one. |
scopes | string[]Required | 1–8 entries. Workspace: items:read, items:write, import:write, workspace:read. Organization: org:read, org:write. Types cannot be mixed; whitespace is trimmed and duplicates removed.Min entries: 1 · Max entries: 8 |
expires_at | date-time | nullOptional | Future ISO 8601 datetime; omit or null for no expiry. Use Z or an explicit offset; timezone-free input is treated as UTC. |
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/keys' \
--header "Authorization: Bearer $FINDEZ_USER_ACCESS_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"name": "Team reporting",
"workspace_id": "10000000-0000-0000-0000-000000000001",
"scopes": [
"items:read",
"workspace:read"
],
"expires_at": null
}'{
"id": "40000000-0000-0000-0000-000000000001",
"workspace_id": "10000000-0000-0000-0000-000000000001",
"name": "Team reporting",
"key_prefix": "findez_live_sk_EXAMPL",
"scopes": [
"items:read",
"workspace:read"
],
"created_at": "2026-09-10T12:00:00Z",
"last_used_at": null,
"expires_at": null,
"revoked_at": null,
"org_id": "20000000-0000-0000-0000-000000000001",
"created_by": "20000000-0000-0000-0000-000000000001",
"key": "findez_live_sk_EXAMPLE_NOT_A_REAL_KEY"
}Response fields
| Field | Type / presence | Meaning & constraints |
|---|---|---|
id | uuidRequired | |
workspace_id | string | nullRequired | |
name | stringRequired | |
key_prefix | stringRequired | |
scopes | string[]Required | |
created_at | date-timeRequired | |
last_used_at | string | nullRequired | |
expires_at | string | nullRequired | |
revoked_at | string | nullRequired | |
org_id | uuidRequired | |
created_by | uuidRequired | |
key | stringRequired |
Organization read key
curl --silent --show-error --fail-with-body --max-time 30 --request POST \
--url 'https://api.findez.ai/api/v1/keys' \
--header "Authorization: Bearer $FINDEZ_USER_ACCESS_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"name": "All-team reporting",
"workspace_id": null,
"scopes": [
"org:read"
]
}'/api/v1/keys/workspacesFind teams you own
Owner user session required · HTTP 200
Requires the signed-in owner's Supabase user access token. Returns teams owned by that user, ordered by name. Membership alone does not qualify. The returned team_id is the workspace_id used when creating keys. Includes empty teams. Returns an empty workspaces array if none are owned. This endpoint does not accept integration API keys.
No URL parameters.
No request body.
curl --silent --show-error --fail-with-body --max-time 30 --request GET \
--url 'https://api.findez.ai/api/v1/keys/workspaces' \
--header "Authorization: Bearer $FINDEZ_USER_ACCESS_TOKEN"{
"workspaces": [
{
"team_id": "10000000-0000-0000-0000-000000000001",
"name": "Robotics team"
}
]
}Response fields
| Field | Type / presence | Meaning & constraints | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
workspaces | OwnedWorkspace[]Required | Nested fields
|
/api/v1/keys/{key_id}Revoke a key
Owner user session required · HTTP 200
Requires a Supabase user access token for the account that issued the key. Uses the metadata id, not the secret or prefix. Revocation is permanent; retained metadata stays in the list. The secret will be rejected on subsequent authentication. Revocation cannot undo writes already committed. Unknown, other-account, and already-revoked IDs all return 404 key_not_found; a repeated revoke does not return a second success. Create and test a replacement before revoking a key used by an active integration.
| Parameter | Type / location | Meaning & constraints |
|---|---|---|
key_idRequired | uuid · path | Key metadata UUID, not the raw secret or key prefix. |
No request body.
curl --silent --show-error --fail-with-body --max-time 30 --request DELETE \
--url 'https://api.findez.ai/api/v1/keys/40000000-0000-0000-0000-000000000001' \
--header "Authorization: Bearer $FINDEZ_USER_ACCESS_TOKEN"{
"revoked": true,
"id": "40000000-0000-0000-0000-000000000001",
"revoked_at": "2026-09-10T12:00:00Z"
}Response fields
| Field | Type / presence | Meaning & constraints |
|---|---|---|
revoked | trueRequired | |
id | uuidRequired | |
revoked_at | date-timeRequired |
Support & API coverage
This reference covers all public /api/v1 integration endpoints, including the owner-session key-management routes. Other routes used by FindEZ’s own apps have a separate user-session contract and are not enabled by integration keys. This API does not expose deletion of inventory, Space/Team management, document uploads, chat streaming, billing, or account deletion. Use FindEZ for those actions.
The request contract is generated from the current router and validated by the test suite. The route version is v1; no deprecation timetable or compatibility guarantee beyond the documented behavior has been announced. Keep clients tolerant of additional response fields and unexpected errors.
Contact info@findez.ai with the method, path, HTTP status, error code, correlation ID, approximate UTC request time, and a redacted example. Never send the key, user token, Authorization header, or private inventory data.