Offers: pricing, approval, and where a license is held¶
A product is what you publish. An offer is the terms on which someone may take it. One product can carry several offers, and each one independently answers four questions: who may see it, what it costs, who approves it, and where the resulting license is held.
POST /software-publishing/v1/products/{productId}/offers
{
"audience": "PUBLIC",
"licenseModel": "SUBSCRIPTION",
"approvalPolicy": "EXTERNAL",
"externalAuthorityAccountId": "acct-4711",
"priceMinor": 4900,
"currency": "USD",
"licenseHolder": "DEVICE"
}
Field |
Values |
|---|---|
|
|
|
|
|
|
|
required when |
|
minor units, non-negative; currency defaults to |
|
|
These four axes are independent. licenseModel is the commercial shape,
approvalPolicy is who decides, licenseHolder is where the license lives, and the
product’s kind is what the artifact is. Choosing a subscription does not imply an
approval; choosing an external authority does not imply a device-held license.
Where the license is held¶
This is the choice that changes what your customer manages every day.
ACCOUNT — the customer’s account holds a pool of seats. They subscribe once and
assign seats to terminals, unassigning to recover capacity. This is the model for a
web application, and for software licensed by headcount rather than by machine.
DEVICE — the robot holds the license. There is no seat pool at all:
One request and one decision per robot.
The signed license names the robot itself, so the license stays with that machine. There is nothing to assign, and an attempt to assign one is refused with
409 device_held_license_not_assignable.Issuance also mints an X.509 certificate for that robot, bound to its identity and delivered over the device’s own channel. Your application validates it at start-up: the certificate, not a console flag, is what authorises the software to run.
Issuance and certificate delivery are one unit. If the certificate cannot be delivered — the owning account does not hold the product, or the listing has no current published version — issuance is refused with
409 device_certificate_delivery_unavailableand the request stays approved and retryable rather than becoming a license nothing can enforce.
If you omit licenseHolder, it is derived from what you published: firmware and
applets default to DEVICE, everything else to ACCOUNT. State it explicitly when
the default is not what you want.
Warning
audience, licenseModel and licenseHolder are fixed when the offer is
created. A license snapshots its holder model at request time, so changing it under
issued licenses would rewrite what was already granted. Offer edits cover the
commercial fields only — price, currency, approval policy, and the external authority.
To change the audience, the commercial model, or the holder, author a new offer and
retire the old one.
Who approves¶
|
Decision maker |
|---|---|
|
nobody — a license is issued on request |
|
decided inside the account that holds the request |
|
the account named in |
EXTERNAL makes the licensing authority a distinct account from you: you publish,
they decide. An offer that declares EXTERNAL without naming an authority account is
rejected with 400, field externalAuthorityAccountId, and the same invariant is
re-checked on every edit — you cannot clear the authority off a live external offer.
The integrator’s side of that relationship — polling, deciding, delegating back to the customer, compensating, and the licensed-subject roster — is documented in External software-licensing integration.
Going live¶
An authored offer starts in DRAFT: it exists but nobody can take it. Publish it
explicitly:
POST /software-publishing/v1/offers/{offerId}/lifecycle
{ "action": "PUBLISH" }
PUBLISH moves DRAFT or SUSPENDED to ACTIVE, and requires the product itself to
be published. A firmware offer additionally requires a release carrying signature
evidence — an unsigned firmware offer is refused with 422 firmware_release_unsigned.
SUSPEND takes an ACTIVE offer out of circulation; RETIRE is terminal, and a
retired offer can no longer be edited.
From the CLI¶
fgai publisher offer-create --product <product-id> --body-file offer.json
fgai publisher offer-lifecycle --offer <offer-id> --body '{"action":"PUBLISH"}'
fgai publisher offer-list --product <product-id>