FurniOpsDocs

API changelog

Every dated API version with its breaking changes, and the additive changes each contracts release brought.

A request names its version in the FurniOps-Version header. Without one, it gets the version its organisation was pinned to on first use. Breaking changes ship only in a new dated version, and an older version keeps its shape: responses and webhook events are rewritten for it. Additive changes (new endpoints, fields, events and enum values) reach every version as they are released.

Versions

VersionStatusBreaking changes
2026-10-04latestnone
2026-10-01supportednone

Breaking changes by version

2026-10-04

No breaking changes.

2026-10-01

No breaking changes.

Additive changes by contracts release

0.103.0

  • The organisation export gains the assistant_notes dataset: the organisation's notes for FurniOps AI, as GET /v1/assistant/memories?scope=org answers them, written after webhook_deliveries. Every member reads them, so the dataset needs no read permission (null, like org). Members' own notes stay out of the organisation's archive.

0.102.0

  • Billing for a live launch. A subscription shows a plan change held for the end of the term (scheduled_change), and cancelScheduledChange drops it. The account carries who invoices are made out to (details), the credit balance a proration left and the overdue invoice of a past-due subscription. updateBillingDetails edits the business name and billing email. The preview says when the plan takes effect (timing, effective_at), what is charged today (amount_due, credit_applied) and when it was quoted (quoted_at, which subscribe takes back to charge exactly the quote); subscribe also takes the billing email and business name. A declined card is the new 402 payment_declined.

0.101.1

  • The changelogs name the assistant FurniOps AI throughout and describe the catalogue consumer features without naming a customer. Text only; no shape changes.
  • The change feed's cursor semantics, documented: a caught-up page's cursor is the feed's horizon, so a page that matched nothing (a quiet catalogues filter, a types filter) still moves forward and a reader that keeps reading never falls behind retention. The stream's heartbeat and end move the last event ID on to how far it read. Descriptions only; no shape changes.

0.101.0

  • Mapping field cartons (variant, repeatable) with a new value kind cartons, which fills the canonical variant's cartons: one cell lists a variant's shipping boxes, split on the column's separator, each size; weight; count in any order (49 x 18 x 7 in; 60 lb; 2 boxes). The column's unit is a length unit; a weight states its own; ; cannot be a cartons separator (CARTONS_CELL_FORMAT).

0.100.0

  • What a consumer that mirrors catalogues needs from FurniOps.
    • Catalogue filter: catalogues (comma-separated cat_ IDs, at most 50) on GET /v1/changes, GET /v1/changes/recent and GET /v1/stream, and filters.catalogues on webhook endpoints: only events whose catalogue is listed (a source health event's is the catalogue the source feeds). The feed cursor moves past the events it skips. Endpoint reads without the field parse as no filter.
    • served_by on runs: which of a source's APIs served the run, named by its connector (Coaster v2, or v1 after a fallback); null for a connector with one API.
    • Stored images: each media[] of a product or variant gains id (img_) and, once stored, stored_url, a stable API URL. The new getMedia (GET /v1/media/{id}, products:read) answers the stored WebP's size and a download link signed for 15 minutes.
    • Products list: catalogue and source filters on GET /v1/products.
    • Offer withdrawal: a source that stops stating an offer withdraws it after a complete run: kept under its ID with quantity 0, no incoming, availability out_of_stock, prices as last stated, and the new withdrawn_at (null or absent while stated); offer.changed says so (facets stock and availability). Stating it again clears withdrawn_at. OFFER_WITHDRAWAL holds the values.
    • SDK clientCredentials({ tokenUrl, clientId, clientSecret, scope? }): a refreshable apiKey for a machine client. One shared token, replaced once a fifth of its life is left (at least 30 s); one token request for concurrent callers; retries on 429, 5xx and lost connections with backoff and Retry-After; client_secret_basic (default) or client_secret_post; the current token served while a refresh fails. When the API answers 401, the client drops the token and sends the call once more with a fresh one. Also ClientCredentialsError, isRefreshableApiKey and furniops.media.get.

0.99.0

  • The assistant is named FurniOps AI in every description, summary and catalogue label. The MCP relay tool is ask_furniops_ai (titled "Ask FurniOps AI"), its answer object is assistant_answer, and the relay's exports are renamed: ASSISTANT_MCP_TOOL, ASSISTANT_MCP_ANNOTATIONS, MCP_ASSISTANT_WAIT_SECONDS, MCP_ASSISTANT_REQUEST_STATE_SECONDS, assistantAskInputSchema, assistantAnswerSchema, ASSISTANT_ANSWER_STATUSES and their types. The relay was not served yet, so no client breaks.

0.98.0

  • FurniOps AI credit: an organisation on a paid plan buys credit (1 credit = 1¢ of list price, held as micro-USD) that Ask AI spends once the day's allowance is used.

    • Eight public routes under Billing, all out of MCP and never an agent's tool: getCreditBalance, listCreditEntries, getCreditRateCard, previewCreditPurchase, createCreditPurchase, listCreditPurchases, getCreditPurchase and updateCreditSettings. Buying and the settings need billing:write (owner only, never an API key) and a person's own token.
    • Schemas for the balance, purchases (a one-off invoice paid in the Payment Element), ledger entries, lots (12-month expiry) and the rate card (tokens per credit per model, worked out from the dated prices). ID prefixes crp, cre and crl.
    • Shared math: listMicros (cost plus margin, rounded once), fundingSplit, creditHoldMicros, spendFromLots (earliest expiry first), money to credit and back, refund clawback per refund, purchase bounds per currency, lot expiry and priceRiseDays (30 days' notice for a rise).
    • AI configuration: margin_bps on dated prices; the platform's default margin (+30%), credit cap and purchase limits on the settings; list_micros and credit_micros on usage rows.
    • Ask AI: the credits refusal reason, funding on the member view, the organisation's daily credit limit on its settings, and a member's cap up to $200 (it may pass the allowance).
    • Events credits.purchased, credits.low, credits.exhausted and credits.expiring (webhooks only), audit actions, email kinds, and the errors currency_unsupported, model_unpriced and price_notice_too_short.

0.97.0

  • FurniOps AI on paid plans: plans gain assistant_daily_micros, the organisation's daily Ask AI spend under the plan (0 by default), and the org settings show it as plan_daily_micros, the ceiling of the organisation's own budget.

0.96.0

  • Contracts for the rest of FurniOps AI.

    • Agent flags: RouteSpec.agent: false (never a tool), people: true (people only) and agentAsks (always asks, on RouteSpec and InternalRouteSpec); AgentTool.agent_asks.
    • Support access: conversation context (platform, support, tenant) beside surface, with org, acting_for and livemode; the FurniOps-Support-Token header; support_token_required, support_token_invalid and support_renewal_required; the token's support_use; grants' use and a burst counted over distinct organisation and member pairs; request logs' via; audit on_behalf_of and AuditDelegationData.
    • Ask AI: the public /v1/assistant/* routes, member and organisation settings, spend, the plan feature assistant, the AI purpose assistant.dashboard and a tenant daily cap. No public route acts alone.
    • MCP, CLI and memory: surfaces dashboard, mcp and cli; the MCP relay's ask input and answer; memory schemas and routes (mem_ IDs, memory_limit, assistant_memories:write, never a key's), whose create and delete tools always ask and are never offered over MCP.

0.95.0

  • Agent tools say whether an agent may run them unasked (acts_alone). A route opts in with agentActsAlone, and only a plain write that previews and can be undone qualifies.

0.94.0

  • Assistant approvals name the tool call they belong to (tool_call_id), so a client can pair each approval with its tool part. Also exports the AssistantConversationDeleted type.

0.92.0

  • Exports gain the organisation kind: everything the organisation holds, one gzip JSON Lines file per dataset (EXPORT_DATASETS, each line as its API read answers it), closed by a manifest. It needs every dataset's read permission (403 insufficient_scope), takes no filters (422 unprocessable), and is neither plan-gated nor metered. Every export file now names its dataset.
  • The agent tool manifest: buildAgentTools(routes, principal) lists every operation a principal may call as an agent's tool, with its risk, preview, undo, re-authentication and whether a person must do it; GET /v1/agent/tools (listAgentTools) serves it. The SDK gains the route.
  • GET /v1/status and GET /v1/status/history (public, kept out of MCP): the platform's measured health per status-page component, the latest level from the last 3 minutes and 90 days of daily rollups (minutes checked, degraded and failing).
  • push_records gets a lean MCP tool. A route can name an mcpBody: the body its tool lists, when its own schema is too large for a client's tool list. The route still checks every call in full. pushRecords lists an outline of RecordsInput (the mode, each product's key and common fields, its variants' and offers'), 3.7 KB in place of 43 KB, and every other field passes through. OpenAPI is unchanged.
  • The identity.brand_claims:write scope reads as one plain sentence on a consent screen, without a parenthetical in the middle.

0.90.0

  • Offers on their own: GET /v1/offers (listOffers, offerListQuerySchema, offerListItemSchema) lists every source's price and stock the caller's licences show, newest first, filtered by variant, product, source, availability or updated_since, each naming its variant and catalogue, and its product, title and SKU where the licences show them; the SDK gains offers.list. The webhook event catalogue (WEBHOOK_EVENT_CATALOG, WEBHOOK_EVENT_GROUPS) names every subscribable event type with its group and description, for event pickers.

0.89.0

  • Vendor portal: a vendor publishes its catalogue to the network and can withdraw it (GET /v1/catalogue, POST /v1/catalogue/publish, POST /v1/catalogue/withdraw; publishing needs the data agreement and a verified vendor profile), hides single products on a plan with product_visibility (POST /v1/catalogue/visibility), and follows each change as a publication run (GET /v1/catalogue/publication-runs[/{id}]). New publishing permission (vendors only; owners and admins write). Products that appear on or leave the network are announced as product.published and product.withdrawn. The own catalogue's variants with price levels (wholesale, MAP, MSRP), availability and issues (GET /v1/catalogue/variants) and its images (GET /v1/catalogue/images). Products are written through a push source with POST /v1/sources/{id}/records, whole (upsert) or as patches (merge); sources say whether they are first_party. Vendor analytics gain available_to, a weekly series and the most read products, each figure from at least k distinct retailers.

0.88.0

  • Claim your brand: the vendor directory (GET /v1/vendor-profiles, any organisation) and a vendor's own brand (GET/PATCH /v1/brand), claims with graded evidence (DNS on the brand's domain, members' verified addresses, FurniOps invitations, trademark references) filed, checked and withdrawn under /v1/brand/claims, and invitation previews. A new vendor-only brand permission whose writes stay with people, the vnd, vcl and vci IDs, and the brand_unverified problem for publishing before a brand is verified. Retailers receive catalogue.verified and catalogue.unverified.

0.81.1

  • The undo route's description: a retried undo answers 409 (the entry is undone already), and undoing the undo's own entry redoes the change.

0.81.0

  • Undo through the audit log: POST /v1/audit-events/{id}/undo (undoAuditEvent, permission audit:write, not idempotent itself, previewable), AuditEvent.undo (kind, available, why not, until when) and undo_of, the undo_unavailable problem, and POST /v1/match-pairs/{id}/reopen (reopenMatchPair), which is now what undoes dismissMatchPair.

0.78.0

  • Previews and undo declarations. Every public write declares how it answers FurniOps-Dry-Run (transactional, custom, or none with a reason) and how it is undone (inverse, a named route, or none with a reason); previewGaps(table) finds writes that do not. New FurniOps-Dry-Run header (true | explain; never ignored: a route that cannot preview answers 422 preview_unsupported), the Preview schema answered by explain on the route's own status as application/vnd.furniops.preview+json, and x-furniops-preview / x-furniops-undo in the OpenAPI document.

0.77.0

  • Billing launch contracts. The plan catalogue: plans.ts (plan intervals, immutable plan prices, catalogue plans) and the public GET /v1/plans. Plan definitions gain live (defaults to true for older definitions; overrides never grant it by omission) and a plan_required problem. Subscribing takes an interval, a promotion code, a billing address and a tax ID; POST /v1/billing/preview prices it with tax; the billing account reports live, the grace window, the address and the tax ID; the subscription reports its interval, cancel_at and discount. New ID prefixes plp, cpn, pmc.

0.76.0

  • AI configuration changes take an optional reason (1–500 characters, trimmed), recorded in the platform audit log with the change.

0.74.0

  • Routes can keep body fields from their MCP tool (mcpOmit): the tool refuses them and its description says a person changes them. update_webhook_endpoint no longer takes url, so text an agent reads can never redirect an endpoint's events.

0.73.1

  • MCP tools' output schemas have an object at the root (the root $ref is inlined), as MCP requires; 2025-era clients no longer see list and resource results wrapped in { result }.

0.73.0

  • MCP tools generated from the route contracts: buildMcpTools(routes, sets) turns every GET route with an API-key permission and a JSON answer into a read tool. Write tools are the non-GET routes an API key may hold, never security routes. A tool's arguments are the route's path parameters and query, plus body, idempotency_key and if_match where the route takes them. They are JSON Schema 2020-12, and the tools come sorted by name. mcpCall turns a tool call into the route's request. A route opts out with mcp: false. mcp/tools.json is generated and checked like the OpenAPI document.

0.72.0

  • Billing routes: GET /v1/billing, POST /v1/billing/subscription, POST /v1/billing/card, POST /v1/billing/subscription/cancel and /resume, GET /v1/billing/invoices. Payments are confirmed with Stripe's Payment Element; Stripe's webhooks change the plan.

0.71.0

  • The request_logs:read permission for the organisation's API request logs. Owners, admins and developers hold it, and an API key may too. Adds the routes GET /v1/request-logs and GET /v1/request-logs/{id} (RequestLog), with logs kept 31 days.

0.70.0

  • The oauth_apps resource: oauth_apps:read and oauth_apps:write for the OAuth apps an organisation registers. Owners, admins and developers hold both. Write is human-only: no API key can mint client secrets. Adds the ocs ID prefix for an OAuth app's client secrets.

0.69.0

  • Shadow record: AgentSettings.shadow counts an agent's shadow proposals of the last 30 days and how many a person later made by hand the same way (agreed) or otherwise (differed), the measure for promoting it.

0.68.0

  • Triage notes: the triage proposal kind, a plain-words explanation of a run's failure or problems from the triage agent. Each cause cites the run's error or one column's cells with one problem (TriageIssue) and says who can fix it (TRIAGE_FIXERS). There is nothing to apply: accepting one answers 409, reject dismisses it. TriageEvidence records what the note was checked against.

0.67.0

  • Source probes and onboarding proposals: POST /v1/source-probes queues a polite probe of a vendor URL (robots.txt obeyed, at most 25 requests), GET /v1/source-probes/{id} and GET /v1/source-probes read it with its report (platform and evidence, walls, the public feed, sitemaps, JSON-LD coverage, an effort estimate). The source proposal kind is the onboarding agent's proposed source from a probe (always high risk; accepting creates it), with its evidence.

0.66.0

  • Mapping proposals from the repair agent: the mapping proposal kind (columns for the headers a mapping left unplaced) with its evidence (rows proved on, problems before and after, fields gained), and the agent mapping tier for columns an agent's proposal applied without a person.

0.65.0

  • GET /v1/org answers the organisation's plan and the features it switches on (CurrentOrganisation); the audit timeline filters by time (since, until); an endpoint's deliveries filter by one event.

0.64.0

  • AI agents' proposals: the proposal resource (prp_), with its payload and proof per kind (category rules first); GET /v1/proposals, GET /v1/proposals/{id}, POST /v1/proposals/{id}/accept|reject; each agent's autonomy and daily budget (GET /v1/agents, PATCH /v1/agents/{agent}); webhook-only proposal.created and proposal.applied events.

    The SDK gains the proposal and agent settings operations.

0.61.0

  • GET /v1/merges (listMerges): the organisation's merges, newest first, by status and product group, to find a merge to undo whoever decided it. A review pair carries merge, the merge a person made from it.

0.59.0

  • Add GET /v1/changes/recent (listRecentChanges): the change feed's events newest first, paged backwards with starting_after, with a forward cursor on the first page for GET /v1/changes or GET /v1/stream.

0.58.0

  • GET /v1/org reads the caller's organisation (ID, name, slug), and GET /v1/me/orgs lists every organisation the signed-in user belongs to with their role (user tokens only). New Organisation and Membership schemas.

0.57.0

  • Server-side product filters: GET /v1/products takes status, brand (ignoring case and spacing), category (that category or below it), availability (at least one offer of it) and q. q matches products whose title or brand holds every word, or with a variant whose SKU or MPN is the text. Every filter given must match. brand, category and q read content fields, so they match only in catalogues whose content the organisation sees from every source.

0.56.2

  • The exports resource description cites its decision instead of a plan step, so consent screens that strip decision refs show it in plain words. A test keeps plan references out of every resource description.

0.56.1

  • Declares 503 unavailable on the routes that can answer it. Creating a source, starting or completing an upload, starting an export, reading the change feed or stream, merging, dismissing or undoing a match, and reading or changing the judge settings can each be refused while a dependency is missing, while the organisation's catalogue is still being set up, or while the instance's wait pool is full. Like a shed, unavailable always carries Retry-After.

0.56.0

  • The public document lists every event pushed to webhook endpoints in OpenAPI webhooks, each with its named schema, including the organisation's own (delivery.disabled, usage.threshold_reached, export.completed) and webhook.test. Responses that name what they made or queued now document their Location header. Reading usage no longer lists quota_exceeded, because it is never refused for a spent quota. The SDK's WebhookEvent now covers every event webhooks deliver. The new ChangeEvent type is what the change feed and stream carry.

0.55.0

  • FurniOps-Mode: live | test picks the data a call acts on. The public spec declares it on every bearer-authenticated operation. An OAuth access token (a person, or an org's machine client) uses it to reach the organisation's sandbox, and is live when it is absent. An API key's mode is its own: the header may repeat it, and naming the other mode is refused with 400 invalid_request.

    The SDK takes mode on the client and per request. The CLI takes --mode live|test (or FURNIOPS_MODE), so a device login can work in test mode.

0.54.1

  • The public OpenAPI document declares the headers its refusals carry, as reusable header components: Retry-After, RateLimit and RateLimit-Policy on every 429 (rate_limited, quota_exceeded), and Retry-After on every 503 shed (overloaded). createExport states the open-export limit as the API applies it: at most 3 pending or running exports per mode, test and live counted apart. The SDK's generated types carry the new response headers.

0.54.0

  • Bulk exports: POST /v1/exports (rate class export, exports:write plus products:read, the plan's bulk exports), GET /v1/exports/{id} with 15-minute download links for its gzip JSON Lines files, GET /v1/exports, the export.completed event, the exp_ ID prefix and the exports policy resource (developer and analyst may export).

0.53.0

  • Feature gates: creating or updating a webhook endpoint with a fat payload answers 403 feature_unavailable when the plan lacks fat webhooks (an endpoint whose plan lost it is sent thin); the change feed and delivery logs keep the plan's feed and delivery days when fewer than the service's 30.

0.52.0

  • Metered usage: GET /v1/usage and GET /v1/usage/series (usage:read), the 429 quota_exceeded error on every authenticated route, the public usage.threshold_reached event (usageEvents), LEVEL_METERS and usageStatus.

0.51.0

  • Rate classes export and stream (RATE_CLASSES, routeRateClass; /v1/stream is stream), rate tier max, and the 503 overloaded error, documented on every route but writes.

0.50.0

  • Webhook endpoints carry api_version: the version whose shape their events have, named in each delivery's FurniOps-Version header. Optional on create (defaults to the request's version) and update.

0.49.0

  • Dated API versions: 2026-10-04 is the first dated version and the latest; 2026-10-01 stays served with the same shape. The version-change registry (VERSION_CHANGES, empty for now) holds each breaking change with transforms keyed by resource object and by event type, which rewrite the latest shape back to the version before it; downgradeToVersion applies the changes newer than a caller's version, newest first, to a response body or an event envelope, and assertVersionChanges keeps the registry ordered and bound to served versions. The API changelog (docs/api-changelog.md) is generated from the registry and this changelog, and gated by generate:check.

0.48.0

  • Test mode: triggerTestEvent (POST /v1/test/events) emits a sample public event in the organisation's sandbox, and the test_mode_required problem (403) answers a live key there.

0.47.0

  • Matching's review routes: listMatchPairs and getMatchPair (review pairs, most probable first, with evidence, conflicts and the AI judge's answer), mergeMatchPair and dismissMatchPair, getMerge and undoMerge, and getJudgeSettings / updateJudgeSettings. New ID prefixes mpr, mrg and mjd.

0.46.0

  • Merge events: product.merged / product.unmerged and variant.merged / variant.unmerged, public, one per aggregate version. A merge names merged_into (its status merged, no fields); an unmerge lists every field again and names unmerged_from.

0.45.0

  • Fat event payloads and category filters: change events carry an optional object, the product, variant or offer as of the event's version (without its children); webhook endpoints take payload: "thin" | "fat" and a categories filter; the change feed and stream take a payload query parameter.

0.44.0

  • Change feed long-poll and live stream: GET /v1/changes takes wait (0 to 25 s); GET /v1/stream (streamChanges) serves the feed as server-sent events with streamQuerySchema. A route's response may declare contentType: "text/event-stream": it is documented as such, with no ETag or 304, and the ETag lint rule exempts it.

0.43.0

  • Webhook deliveries: GET /v1/webhook-endpoints/{id}/deliveries[/{delivery}] (attempt log), POST …/deliveries/{delivery}/resend, POST …/recover (failed deliveries in a time range), POST …/test (webhook.test); coalesce_seconds on endpoints; webhookEvents registry with delivery.disabled (subscribable) and webhook.test, which only webhooks deliver.

0.42.0

  • Webhook endpoints: WebhookEndpoint (secrets by hint only), create/list/get/update/delete and rotate-secret routes; a secret's value appears once, in WebhookEndpointWithSecret.

0.41.0

  • Change feed and products read API: licence field groups (fieldGroupOf, grantsGroup), publicBackendEvents, GET /v1/changes (ChangePage), GET /v1/products and GET /v1/products/{id} (Product, ProductVariant, ProductOffer).

0.40.0

  • Freshness routes: POST /v1/demand records the variants a tenant wants kept fresh (their sources are checked at their fastest until the demand lapses), and POST /v1/variants/{id}/checks checks one variant's offers now, joining a run under way or answering with one that just finished. Both need the new freshness:write permission, which developers hold.

0.39.0

  • Sources: schedule.adaptive (default true) lets FurniOps check a source sooner when it changes often, and the read-only freshness shows what it learned per run stream (interval_minutes, changes_per_day, observations, learned_at). freshness defaults to nothing learned, so source events published before it still parse.

0.38.0

  • Mapping field tags: a product-level, repeatable list that fills the canonical product's tags.

0.37.0

  • A canonical tags field on products; the credentials run error code; waiting on runs (a rate limit or a retry, until when, the attempt); the inbound message log (GET /v1/sources/{id}/inbound-messages, eml IDs) and POST /v1/sources/{id}/inbound-address/rotate; testSource covers API sources through their connector.

0.36.0

  • Categories: the FurniOps taxonomy (listCategories; ID prefix ctg, each category naming its GS1 GPC class) and per-source category rules (listSourceCategories, saveCategoryRules), the most specific rule winning. A canonical product carries the FurniOps category its source's rules file it under.

0.35.0

  • Uploads and mappings: staged uploads (createUpload, getUpload, completeUpload; ID prefix upl) and the mapping flow (listMappings, getMapping, confirmMapping; ID prefix map). The canonical field list (MAPPING_FIELDS, with level, kind and repeatability), mapping tiers (built_in, learned, template, synonym, person) and the shared validator mappingProblems(headers, columns) move to contracts.

0.34.0

  • First sources: SourceConfig.warehouse, price_code (api) and inbound.senders (file), SourceSchedule.stock_interval_minutes with sourceScheduleProblems, Source.inbound_address, run streams (RUN_STREAMS, Run.stream) and an optional createRun body (RunCreate), with a bodyOptional route flag the OpenAPI document honours.

0.33.0

  • Runs carry problems: the first cells normalise could not use (sheet, line, column, field, kind and detail), up to RUN_PROBLEMS_KEPT. stats["normalise.problems"] still counts every one.

0.32.0

  • Source health: status_reason on sources (the run error behind degraded or blocked), and the public events source.degraded, source.recovered and source.blocked.

0.31.0

  • Runs: POST /v1/sources/{id}/replay (replayRun) queues a run that processes the source's current snapshot again without fetching, and the run error code no_snapshot.

0.30.0

  • Catalogue change events: product.created / updated / discontinued, variant.created / updated, offer.created / changed, all public. Each carries the diff per claim field with the new value's provenance; an offer change is one event per version with facets (price, stock, availability) for filtering. New changes schemas and offerFacetOf.

0.29.0

  • Runs: the run object (triggers, statuses, stages, error codes), POST /v1/sources/{id}/runs, GET /v1/sources/{id}/runs, GET /v1/runs/{id}. A file source may now have a URL to be fetched from.

0.28.0

  • Sources say how their values read: config.currency, config.decimal and config.role (supplier or retailer), with sourceReading(kind, config) filling the defaults (usd, a dot, and retailer for storefront kinds, supplier otherwise).

0.27.0

  • Sources: the Source resource, its create/update/credentials/test bodies, sourceConfigProblems() (a kind's config rules, shared by the console and the API), seven public routes under /v1/sources.

0.26.0

  • The canonical model v1: canonicalProductSchema with variants, offers, dimensions, cartons, rugs, compliance and media; canonical integer units (toHundredthsInch, toHundredthsPound, measuredLength, measuredWeight); GTIN check digits; provenance methods, source kinds, lifecycle and availability vocabularies; ID prefixes cat, lic, rec.

0.24.0

  • Add the first public route, listAuditEvents (GET /v1/audit-events, audit:read), with action and actor filters; openapi/public.json and the policy matrix regenerated.

0.22.0

  • The feature_unavailable and plan_limit_reached problems (403): an org's plan does not include a feature, or the org is at one of its plan's limits.

0.20.0

  • Resource scopes: RESOURCE_SCOPE_KINDS (source → src_), ResourceScope (<kind>:<id>), isResourceScope, resourceScopeSchema, parseResourceScopes (known vs unknown kinds from the feed), scopeSelection and inScope; Principal.resourceScopes. Scopes narrow data, never routes. A membership's resource_scopes on the feed is now bounded (500 × 200 characters).

0.18.0

  • The akr ID prefix: review items identity opens when an API key shows suspicious activity.

0.17.0

  • Problems interaction_superseded (409: a newer authorization request replaced this one in the browser) and account_mismatch (409: the application asked for a different account than the one signed in), so a client tells them apart from a plain conflict without reading detail.

0.16.0

  • Single sign-on: the sso resource (sso:read, sso:write; owners and admins, human only, never API keys), the dom and sso ID prefixes, the sso_required problem (403).

0.11.0

  • OpenAPI for first-party APIs and one shared artifact gate.

    • contracts: OpenApiInfo.auth ("apiKey" default, or "session", documenting the __Host-fo_session cookie); SESSION_COOKIE is exported as the single source of the name; every write route now lists the CSRF refusal (permission_denied, 403), public ones included.

0.9.0

  • Add the pkey ID prefix (passkeys) and the reauthentication_required error code (403), for identity's passkeys and sudo.

0.8.0

  • Add the ses ID prefix (sessions) and the invalid_credentials error code (401), for identity's sign-in.

0.7.0

  • Routes a signed-in principal calls on itself.

    • contracts: RouteSpec.permission may be "authenticated" (any principal, no org permission; RoutePermission, isPolicyKeyword). OpenAPI documents the credential errors but no scopes or permission errors, and the matrix opens it to every role. Principal.role is optional: a principal without an org context holds no org permission.

0.6.0

  • Handlers bound to route contracts.

0.5.0

  • Policy skeleton and authorisation matrix.

    • contracts: permissions (<resource>:<read|write>), built-in role grants, Principal with restricted credentials (intersection), effectivePermissions and can. Route contracts replace scopes with permission (or "public") plus a risk class, checked at definition. buildPermissionMatrix / renderPermissionMatrix; policy/matrix.md is generated beside openapi/public.json by the renamed generate / generate:check scripts.

0.4.0

  • contracts: route contracts (defineRoute, createRouteTable, isIdempotentRoute) and buildOpenApiDocument (OpenAPI 3.1 via zod-openapi), which adds the shared headers (FurniOps-Version, Request-Id, Idempotency-Key, ETag/If-None-Match/304, If-Match), the conventional and domain problem responses, and the x-furniops-* extensions. publicRoutes with PUBLIC_API_INFO; the generated openapi/public.json is published as @furniops/contracts/openapi.json.

0.2.0

  • contracts: event registry (defineEvent, createEventRegistry), cursor pagination (listQuerySchema, listSchema, encodeCursor/decodeCursor, toPage), money in minor units (moneySchema, exact moneyFromDecimal/moneyToDecimal), length and weight measurements with converters, documented envelope fields, and error codes aligned with the IETF Idempotency-Key draft (idempotency_key_invalid 400, idempotency_request_in_progress 409, idempotency_key_reused 422; idempotency_conflict removed) plus one default code per status via defaultCodeForStatus.

On this page