Skip to documentation
FindEZ / Developers
OpenAPI JSONManage API keys ↗

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.

Base URLhttps://api.findez.ai/api/v1

Public documentation · No sign-in needed · Examples use fictional data

Your first connection

  1. 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.
  2. 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.
  3. 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.
  4. 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_KEY

Export 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_KEY
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"

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.

Available scopes
ScopeKey typeAllows
items:readWorkspaceList items and run structured queries
workspace:readWorkspaceRead location counts and workspace summaries
items:writeWorkspaceCreate and patch items
import:writeWorkspaceBulk upsert items
org:readOrganizationItem queries, lists, locations, and summaries across owned teams
org:writeOrganizationCreate, 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

  1. Choose which system owns each field to avoid overwriting edits made in FindEZ.
  2. Link the destination Spaces and verify their exact names. For organization keys, map each record to an owned Team UUID.
  3. Choose a stable source_system namespace and an external_id for each source record. The same identity in a different workspace is a different record.
  4. 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.
  5. 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.
  6. Reconcile results with a read-enabled key. processed acknowledges 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

Default per-key budgets
LimitDefaultApplies to
Standard requests120 / minuteAll API-key endpoints except bulk, including /whoami
Bulk requests10 / minutePOST /items/bulk, in a separate budget
Bulk items500 / requestAt 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.

Error code reference
HTTPCodeWhat to do
400invalid_scopeScopes are empty after normalization or contain an unknown scope. Use the permission table.
400scope_type_mismatchWorkspace and organization scope types do not match workspace_id.
400invalid_expirationChoose an expires_at in the future.
400workspace_requiredAdd workspace_id to each organization-key item write.
400empty_updateSupply at least one supported PATCH field.
400duplicate_external_idRemove repeated external identities within a bulk request.
400invalid_inventory_requestCheck the query and exact linked Space name; resolve ambiguous names in FindEZ.
401invalid_api_keySupply the complete integration key in the Bearer header; check for copy errors.
401wrong_environmentUse a key issued by this environment. Production uses findez_live_sk_.
401revoked_api_keyReplace the revoked key; it cannot be restored.
401expired_api_keyCreate a replacement key with an appropriate expiry.
403insufficient_scopeCreate a key with the required permission; writing does not imply reading.
403workspace_access_deniedCheck the team owner and key workspace. A workspace key cannot select another team.
403organization_requiredCreate or own a Team before issuing an organization key.
404item_not_foundCheck item_id and current workspace visibility.
404key_not_foundCheck the key metadata id and issuing account; already-revoked keys return this too.
409duplicate_external_idThe identity already exists. Use bulk upsert or a distinct identity.
413bulk_payload_too_largeSplit the batch below the configured server cap. More than 500 rows fails schema validation with 422.
422invalid_requestCheck JSON structure, unknown properties, UUIDs, required fields, types, lengths, and numeric limits. Field-level details are not returned.
429rate_limit_exceededWait for Retry-After (the per-key limiter sends 60 seconds), then retry with bounded backoff.
503authentication_unavailableAuthentication storage is temporarily unavailable.
503rate_limit_unavailableThe request-budget check is temporarily unavailable.
503key_creation_unavailableKey issuance failed. Check the saved key list before trying again.
503key_list_unavailableKey metadata could not be loaded.
503workspace_list_unavailableOwned teams could not be loaded.
503key_revocation_unavailableRevocation could not be completed; check status before retrying.
503database_unavailableThe inventory operation failed. Reconcile ambiguous writes before retrying.
500internal_errorAn 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.

GET/api/v1/whoami

Test 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
Success response
FieldType / presenceMeaning & constraints
key_iduuidRequired
workspace_idstring | nullRequired
scopesstring[]Required
GET/api/v1/items

List 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.

URL parameters
ParameterType / locationMeaning & constraints
pageOptionalinteger · queryOne-based page number.Minimum: 1 · Default: 1
page_sizeOptionalinteger · queryMaximum records in a page, from 1 to 100.Minimum: 1 · Maximum: 100 · Default: 50
workspace_idOptionaluuid | null · queryTeam 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
Success response
FieldType / presenceMeaning & constraints
itemsItem[]Required
Nested fields
items fields
FieldType / presenceMeaning & constraints
item_iduuidRequired
workspace_iduuidRequired
namestringRequiredItem name. Leading/trailing whitespace is removed; blank values are rejected.
categorystringRequiredCategory label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.
quantityintegerRequiredStored unit quantity, as a whole number from 0 to 100000 inclusive. Writes set the absolute quantity; they do not increment it.
locationstringRequiredExact, 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.
image_urlstring | nullRequiredImage reference string; this endpoint does not upload a file or return a newly signed image URL. Use a URL your intended readers can access.
barcodestring | nullRequiredBarcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.
purchase_sourcestring | nullRequiredPurchase source or vendor text.
notesstring | nullRequiredFree-text notes.
brandstring | nullRequiredBrand or manufacturer text.
part_numberstring | nullRequiredPart identifier string. Preserve formatting and leading zeroes for exact matching.
source_systemstring | nullRequiredExternal integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert.
external_idstring | nullRequiredStable record identifier in the source system. Reuse the same value for later syncs; do not generate a new ID each run.
created_atstringRequiredStored creation timestamp. Older inventory records may omit a timezone offset; this is not a modification checkpoint.
pageintegerRequired
page_sizeintegerRequired
totalintegerRequired
POST/api/v1/items

Create 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.

JSON request body
FieldType / presenceMeaning & constraints
workspace_iduuid | nullOptionalTeam 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.
namestringRequiredItem name. Leading/trailing whitespace is removed; blank values are rejected.Min characters: 1 · Max characters: 200
categorystringOptionalCategory label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.Min characters: 1 · Max characters: 100 · Default: "Other"
quantityintegerOptionalStored 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
locationstringRequiredExact, 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_urlstring | nullOptionalImage 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
barcodestring | nullOptionalBarcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.Max characters: 100
purchase_sourcestring | nullOptionalPurchase source or vendor text.Max characters: 200
notesstring | nullOptionalFree-text notes.Max characters: 2000
brandstring | nullOptionalBrand or manufacturer text.Max characters: 100
part_numberstring | nullOptionalPart identifier string. Preserve formatting and leading zeroes for exact matching.Max characters: 100
source_systemstring | nullOptionalExternal integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert.Max characters: 100
external_idstring | nullOptionalStable 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
Success response
FieldType / presenceMeaning & constraints
itemobjectRequired
Nested fields
item fields
FieldType / presenceMeaning & constraints
item_iduuidRequired
workspace_iduuidRequired
namestringRequiredItem name. Leading/trailing whitespace is removed; blank values are rejected.
categorystringRequiredCategory label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.
quantityintegerRequiredStored unit quantity, as a whole number from 0 to 100000 inclusive. Writes set the absolute quantity; they do not increment it.
locationstringRequiredExact, 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.
image_urlstring | nullOptionalImage reference string; this endpoint does not upload a file or return a newly signed image URL. Use a URL your intended readers can access.
barcodestring | nullOptionalBarcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.
purchase_sourcestring | nullOptionalPurchase source or vendor text.
notesstring | nullOptionalFree-text notes.
brandstring | nullOptionalBrand or manufacturer text.
part_numberstring | nullOptionalPart identifier string. Preserve formatting and leading zeroes for exact matching.
source_systemstring | nullOptionalExternal integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert.
external_idstring | nullOptionalStable record identifier in the source system. Reuse the same value for later syncs; do not generate a new ID each run.
created_atstringOptionalStored creation timestamp. Older inventory records may omit a timezone offset; this is not a modification checkpoint.
POST/api/v1/items/bulk

Sync 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.

JSON request body
FieldType / presenceMeaning & constraints
itemsAPIBulkItem[]Required1–500 records with stable external identities. A configured server cap can be lower.Min entries: 1 · Max entries: 500
Nested fields
items fields
FieldType / presenceMeaning & constraints
workspace_iduuid | nullOptionalTeam 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.
namestringRequiredItem name. Leading/trailing whitespace is removed; blank values are rejected.Min characters: 1 · Max characters: 200
categorystringOptionalCategory label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.Min characters: 1 · Max characters: 100 · Default: "Other"
quantityintegerOptionalStored 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
locationstringRequiredExact, 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_urlstring | nullOptionalImage 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
barcodestring | nullOptionalBarcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.Max characters: 100
purchase_sourcestring | nullOptionalPurchase source or vendor text.Max characters: 200
notesstring | nullOptionalFree-text notes.Max characters: 2000
brandstring | nullOptionalBrand or manufacturer text.Max characters: 100
part_numberstring | nullOptionalPart identifier string. Preserve formatting and leading zeroes for exact matching.Max characters: 100
source_systemstringRequiredExternal integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert. Required for bulk sync; surrounding whitespace is trimmed and blank values are rejected.Min characters: 1 · Max characters: 100
external_idstringRequiredStable record identifier in the source system. Reuse the same value for later syncs; do not generate a new ID each run. Required for bulk sync; surrounding whitespace is trimmed and blank values are rejected.Min characters: 1 · Max characters: 200
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
Success response
FieldType / presenceMeaning & constraints
processedintegerRequired
PATCH/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.

URL parameters
ParameterType / locationMeaning & constraints
item_idRequireduuid · pathItem UUID from a create response or inventory read.
JSON request body
FieldType / presenceMeaning & constraints
namestringOptionalItem name. Leading/trailing whitespace is removed; blank values are rejected.Min characters: 1 · Max characters: 200
categorystringOptionalCategory label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.Min characters: 1 · Max characters: 100
quantityintegerOptionalStored 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
locationstringOptionalExact, 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_urlstring | nullOptionalImage 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
barcodestring | nullOptionalBarcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.Max characters: 100
purchase_sourcestring | nullOptionalPurchase source or vendor text.Max characters: 200
notesstring | nullOptionalFree-text notes.Max characters: 2000
brandstring | nullOptionalBrand or manufacturer text.Max characters: 100
part_numberstring | nullOptionalPart identifier string. Preserve formatting and leading zeroes for exact matching.Max characters: 100
source_systemstring | nullOptionalExternal integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert.Max characters: 100
external_idstring | nullOptionalStable 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
Success response
FieldType / presenceMeaning & constraints
updatedtrueRequired
item_iduuidRequired
POST/api/v1/query

Filter 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.

JSON request body
FieldType / presenceMeaning & constraints
resource"items"OptionalOnly items is supported.Default: "items"
workspace_iduuid | nullOptionalTeam 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.
filtersInventoryFilter[]OptionalUp to 10 ANDed filters; empty array matches all visible rows.Max entries: 10
Nested fields
filters fields
FieldType / presenceMeaning & constraints
fieldstringRequiredOne of the listed item fields. Text and quantity have different operator/value rules.Allowed: "name", "category", "location", "brand", "part_number", "barcode", "quantity", "source_system", "external_id"
opstringOptionalText: eq or neq only. Quantity: eq, neq, gt, gte, lt, lte. Defaults to eq.Allowed: "eq", "neq", "gt", "gte", "lt", "lte" · Default: "eq"
valuestring | integerRequiredText: case-sensitive string of at most 200 characters. Quantity: JSON integer 0–100000; no strings, booleans, decimals, or null.
aggregatestring | nullOptionalcount = item records; sum_quantity = stored unit total across all matches. Omit/null to return an item page.Allowed: "count", "sum_quantity"
pageintegerOptionalOne-based page; maximum 1000000. Validated even for aggregates.Minimum: 1 · Maximum: 1000000 · Default: 1
page_sizeintegerOptional1–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
Success response: QueryPage
FieldType / presenceMeaning & constraints
resource"items"Required
itemsItem[]Required
Nested fields
items fields
FieldType / presenceMeaning & constraints
item_iduuidRequired
workspace_iduuidRequired
namestringRequiredItem name. Leading/trailing whitespace is removed; blank values are rejected.
categorystringRequiredCategory label. Leading/trailing whitespace is removed; blank values are rejected. No fixed category enumeration.
quantityintegerRequiredStored unit quantity, as a whole number from 0 to 100000 inclusive. Writes set the absolute quantity; they do not increment it.
locationstringRequiredExact, 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.
image_urlstring | nullRequiredImage reference string; this endpoint does not upload a file or return a newly signed image URL. Use a URL your intended readers can access.
barcodestring | nullRequiredBarcode string. Preserve leading zeroes; no barcode lookup or normalization is performed by this API.
purchase_sourcestring | nullRequiredPurchase source or vendor text.
notesstring | nullRequiredFree-text notes.
brandstring | nullRequiredBrand or manufacturer text.
part_numberstring | nullRequiredPart identifier string. Preserve formatting and leading zeroes for exact matching.
source_systemstring | nullRequiredExternal integration namespace, for example erp. Together with workspace_id and external_id, identifies a row for bulk upsert.
external_idstring | nullRequiredStable record identifier in the source system. Reuse the same value for later syncs; do not generate a new ID each run.
created_atstringRequiredStored creation timestamp. Older inventory records may omit a timezone offset; this is not a modification checkpoint.
pageintegerRequired
page_sizeintegerRequired
totalintegerRequired
Success response: Aggregate
FieldType / presenceMeaning & constraints
resource"items"Required
aggregatestringRequiredAllowed: "count", "sum_quantity"
valueintegerRequired
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
}
GET/api/v1/spaces

List 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
Success response
FieldType / presenceMeaning & constraints
spacesLocation[]Required
Nested fields
spaces fields
FieldType / presenceMeaning & constraints
workspace_iduuidRequired
locationstringRequired
item_countintegerRequired
GET/api/v1/workspaces/summary

Summarize 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
Success response
FieldType / presenceMeaning & constraints
workspacesWorkspace[]Required
Nested fields
workspaces fields
FieldType / presenceMeaning & constraints
workspace_iduuidRequired
namestringRequired
item_countintegerRequired
space_countintegerRequired
GET/api/v1/keys

List 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
Success response
FieldType / presenceMeaning & constraints
keysKeyMetadata[]Required
Nested fields
keys fields
FieldType / presenceMeaning & constraints
iduuidRequired
workspace_idstring | nullRequired
namestringRequired
key_prefixstringRequired
scopesstring[]Required
created_atdate-timeRequired
last_used_atstring | nullRequired
expires_atstring | nullRequired
revoked_atstring | nullRequired
POST/api/v1/keys

Create 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.

JSON request body
FieldType / presenceMeaning & constraints
namestringRequiredIntegration label, 1–100 characters before trimming; must not be blank.Min characters: 1 · Max characters: 100
workspace_iduuid | nullOptionalOwned 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.
scopesstring[]Required1–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_atdate-time | nullOptionalFuture 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
Success response
FieldType / presenceMeaning & constraints
iduuidRequired
workspace_idstring | nullRequired
namestringRequired
key_prefixstringRequired
scopesstring[]Required
created_atdate-timeRequired
last_used_atstring | nullRequired
expires_atstring | nullRequired
revoked_atstring | nullRequired
org_iduuidRequired
created_byuuidRequired
keystringRequired
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"
  ]
}'
GET/api/v1/keys/workspaces

Find 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
Success response
FieldType / presenceMeaning & constraints
workspacesOwnedWorkspace[]Required
Nested fields
workspaces fields
FieldType / presenceMeaning & constraints
team_iduuidRequired
namestringRequired
DELETE/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.

URL parameters
ParameterType / locationMeaning & constraints
key_idRequireduuid · pathKey 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
Success response
FieldType / presenceMeaning & constraints
revokedtrueRequired
iduuidRequired
revoked_atdate-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.