Submit a licensing request
POST /api/integrations/v1/licensing-requestsUse this endpoint after your own commercial process has approved an order. You execute the PO, contract or invoice outside LicenChain, then submit the approved order data — never the PO document itself. LicenChain creates a licensing request and returns a single-use Buyer onboarding link that you send to the Buyer contact.
Product terms are predefined on the registered product. The Buyer reviews and accepts the applicable terms version inside LicenChain; there is no second checkout.
Request
Section titled “Request”curl --request POST "$LICENCHAIN_API_BASE_URL/api/integrations/v1/licensing-requests" \ --header "Authorization: Bearer $LICENCHAIN_API_KEY" \ --header "Content-Type: application/json" \ --header "Idempotency-Key: 5d1f8a3c-2b7e-4c49-9f0a-6e3b2d4c8a17" \ --data '{ "externalOrderId": "SELLER-PO-EXAMPLE-001", "productRef": "ai-specialist-standard", "buyer": { "organizationName": "Example Research Cooperative", "contactEmail": "licensing@example.com" }, "price": { "currency": "EUR", "netLicensedAmount": "1200.00" }, "validity": { "validFrom": "2027-01-01T00:00:00Z", "validUntil": "2028-01-01T00:00:00Z" }, "quantity": 25}'// Server-side only. Never ship the API key to browsers, apps or agent prompts.const baseUrl = process.env.LICENCHAIN_API_BASE_URL;const apiKey = process.env.LICENCHAIN_API_KEY;
const payload = { "externalOrderId": "SELLER-PO-EXAMPLE-001", "productRef": "ai-specialist-standard", "buyer": { "organizationName": "Example Research Cooperative", "contactEmail": "licensing@example.com" }, "price": { "currency": "EUR", "netLicensedAmount": "1200.00" }, "validity": { "validFrom": "2027-01-01T00:00:00Z", "validUntil": "2028-01-01T00:00:00Z" }, "quantity": 25};
// Persist one idempotency key per order and reuse it on every retry of that order.const idempotencyKey = '5d1f8a3c-2b7e-4c49-9f0a-6e3b2d4c8a17';
const response = await fetch(`${baseUrl}/api/integrations/v1/licensing-requests`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey, }, body: JSON.stringify(payload),});
if (response.status === 201 || response.status === 200) { const result: { requestId: string; onboarding: { url: string; expiresAt: string } } = await response.json(); // Store result.requestId with your order. Send result.onboarding.url only to the Buyer contact. // Do not log the onboarding URL: it is a single-use secret.} else { const problem: { code: string; traceId?: string } = await response.json(); throw new Error(`Licensing request failed: ${problem.code} (trace ${problem.traceId ?? 'n/a'})`);}# Server-side only. Never ship the API key to browsers, apps or agent prompts.import osimport requests
base_url = os.environ["LICENCHAIN_API_BASE_URL"]api_key = os.environ["LICENCHAIN_API_KEY"]
payload = { "externalOrderId": "SELLER-PO-EXAMPLE-001", "productRef": "ai-specialist-standard", "buyer": { "organizationName": "Example Research Cooperative", "contactEmail": "licensing@example.com", }, "price": { "currency": "EUR", "netLicensedAmount": "1200.00", }, "validity": { "validFrom": "2027-01-01T00:00:00Z", "validUntil": "2028-01-01T00:00:00Z", }, "quantity": 25,}
# Persist one idempotency key per order and reuse it on every retry of that order.idempotency_key = "5d1f8a3c-2b7e-4c49-9f0a-6e3b2d4c8a17"
response = requests.post( f"{base_url}/api/integrations/v1/licensing-requests", headers={ "Authorization": f"Bearer {api_key}", "Idempotency-Key": idempotency_key, }, json=payload, timeout=10,)
if response.status_code in (200, 201): result = response.json() # Store result["requestId"]. Send result["onboarding"]["url"] only to the Buyer contact. # Do not log the onboarding URL: it is a single-use secret.else: problem = response.json() raise RuntimeError(f"Licensing request failed: {problem['code']} (trace {problem.get('traceId')})")Headers
Section titled “Headers”| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer <Seller API key>. See Authentication. |
Content-Type |
Yes | application/json |
Idempotency-Key |
Yes | Unique per logical order submission (UUID v4 recommended). Reuse it for every retry of the same order. |
Body fields
Section titled “Body fields”| Field | Type | Description |
|---|---|---|
externalOrderId |
string | Your PO, contract or order reference. Unique per Seller; used for traceability and duplicate detection. 1–100 characters: letters, digits and . _ : / -. |
productRef |
string | Reference of a product registered on your Seller account. Its predefined terms apply. |
buyer.organizationName |
string | Legal or trading name of the corporate Buyer. |
buyer.contactEmail |
string | Buyer contact who will receive the onboarding link from you. Personal data — see below. |
price.currency |
string | ISO 4217 code, for example EUR. |
price.netLicensedAmount |
string | Net licensed amount excluding VAT, as a decimal string with exactly two fraction digits, for example "1200.00". Never send a floating-point number. |
validity.validFrom |
string | Start of the license period, inclusive. UTC timestamp ending in Z. |
validity.validUntil |
string | End of the license period, exclusive. UTC timestamp ending in Z, later than validFrom. |
quantity |
integer, optional | Seat or unit allowance within the single corporate license issued for this request (1–10,000), for example the number of named users the Buyer may assign. Omit it for products without an allowance. It never creates more than one license. |
The first contract version covers one product per request and a corporate Buyer. Named end users are not required: user assignment happens later in your own delivery system. Do not attach the PO document or other contract files.
One request, one corporate license
Section titled “One request, one corporate license”Each licensing request results in exactly one issued corporate license with one canonical
licenceKey, whatever the quantity:
| Concept | Meaning |
|---|---|
| License | The corporate license issued to the Buyer organization for one product, identified by one licenceKey. |
quantity (request) |
Optional seat or unit allowance recorded on that license. |
seatLimit (licence.issued) |
The same allowance as recorded at issuance, or null when the product has none. |
| Named users / seats | Assigned and enforced by your delivery system against seatLimit. LicenChain does not create a license per seat. |
price.netLicensedAmount is the net amount for the whole license. LicenChain derives no amount from
quantity. Issuing separate licenses for individual seats is not part of this contract; it would
require an explicit future business-model decision.
Orders with several lines (future evolution)
Section titled “Orders with several lines (future evolution)”externalOrderId is unique per Seller, and in this version one order maps to one product, one
licensing request and one license. If a PO has several product lines, it cannot yet be submitted as one
order. Multi-line support is intentionally left open; it is expected to be additive — for example an
optional per-line reference used together with externalOrderId for duplicate detection, or a parent
order with line items — so that existing single-product integrations remain valid. No such field exists
in this contract version; do not send one.
Response
Section titled “Response”201 Created
Section titled “201 Created”{ "schemaVersion": "1.0", "requestId": "lreq_7Q2M9K4X8P3D", "externalOrderId": "SELLER-PO-EXAMPLE-001", "productRef": "ai-specialist-standard", "termsVersion": "2026.2", "status": "awaiting_buyer", "onboarding": { "url": "https://sandbox.licenchain.example/onboarding/REDACTED-ONE-TIME-TOKEN", "expiresAt": "2026-12-08T09:00:00Z" }, "createdAt": "2026-11-08T09:00:00Z"}| Field | Description |
|---|---|
requestId |
LicenChain identifier of the licensing request. Store it with your order. |
termsVersion |
The product terms version the Buyer will be asked to accept. |
status |
State of the request: awaiting_buyer, onboarding_completed, issued, expired or cancelled. |
onboarding.url |
Single-use Buyer onboarding link. |
onboarding.expiresAt |
When the link stops working (UTC). |
200 OK — idempotent replay
Section titled “200 OK — idempotent replay”Retrying with the same Idempotency-Key and the same body returns the original result with
200 and the header Idempotent-Replayed: true. No second request is created.
Duplicates and idempotency
Section titled “Duplicates and idempotency”| Situation | Result |
|---|---|
Same Idempotency-Key, same body |
200 with the original response (Idempotent-Replayed: true). |
Same Idempotency-Key, different body |
409 idempotency_key_reused. |
New Idempotency-Key, externalOrderId already submitted |
409 duplicate_external_order with existingRequestId. |
Idempotency keys are retained for at least 24 hours. Generate the key when you first create the order submission and persist it, so a crash or timeout can be retried safely.
Errors
Section titled “Errors”Errors use RFC 9457 problem details. Example validation error:
{ "type": "https://developers.licenchain.io/api/docs/errors/#validation_failed", "title": "Request validation failed", "status": 400, "code": "validation_failed", "detail": "One or more fields are invalid.", "traceId": "trc_4H8K2M6Q9R", "errors": [ { "field": "price.netLicensedAmount", "code": "invalid_decimal", "message": "Use a decimal string with exactly two fraction digits, for example \"1200.00\"." } ]}| Status | code |
What to do |
|---|---|---|
400 |
validation_failed |
Fix the fields listed in errors[]. Do not retry unchanged. |
401 |
unauthenticated |
Check or rotate the API key. |
403 |
forbidden |
The key is not permitted to submit licensing requests. |
404 |
product_not_found |
productRef is not a registered product of your Seller account. |
409 |
duplicate_external_order / idempotency_key_reused |
See the table above. |
429 |
rate_limited |
Wait for Retry-After, then retry with the same Idempotency-Key. |
503 |
service_unavailable |
Retry with backoff and the same Idempotency-Key. |
Personal data
Section titled “Personal data”buyer.contactEmail is personal data. Send only a business contact needed for onboarding, prefer role
mailboxes where possible, and avoid copying it into logs. LicenChain never returns it in error
responses or webhook payloads.
What happens next
Section titled “What happens next”The Buyer opens the link, signs in or creates an account, and accepts the terms. You then receive
onboarding.completed and later licence.issued, which
delivers the licenceKey. Keep requestId and externalOrderId with your order: they are your
references if an issuance event is ever missed. See the Licensing lifecycle.
Reference
Section titled “Reference”- API reference —
createLicensingRequest - OpenAPI target document