im-web business rules, in detail

What we know about how image licensing works in the new image portal, as of 2026-10-05. Gathered from the LIP-1338 branch code, the products API spec, the 2026-09-30 user tests and the team's statements during them.

Sources

Each rule is tagged with where it comes from.

  • [API] products API spec, im-web packages/openapi-client/schemas/products.json at d62190c. The live API docs (below) show what the team is working on now and may have moved on.
  • [Code] im-admin / im-public in im-web, branch feature/LIP-1338-scopes-real-api as of 2026-10-05: d8821ea.
  • [UI] text or behaviour seen on screen during the tests.
  • [Said] stated by the team during the tests (mostly Ferdinand, UX).
  • [Today] how NTB works today, per the testers.
  • [Open] undecided or unverified.

Live API docs on the test environment, one per API. They change with the team's current work; cite a schema at an im-web commit for a stable reference.

APICoversSwaggerSpec
productsscopes, scope parameters, metadata fields, licenses, products, prices, subscriptions, price lists, price resolverUIJSON, YAML
salessuppliers, supplier mappings, distributor config, downloads and purchasesUIJSON, YAML
usertenants, users, roles, workspaces, enterprise organizationsUIJSON, YAML
searchmedia search, media objectsUIJSON, YAML
indexertenant indices, reindexingUIJSON, YAML

Content: what can be sold

  • A metadata field is an asset property and its possible values, e.g. distributor: NTB, AP, AFP. It has a key, a label and a data type (enum, multi-enum, free text, date, number). Developers set them up. [API] [UI]
  • A scope parameter is a filter scopes can use, built on one or more metadata fields (e.g. Credit uses credit and owner, Dates uses dateCreated and dateArchived). It has a field type, can be required, and is in use or unused. System admins set them up. [API] [UI]
  • A main group (primary scope) is a saved filter: chosen values for scope parameters, e.g. Archive = SDL and Media family = editorial. Every required parameter gets one value. [API] [Code]
  • A content group (secondary scope) narrows one main group by setting more scope parameters. It keeps the main group's values. [API]
  • Filter logic: AND across parameters, OR within a multi-value parameter. An unset parameter doesn't filter. [Code]
  • Selections are explicit value-id lists. There is no "all except" and no "all, including future values". [API]
  • A content group can be saved with no parameters. It then equals its main group. [Code]
  • Content groups have a priority order within their main group. It decides which applies when groups overlap. [Code] [Open: backend semantics]
  • New assets that match a filter join it automatically. [Said]

Licenses

  • A license is a name plus a description (terms text). Nothing else. [API]
  • Every license belongs to exactly one license group. Licenses can't be shared between groups. [API]
  • A license group can be linked to scopes (scopeIds). [API]
  • The license description is shown to customers on the purchase panel. [UI] [Code]
  • The admin requires the description. The API doesn't. [Code]
  • Licenses carry no usage dimensions (channel, duration, reach, paid promotion). [API]

Products and variants

  • A product = one main group + one license group + a base price per selected license. [API]
  • A product can include a subset of its license group's licenses. [UI]
  • A variant is a product of type variant with a parent product. It points at a content group and has its own base prices. [API]
  • Each main group should be used by one product. Not enforced in the admin. [Said] [Open]
  • A license group can serve many products. [Said]
  • Product state is draft or published. [API]
  • No admin screen sets the state. Every product stays Draft. [Code]
  • Draft products are still priced on the public site. [UI] [Open: is that intended?]

Prices

  • A base price is per license, per product, per download. [API]
  • Each base price has validFrom / validTo. [API]
  • The admin always saves base prices valid from now for 100 years. Price changes can't be scheduled. [Code]
  • Currency: NOK only. [API]
  • Prices are shown "in NOK, VAT (25%) excluded" on the public site. [UI]
  • The price customers see without an agreement is the "Global" price. [Said, Test 2] [Open: exact definition]

Customer-specific terms

Both point at a customer group (customerGroupId). The admin UI calls it "Workspace". [API] [UI]

  • Ferdinand: it should be "Customer Group", to choose either single companies or global. [Said]

Discounts and premiums (API: price list)

  • One price list = one customer group + one product + optional validity dates. [API]
  • Each item targets one base price, with either an overridePrice or a discountPercentage. [API]
  • A percentage follows base price changes. A fixed override doesn't. [Code, per audit]
  • A premium is a price above base. [UI]

Subscriptions

  • One subscription = one customer group + one product + a license group + included licenses + price + validity dates. [API]
  • Type time: unlimited downloads within the date range, for a fixed price. [API] [UI]
  • Type quota: a fixed number of downloads for the period. Access stops when the quota is reached. [API] [UI]
  • Each included license can have its own quantityLimit. [API]
  • A subscription can be disabled. Disabled subscriptions can't be used to resolve prices. [UI]
  • A subscription customer can still buy other uses at their own price. [Said, "that's the plan"]
  • After a subscription was created in Test 2, the public page still showed a purchase price, not a download. [UI] [Open: bug or not built]

Price resolution

  • POST /api/v1/price-resolver takes an asset's metadata and a customerGroupId. [API]
  • It returns one product for the asset, with per-license base price, validity, and any subscription and price list that apply. [API]
  • It does not return a final price or which rule won. [API]
  • im-public uses it on the asset page. The admin doesn't. [Code]
  • Precedence between subscription, discount, variant and base price is undefined. [Open]

Customer side

  • The asset page shows a purchase panel: license options with price, an instructions box, and "Add to cart". [UI]
  • The last license option should be "I can't find the license I need", leading to the customer centre. [Said]
  • Logged-in customers with a subscription should see a direct download. [Said, not built]
  • Re-download of purchased images will have a time limit. [Said] [Open: how long]
  • Private customers (e.g. a print for the wall) should be able to buy without contacting the customer centre. [Today, Test 2 wish]
  • Scandinavian Creative will have a lower threshold for anyone to buy. [Said]
  • Editorial images can be visible but not buyable until the customer is approved. [Said, earlier FigJam board]
  • Magnum images are sold only with Magnum's approval. [Today, Test 2]

Markets and languages

  • Tenants: NTB and TT (Swedish) at least. Tests ran on TT. [UI]
  • The product is white label for Sweden, Finland and Denmark. [Said]
  • Customer side in Norwegian and English. Admin maybe English only. [Said]
  • Names and license text are single-language today. [API]

How it works today

  • A legacy contract system holds customer agreements. [Today]
  • Exclusions use priority layering: a broad contract plus a higher-priority "not this" contract (e.g. no NYT). [Today, Test 1]
  • Most customers have tailor-made deals. [Today]
  • The customer centre sets up subscriptions, "klippekort" (download bundles), per-customer prices and private customer sales. [Today, Test 2]
  • Usage is negotiated per order by email: channel, duration, accounts, paid promotion. [Today, Test 1]
  • Rollout is likely gradual, e.g. Amedia first, with old and new systems in parallel. [Said]

Open questions

  • Price precedence: agreement, subscription, discount, variant, base.
  • What Draft means for sales, and how publishing works.
  • Exclude rules and "all, including future values".
  • One main group per product: enforced or not.
  • Content group overlap and priority semantics.
  • Usage dimensions on licenses.
  • An agreement object per customer or enterprise.
  • Re-download rules and time limits.
  • Approval gating (editorial, Magnum): who approves, where it's stored.
  • "Global" customer group: what it is and how it's set.
  • Which texts customers see, and in which languages.

Corrections to the order statechart assumptions

Assumptions made in the earlier order statechart prototype, and what we know now.

Assumption in the statechartWhat we now know
List price 1 200 kr, VAT 25% at checkoutBase price per license per product. Tests used 1 711 kr. Shown "VAT (25%) excluded".
Custom price is 15% offDiscount or premium per customer group and product, as a percentage or a fixed override per license.
Subscription: 12 months, 4 900 krTime-limited: unlimited downloads in a date range, fixed price. Any dates.
Pack: 3 images, 2 900 kr"Per quota" subscription (today: "klippekort"). Download limit per subscription and per license.
"Select customer" step droppedReal form selects a customer group ("Workspace" in UI, to be renamed).
"Choose licenses that fit usage"Subscription picks included licenses from the product's license group.
Customer contacts support via phone, email, chatPlanned as a last license option: "I can't find the license I need".
Subscription holder downloads directlyPlanned. Not built: Test 2 still showed a purchase price.
Editorial gating (not modelled)Confirmed as an idea. Also supplier approval (Magnum).