# Moonbase Documentation (full text) > Source: https://moonbase.sh/docs/ # https://moonbase.sh/docs/api/ title: 'Core API Reference', description: 'On this page, you can learn how to use the Core API to integrate Moonbase into your storefront.', } # Core API Reference The Moonbase Core API has endpoints where you can manage your Moonbase account using API keys from other systems. It's the ideal set of endpoints to use when building custom integrations or migrating a lot of data to Moonbase. When calling endpoints, be sure to use the full URL including your Moonbase account ID. Examples on this page assumes an account ID of `demo`, and so the base URL for all endpoints will be `https://demo.moonbase.sh/api/`. Do you have a specific scenario you need to support or other questions?\ Reach out to us through the support channel, or at [developers@moonbase.sh](mailto:developers@moonbase.sh). --- ## Authentication All endpoints that are part of this Core API require an API key on requests. API keys can be created in your [Moonbase account settings](https://app.moonbase.sh/account-settings#api-keys), and should be included in a header called `Api-Key`. For example: ```bash curl -X 'GET' https://demo.moonbase.sh/api/products \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` --- ## Rate limits Core API requests are limited to **500 requests per minute**, counted per API key. Each API key you create has its own independent budget, so separate integrations do not compete for the same allowance. When you exceed the limit, the API responds with `429: Too Many Requests` and a `Retry-After` header telling you how many seconds to wait. Rather than hard-coding the limit into your client, honor `Retry-After` and back off until the window resets. Present on `429` responses. The number of seconds to wait before retrying. Need a higher limit for a bulk migration or a high-traffic integration? Reach out at [developers@moonbase.sh](mailto:developers@moonbase.sh). ```http {{ title: '429 response' }} HTTP/1.1 429 Too Many Requests Retry-After: 60 ``` --- ## Pagination For endpoints that query a list of resources, we have pagination in place to make sure you can iterate through large amounts of data easily. When you receive paginated responses, they always come wrapped in the same response wrapper: This array contains objects with the resources requested. Flag to indicate whether there are more results to be retrieved. The path to call to get the next page of results. The actual page size applied to this response. Requested `pageSize` values are clamped to a per-endpoint maximum (typically between 100 and 500) — read this field to confirm the effective limit rather than assuming the request value was honored. ```json {{ title: 'Pagination example' }} { "items": [ { ... } ], "hasMore": true, "next": "/api/customers?paginationToken=...", "pageSize": 100 } ``` --- ## Custom Properties Custom properties allow you to attach arbitrary metadata to your core entities: customers, licenses, products, trials, product releases, vouchers, and coupons. Each property has a typed value and two visibility flags that control where the property appears. ### Property structure Each custom property is an object with the following fields: The type of value this property holds. The value of the property: * **text**: A string value * **number**: A decimal number value * **boolean**: A true or false value * **date**: An ISO-8601 date and time value * **object**: A nested object containing more typed values Controls if this property is visible in storefront and customer-facing endpoints. Properties with `public` set to `false` will only be visible in the Core API. Controls if this property is included in license tokens. See the [licensing API documentation](/docs/licensing/api#licensing) for details on how custom properties appear in tokens. ### Validation rules * Property names must start with a letter or underscore and contain only letters, digits, underscores, or hyphens. * Nested objects support up to 5 levels of depth. * Total custom properties size must not exceed 48 KB per entity. * Object properties must contain at least one field. ```json {{ title: 'Custom property examples' }} { "company_size": { "type": "number", "value": 50, "public": false, "includeInToken": false }, "category": { "type": "text", "value": "Audio Tools", "public": true, "includeInToken": false }, "beta_access": { "type": "boolean", "value": true, "public": true, "includeInToken": true }, "renewal_date": { "type": "date", "value": "2026-06-01T00:00:00Z", "public": false, "includeInToken": true }, "metadata": { "type": "object", "value": { "region": { "type": "text", "value": "eu-west" }, "tier": { "type": "number", "value": 2 } }, "public": false, "includeInToken": false } } ``` --- ## Customers Use customer endpoints to manage your customer accounts and get insights into who are buying your products. The customer object will contain the following fields: The Moonbase ID of the customer. The full name of the customer. If the customer is a business, this holds the business name. If the customer is a business, this holds the tax ID if given. Current email address of the customer. Flag for if the current email address has ever been confirmed. Flag for if the customer has created a password yet. Flag for if the this customer account has been deleted. List of product IDs that this customer owns. List of product IDs that this customer is currently subscribed to. Billing address for the customer if stored. Contains the following properties: ISO 3166-1 alpha-2 two-letter country code. First line of the regular street address. Second line of the regular street address. Postal code of the address. Locality of the address, only required if no region is given.\ Also known as `City`. Region of the address, only required if no locality is given.\ Also known as `State`. Contains specific communication opt-ins given by the customer: Flag for if the customer wants to receive newsletters. Custom properties attached to this customer. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ```json {{ title: 'Customer example' }} { "id": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "name": "Tobias", "businessName": null, "taxId": null, "email": "tobias@moonbase.sh", "emailConfirmed": true, "hasPassword": true, "isDeleted": false, "ownedProducts": [ "example-app" ], "subscribedProducts": [], "address": null, "communicationPreferences": { "newsletterOptIn": true }, "properties": { "company_size": { "type": "number", "value": 50, "public": false, "includeInToken": false } } } ``` --- ## Get customers {{ tag: 'GET', label: '/api/customers', authenticated: true }} Use this endpoint to fetch all customers page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. ISO-8601 timestamp; only return customers created before this point in time. ISO-8601 timestamp; only return customers created after this point in time. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/customers Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/customers \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "id": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "name": "Tobias", ... } ], "hasMore": false, "next": null } ``` --- ## Get customer by ID or email {{ tag: 'GET', label: '/api/customers/{id|email}', authenticated: true }} Use this endpoint to look up a customer by their ID or email address. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/customers/tobias@moonbase.sh Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/customers/tobias@moonbase.sh \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "name": "Tobias", ... } ``` --- ## Import customer {{ tag: 'POST', label: '/api/customers/import', authenticated: true }} Importing a customer creates them a user account. It will not send any emails to the customer, and they will be able to reset their password using the normal reset password flow on your website or the hosted customer portal. You can also supply their password if you already have it, or can generate a secure one. In case a customer account with the given email already exists, you will receive a `409: Conflict` response, where you can access the customer ID using the `instance` property of the error object. ### Required body properties Full name of the user. Email address of the user. Will be used as username. Can be changed by the user. ### Optional body properties The initial password for the user. Must contain lower case characters, uppercase characters, numbers and a symbol. ID of this customer in your existing system, useful for cross-referencing with other systems. ISO-8601 timestamp to backfill the customer's creation date when migrating from another system. A billing address for the user. Will be used when purchasing new products.\ Contains the following properties: ISO 3166-1 alpha-2 two-letter country code. First line of the regular street address. Second line of the regular street address. Postal code of the address. Locality of the address, only required if no region is given.\ Also known as `City`. Region of the address, only required if no locality is given.\ Also known as `State`. The existing communication preferences the customer has given, if any.\ Contains the following properties: Customer has agreed to receive newsletters or other marketing emails. Customer has agreed to receive product updates and similar emails. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/customers/import Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "name": "Example User", "email": "user@example.com", "password": "Password1234!", "address": { "countryCode": "NO", "streetAddress1": "Slottsplassen 1", "streetAddress2": null, "postCode": "0010", "region": "Oslo", "locality": null }, "communicationPreferences": { "newsletterOptIn": true } } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/customers/import \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "name": "Example User", "email": "user@example.com", "password": "Password1234!", "address": { "countryCode": "NO", "streetAddress1": "Slottsplassen 1", "streetAddress2": null, "postCode": "0010", "region": "Oslo", "locality": null } }' ``` ```json {{ title: 'Response' }} { "id": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "name": "Example User", ... } ``` --- ## Update customer {{ tag: 'PATCH', label: '/api/customers/{id}', authenticated: true }} This endpoint can be used to partially update customer accounts, with every property being optional. ### Optional body properties Full name of the user. Email address of the user. Will be used as username. Can be changed by the user. Changing this will _not_ send an email to the customer, and they will immediately be able to log in using the new address. Phone number of the customer. If you have collected updated communication opt-ins, you can update those using this object: Flag for if this customer has opted in to receive newsletters. ```http {{ title: 'HTTP' }} PATCH https://demo.moonbase.sh/api/customers/49b8da10-2d72-4bdf-bf9c-47e56c184bfa Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "name": "Example User", "communicationPreferences": { "newsletterOptIn": true } } ``` ```bash {{ title: 'cURL' }} curl -X 'PATCH' https://demo.moonbase.sh/api/customers/49b8da10-2d72-4bdf-bf9c-47e56c184bfa \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "name": "Example User", "communicationPreferences": { "newsletterOptIn": true } }' ``` ```json {{ title: 'Response' }} { "id": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "name": "Example User", ... } ``` --- ## Delete customer {{ tag: 'DELETE', label: '/api/customers/{id}', authenticated: true }} Use this to delete customers from your Moonbase account. They will lose access to all products, and their user will be stripped of all personal information. Deleting customers will not affect historical sales, as we are required to store billing details for financial compliancy. ```http {{ title: 'HTTP' }} DELETE https://demo.moonbase.sh/api/customers/49b8da10-2d72-4bdf-bf9c-47e56c184bfa Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'DELETE' https://demo.moonbase.sh/api/customers/49b8da10-2d72-4bdf-bf9c-47e56c184bfa \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "name": "Example User", ... } ``` --- ## Set custom properties {{ tag: 'PUT', label: '/api/customers/{id}/custom-properties', authenticated: true }} Use this endpoint to set custom properties on a customer. This replaces all existing custom properties on the customer. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ### Request body The body should be a JSON object where each key is the property name, and the value is a custom property object. Each property must contain the following fields: The type of value this property holds. The value of the property, matching the declared type. Flag for if this property should be visible in storefront and customer-facing endpoints. Flag for if this property should be included in license tokens. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/customers/49b8da10-2d72-4bdf-bf9c-47e56c184bfa/custom-properties Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "company_size": { "type": "number", "value": 50, "public": false, "includeInToken": false } } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/customers/49b8da10-2d72-4bdf-bf9c-47e56c184bfa/custom-properties \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "company_size": { "type": "number", "value": 50, "public": false, "includeInToken": false } }' ``` ```json {{ title: 'Response' }} { "id": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "name": "Tobias", ... } ``` --- ## Orders An order represents a customer purchase in Moonbase. Every checkout, whether paid or free, creates an order that moves through a short lifecycle: it starts `Open`, becomes `Paid` once payment is captured, and then `Completed` once Moonbase has fulfilled it by issuing licenses and sending the receipt. Use these endpoints to read orders from other systems, for example to reconcile revenue or sync purchases into your own tooling. The order object contains the following: Unique identifier of the order. The current status of the order: * **Open**: The order has been created but not yet paid. * **PaymentProcessing**: Payment is being processed. * **Paid**: Payment has been captured, fulfillment is pending. * **Completed**: The order has been paid and fulfilled. * **Failed**: The payment failed and the order was not completed. The currency used for this purchase. The timestamp for when the order was completed. Omitted while the order is not completed. The timestamp for when the order was fully refunded. Omitted while the order is not fully refunded. Whether the entire order has been refunded. Remains `false` for a partial refund, even though `refundHistory` lists the refunded amounts. Whether the order has been partially but not fully refunded. `true` once at least one refund has settled while the order still has unrefunded units. The total amount refunded across all settled refunds, with `currency` and `amount` properties. Omitted while nothing has been refunded. The settled refunds applied to this order, present once at least one refund has settled. Each entry describes a single refund: Unique identifier of the refund. The timestamp for when this refund was made. The amount refunded to the customer in this refund, containing: * `currency`: The currency of the refunded amount. * `amount`: The refunded amount. The order line items covered by this refund, each containing: Identifier of the refunded order line item. Matches the `lineItemId` of an entry in the order's `items` array. The number of units refunded for this line item in this refund. The amount refunded to the customer for this line item, with the same `currency` and `amount` properties as above. Flag indicating if this purchase has been disputed. Calculated total for all items part of the order. Each value is an object with `currency` and `amount` properties: * `original`: The original amount before discounts. * `discount`: The total amount of discounts applied. * `subtotal`: The total amount after discounts have been removed. * `taxes`: Total taxes to pay, may be inclusive or exclusive of the `subtotal` depending on configuration, currency and region. * `due`: How much is being paid in total by the customer. Payout details for this order, only present if an amount is to be paid out. Note that this may get updated as affiliate revenue splits are calculated later in the pipeline. * `subtotal`: The original amount paid by the customer. * `taxes`: The total amount of taxes collected and remitted. * `platformFees`: The total amount of fees going to Moonbase. * `due`: How much is being paid out to you. Billing details about the customer who made the purchase: Full name of the customer. In case the purchase was a business purchase, this field will have the name. In case the purchase was a business purchase, this field will have the tax id if given. Email address of the customer. Phone number of the customer. Note that collecting phone numbers is an optional feature in Moonbase and not activated by default. Billing address for the customer, not present if the purchase was a lead generator purchase. Contains the following properties: ISO 3166-1 alpha-2 two-letter country code. First line of the regular street address. Second line of the regular street address. Postal code of the address. Locality of the address, only required if no region is given.\ Also known as `City`. Region of the address, only required if no locality is given.\ Also known as `State`. The coupons applied to this order, if any. Each entry contains: Unique identifier of the coupon. The coupon code that was redeemed. Display name of the coupon. Description of the coupon. Whether this coupon can be combined with other discounts. The discount granted by the coupon, discriminated using the `type` property: * `PercentageOffDiscount`: has a `percentage` property with a value between 0 and 1. * `FlatAmountOffDiscount`: has a `total` property mapping currency codes to amounts. Map of product IDs to the pricing variation IDs the coupon applies to. Empty when the coupon applies to the whole order. Map of bundle IDs to the pricing variation IDs the coupon applies to. Empty when the coupon applies to the whole order. Collection of items part of the order. Note that this can be either products or bundles, and they are discriminated using the `type` property. Some properties are specific to each type, others are shared: ### Product line item Item type discriminator. The unique ID of the product. ### Bundle line item Item type discriminator. The unique ID of the bundle. ### Shared Stable identifier of this order line item. Use it to correlate entries in `refundHistory` back to the line they refunded. The ID of the selected pricing variation. The quantity of this item. The original per-unit price, as a map of currency codes to amounts. The calculated total for this line item, with `original`, `discount`, `subtotal` and `due` amounts. When you need to know where the discount came from, the line total also splits it per source, each amount being what one unit of the line was discounted by: `productDiscount` for the discount on the product itself, `offerDiscount` for an offer picked for this item, `couponDiscount` for a redeemed code, and `cartOfferDiscount` for this line's share of a cart offer applied to the order as a whole. All four are omitted on older orders that were priced before the split existed. Details about the selected pricing variation, including its `id`, `name`, `entitlement`, `recurrence` and `price`. Details about how the line item was fulfilled, discriminated using the `type` property. For a `License` fulfillment this includes the issued `licenseIds`. The amount refunded for this line item so far, with `currency` and `amount` properties. Omitted while nothing has been refunded for the line. Whether every unit of this line item has been refunded. When the order was created, containing an `at` timestamp. When the order was last updated, containing an `at` timestamp. ```json {{ title: 'Order example' }} { "id": "dc0b53f9-4e43-4179-8554-00f2a8228a25", "status": "Completed", "currency": "EUR", "completedAt": "2024-11-11T11:11:11.0000000Z", "isFullyRefunded": false, "isPartiallyRefunded": false, "isDisputed": false, "total": { "original": { "currency": "EUR", "amount": 10 }, "discount": { "currency": "EUR", "amount": 5 }, "subtotal": { "currency": "EUR", "amount": 5 }, "taxes": { "currency": "EUR", "amount": 0 }, "due": { "currency": "EUR", "amount": 5 } }, "payout": { "subtotal": { "currency": "EUR", "amount": 5 }, "taxes": { "currency": "EUR", "amount": 0 }, "platformFees": { "currency": "EUR", "amount": 0.5 }, "due": { "currency": "EUR", "amount": 4.5 } }, "customer": { "name": "Example User", "businessName": null, "taxId": null, "email": "user@example.com", "phone": null, "address": { "countryCode": "NO", "streetAddress1": "Utsikten 6", "streetAddress2": null, "locality": "Skien", "region": null, "postCode": "3718" } }, "couponsApplied": [ { "id": "43396b94-53b4-4901-8e9e-ab1d6ee7d3b5", "code": "MY-UNIQUE-CODE", "name": "Half off coupon", "description": "Thanks for being our customer!", "combinable": false, "discount": { "type": "PercentageOffDiscount", "percentage": 0.5 }, "applicableProductVariations": {}, "applicableBundleVariations": {} } ], "items": [ { "type": "Product", "lineItemId": "7c1f0b2a-9d3e-4a5b-8c6d-0e1f2a3b4c5d", "productId": "example-app", "variationId": "v802c5", "quantity": 1, "price": { "EUR": 10 }, "total": { "original": { "currency": "EUR", "amount": 10 }, "discount": { "currency": "EUR", "amount": 5 }, "subtotal": { "currency": "EUR", "amount": 5 }, "due": { "currency": "EUR", "amount": 5 } }, "variation": { "id": "v802c5", "name": "Default", "entitlement": { "type": "PerpetualLicense" }, "recurrence": { "type": "OneOff" }, "price": { "EUR": 10 } }, "fulfillment": { "type": "License", "licenseIds": [ "d564a47a-d7f3-48ea-8698-57ad7512b11d" ] }, "isLineFullyRefunded": false } ], "created": { "at": "2024-11-11T11:11:10.0000000Z" }, "lastUpdated": { "at": "2024-11-11T11:11:11.0000000Z" } } ``` --- ## Get orders {{ tag: 'GET', label: '/api/orders', authenticated: true }} Use this endpoint to fetch all orders page by page, newest first. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. Only return orders with the given status. ISO-8601 timestamp; only return orders created before this point in time. ISO-8601 timestamp; only return orders created after this point in time. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/orders Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/orders \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "id": "dc0b53f9-4e43-4179-8554-00f2a8228a25", "status": "Completed", "currency": "EUR", ... } ], "hasMore": false, "next": null } ``` --- ## Get order by ID {{ tag: 'GET', label: '/api/orders/{id}', authenticated: true }} Use this endpoint to look up a single order by its ID. If no order exists with the given ID, the API responds with `404: Not Found`. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/orders/dc0b53f9-4e43-4179-8554-00f2a8228a25 Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/orders/dc0b53f9-4e43-4179-8554-00f2a8228a25 \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "dc0b53f9-4e43-4179-8554-00f2a8228a25", "status": "Completed", "currency": "EUR", ... } ``` --- ## Licenses License endpoints let you manage the full life cycle of licenses, including individual activations. If you need to connect your own licensing system with Moonbase licenses, these endpoints should suffice. The license object contains the following: The Moonbase ID of the license. The Moonbase ID of the customer that owns this license. The Moonbase ID of the product that this license is for. Current status of the license: * **Active**: The license can be used by the customer * **Revoked**: The license has been revoked by the merchant * **Expired**: The subscription for the license has expired Number of devices currently activated on this license. Max number of devices allowed to activate this license. Flag for if offline activations are allowed for this license. Custom properties attached to this license. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ```json {{ title: 'License example' }} { "id": "32016591-73c5-4956-938c-e34366599bc7", "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app", "status": "Active", "activeNumberOfActivations": 0, "maxNumberOfActivations": 1, "offlineActivationsAllowed": false } ``` --- ## Get licenses {{ tag: 'GET', label: '/api/licenses', authenticated: true }} Use this endpoint to fetch all licenses page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. Optionally filter licenses belonging to this product ID. Optionally filter licenses created before this ISO-8601 date and time. Optionally filter licenses created after this ISO-8601 date and time. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/licenses Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/licenses \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "id": "32016591-73c5-4956-938c-e34366599bc7", "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app", "status": "Active", ... } ], "hasMore": false, "next": null } ``` --- ## Get license by ID {{ tag: 'GET', label: '/api/licenses/{id}', authenticated: true }} Use this endpoint to look up a license by its ID. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/licenses/32016591-73c5-4956-938c-e34366599bc7 Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/licenses/32016591-73c5-4956-938c-e34366599bc7 \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "32016591-73c5-4956-938c-e34366599bc7", "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app", "status": "Active", ... } ``` --- ## Provision licenses {{ tag: 'POST', label: '/api/licenses/provision', authenticated: true }} This endpoint can be used to create multiple licenses for a given customer for several products. In case you want to create the user in the same process, you may instead opt to provide customer details instead of an owner ID. The response will contain a list of licenses that have been provisioned. ### Request body properties Moonbase ID of the customer that should receive the license(s). Skip this if submitting customer details instead. Provide this if you don't have an existing customer to grant the licenses to. This object must contain the following: Full name of the customer. Email address of the customer. If a customer already exists with this email, we attach the licenses to that user instead. A list of license requests that should be fulfilled, each object contain the following properties: Moonbase ID of the product you want to issue a license for. Number of licenses to issue, defaults to 1. Use this to only provision licenses up to a certain number for the given customer. ISO-8601 timestamp; if set, the provisioned licenses will expire at this point in time. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/licenses/provision Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "requests": [ { "productId": "example-app" } ] } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/licenses/provision \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "requests": [ { "productId": "example-app" } ] }' ``` ```json {{ title: 'Response' }} [ { "id": "32016591-73c5-4956-938c-e34366599bc7", "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app", "status": "Active", ... } ] ``` --- ## Import license {{ tag: 'POST', label: '/api/licenses/import', authenticated: true }} Importing a license is almost the same as provisioning it through the Moonbase app or API, with the exception of no communication to the customer being done. The license will be available on their account immediately, and can be used to import a large amount of licenses. This endpoint also allows you to add existing activations for a given license or external key codes previously generated, making migrations easier in some cases. ### Request body properties Moonbase ID of the customer that should receive this license. Either `ownerId` or `ownerEmail` must be provided, but not both. Email address of an existing customer that should receive this license. Either `ownerId` or `ownerEmail` must be provided, but not both. The customer must already exist in your Moonbase account — unknown emails are rejected. Moonbase ID of the product that this license is for. Max number of devices allowed to activate this license. If omitted, Moonbase will use the currently configured value from the product. Flag for if offline activations are allowed for this license. If omitted, Moonbase will use the currently configured value from the product. If you know devices that have already activated this license, you can also import them. It's important that the device signature will be the same so that license activation finds the correct seat to activate. Each activation contains the following properties: User-friendly name of the device. Fingerprint of the device generated by the licensing SDK. Flag for if this activation has been activated as an online or offline device. ISO-8601 date and time for when the device was last validated. If you have an external license key code that you want to associate with this license, you can provide it here. Will be visible to the customer in their customer portal. Cannot be combined with `file`. An existing license file to attach to this license, for customers that already have a generated license file you want to migrate. Cannot be combined with `keyCode`. Contains the following properties: Name of the file as it should appear to the customer. Base64-encoded contents of the file. MIME type of the file, for example `application/octet-stream`. This is an internal property, stored on licenses to let you correlate imported licenses with your existing systems. Sets the expiry date of this license to this ISO-8601 date and time. Sets the created date of this license to this ISO-8601 date and time. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/licenses/import Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app" } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/licenses/import \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app" }' ``` ```json {{ title: 'Response' }} { "id": "32016591-73c5-4956-938c-e34366599bc7", "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app", "status": "Active", ... } ``` --- ## Revoke license {{ tag: 'POST', label: '/api/licenses/{licenseId}/revoke', authenticated: true }} Revoking a license makes it inaccessible for the owner, and causes any further client-side validations to fail. This is different from revoking license *activations*, which only disables single devices and doesn't prevent the user from re-activating their license. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/licenses/32016591-73c5-4956-938c-e34366599bc7/revoke Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/licenses/32016591-73c5-4956-938c-e34366599bc7/revoke \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "32016591-73c5-4956-938c-e34366599bc7", "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app", "status": "Active", ... } ``` --- ## Set custom properties {{ tag: 'PUT', label: '/api/licenses/{id}/custom-properties', authenticated: true }} Use this endpoint to set custom properties on a license. This replaces all existing custom properties on the license. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ### Request body The body should be a JSON object where each key is the property name, and the value is a custom property object. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/licenses/32016591-73c5-4956-938c-e34366599bc7/custom-properties Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "seat_name": { "type": "text", "value": "Studio A", "public": true, "includeInToken": true } } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/licenses/32016591-73c5-4956-938c-e34366599bc7/custom-properties \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "seat_name": { "type": "text", "value": "Studio A", "public": true, "includeInToken": true } }' ``` ```json {{ title: 'Response' }} { "id": "32016591-73c5-4956-938c-e34366599bc7", "ownerId": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "productId": "example-app", "status": "Active", ... } ``` --- ## Trials Trial endpoints let you query current trials in Moonbase, and import existing trials you may have. The trial object contains the following: The Moonbase ID of the trial. The Moonbase ID of the product that this license is for. Friendly name of the device that the trial is activated on. Hardware signature of the device that the trial is activated on. ISO-8601 date and time of the last validation. ISO-8601 date and time of the expiry of this trial. Current status of the trial, if it's active or expired. Custom properties attached to this trial. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ```json {{ title: 'Trial example' }} { "id": "5f113851778f34b175101c65657e7ea8", "productId": "example-product", "deviceName": "Example Device", "deviceSignature": "89419872hc72nv150989", "lastValidatedAt": "2025-11-24T17:26:37.0417505Z", "expiresAt": "2025-12-08T17:26:35.047223Z", "status": "Expired" } ``` --- ## Get trials {{ tag: 'GET', label: '/api/trials', authenticated: true }} Use this endpoint to fetch all trials page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. Optionally filter trials belonging to this product ID. Optionally filter trials created before this ISO-8601 date and time. Optionally filter trials created after this ISO-8601 date and time. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/trials Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/trials \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "id": "5f113851778f34b175101c65657e7ea8", "productId": "example-app", "status": "Active", ... } ], "hasMore": false, "next": null } ``` --- ## Get trial by ID {{ tag: 'GET', label: '/api/trials/{id}', authenticated: true }} Use this endpoint to look up a trial by its ID. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/trials/5f113851778f34b175101c65657e7ea8 Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/trials/5f113851778f34b175101c65657e7ea8 \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "5f113851778f34b175101c65657e7ea8", "productId": "example-app", "status": "Active", ... } ``` --- ## Import trial {{ tag: 'POST', label: '/api/trials/import', authenticated: true }} Importing a trial lets you import existing trials you may have, to prevent your users from re-starting trials from other systems. ### Required body properties Moonbase ID of the product that this trial is for. User-friendly name of the device that the trial is activated on. Fingerprint of the device generated by the licensing SDK of choice. ISO-8601 date and time of the expiry of this trial. ### Optional body properties Moonbase ID of the customer that should be attributed to this trial. ISO-8601 date and time for when the device was last validated. Sets the created date of this trial to this ISO-8601 date and time. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/trials/import Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "productId": "example-app", "deviceName": "Example Device", "deviceSignature": "89419872hc72nv150989", "expiresAt": "2025-12-08T17:26:35.007223Z" } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/trials/import \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "productId": "example-app", "deviceName": "Example Device", "deviceSignature": "89419872hc72nv150989", "expiresAt": "2025-12-08T17:26:35.007223Z" }' ``` ```json {{ title: 'Response' }} { "id": "5f113851778f34b175101c65657e7ea8", "productId": "example-app", "status": "Active", ... } ``` --- ## Set custom properties {{ tag: 'PUT', label: '/api/trials/{id}/custom-properties', authenticated: true }} Use this endpoint to set custom properties on a trial. This replaces all existing custom properties on the trial. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ### Request body The body should be a JSON object where each key is the property name, and the value is a custom property object. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/trials/5f113851778f34b175101c65657e7ea8/custom-properties Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "source": { "type": "text", "value": "website_banner", "public": false, "includeInToken": false } } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/trials/5f113851778f34b175101c65657e7ea8/custom-properties \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "source": { "type": "text", "value": "website_banner", "public": false, "includeInToken": false } }' ``` ```json {{ title: 'Response' }} { "id": "5f113851778f34b175101c65657e7ea8", "productId": "example-app", "status": "Active", ... } ``` --- ## Products Product endpoints let you manage the products in your Moonbase account. The product object contains the following: The Moonbase ID of the product. The name of the product. The tag line of the product. The description of the product. Current status of the product: * **Active**: The product can be issued licenses for * **Inactive**: The product cannot be used by any customer Flag for if this product can be purchased. Configured website URL for this product if any. Link to the icon added to this product if any. Semantic version of the current release of this product if any. Internal notes for this product. Not visible to customers. The ID of the default pricing variation. Controls who can access releases for this product. Controls when this product appears in a customer's account area: * **WhenOwned** (default): visible only to customers who own a license for the product. * **Never**: never surfaced to customers — useful for internal SKUs, bundle children, or products that are sold but should not show up in the inventory UI. * **Always**: always visible, even to customers who don't own it — useful for upsell or cross-sell catalogs. Licensing configuration for this product. Maximum number of concurrent activations allowed per license. Whether offline activation is allowed. Whether to automatically provision a license on activation. Trial configuration for this product. Whether trials are enabled for this product. Whether a customer account is required to start a trial. Duration of the trial period in days. Array of pricing variations for this product. Each variation defines a way the product can be purchased. Unique identifier for this variation. Display name for this variation. The type of entitlement granted. Either `PerpetualLicense` or `SubscriptionLicense` with a grace period. The billing recurrence. Either `OneOff` for a one-time purchase or `Recurring` with a cycle length. Object mapping currency codes to amounts. Array of tiered pricing levels with `minQuantity` and `price`. Override the product-level licensing configuration for this specific variation. Array of pricing discounts for this product. Display name for the discount. Description of the discount. The discount type. Either `PercentageOffDiscount` or `FlatAmountOffDiscount`. Controls which customers can see and use this discount. Array of variation IDs this discount applies to. Time range for when this discount is valid. Number of recurring payments the discount applies to before expiring. Present when the discount comes from a promotion running across your catalogue. The promotion owns the discount, so it cannot be changed or removed through the product endpoints. Custom properties attached to this product. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ```json {{ title: 'Product example' }} { "id": "example-product", "name": "AudioPanel", "tagline": "This product is used for demo purposes", "description": "Used for demo purposes", "notes": null, "status": "Active", "purchasable": true, "website": null, "iconUrl": "https://assets.moonbase.sh/demo/products/example-product/icon/...", "currentReleaseVersion": "1.0.0", "defaultVariationId": "standard", "releaseAccessControlLevel": "AllowOwners", "customerVisibility": "WhenOwned", "licensingConfiguration": { "numberOfActivationsPerLicense": 3, "offlineActivationsEnabled": false, "autoProvisionOnActivation": false }, "trialsConfiguration": { "enabled": false, "requireAccount": false, "numberOfDays": 0 }, "pricingConfiguration": [ { "id": "standard", "name": "Standard", "entitlement": { "type": "PerpetualLicense" }, "recurrence": { "type": "OneOff" }, "price": { "USD": 49.99 } } ], "discounts": [], "properties": { "category": { "type": "text", "value": "Audio Tools", "public": true, "includeInToken": false } } } ``` --- ## Get products {{ tag: 'GET', label: '/api/products', authenticated: true }} Use this endpoint to fetch all products page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. When `true`, only active products are returned. Defaults to `false`. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/products Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/products \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "id": "exmaple-product", "name": "Example Product", ... } ], "hasMore": false, "next": null } ``` --- ## Get product by ID {{ tag: 'GET', label: '/api/products/{id}', authenticated: true }} Use this endpoint to look up a product by their ID. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/products/example-product Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/products/example-product \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "example-product", "name": "Example Product", ... } ``` --- ## Upsert product {{ tag: 'PUT', label: '/api/products/{id}', authenticated: true }} Use this endpoint to create a new product or update an existing one. The product ID is specified in the URL path. If a product with the given ID already exists, it will be updated; otherwise, a new product will be created. Returns `201 Created` when a new product is created, or `200 OK` when an existing product is updated. ### Required body parameters The name of the product. The tag line of the product. The description of the product. ### Optional body parameters Parent product ID, used to create sub-products. Sub-products can also be addressed directly by using a dot in the product ID (for example, `parent-product.sub-product`); when the ID already contains a dot, `parentId` should be omitted. Nested sub-products (a sub-product of a sub-product) are not allowed. Website URL for this product. Internal notes for this product. Not visible to customers. Whether this product can be purchased. Defaults to `false`. The ID of the default pricing variation. Controls who can access releases for this product. Controls when this product is shown in the customer-facing account area. Defaults to `WhenOwned`. See the product schema above for what each value means. Custom properties to attach to this product. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. Licensing configuration for this product. Maximum number of concurrent activations allowed per license. Whether offline activation is allowed. Whether to automatically provision a license on activation. Trial configuration for this product. Whether trials are enabled for this product. Whether a customer account is required to start a trial. Duration of the trial period in days. Array of pricing variations for this product. Each variation defines a way the product can be purchased. Unique identifier for this variation. Display name for this variation. The type of entitlement granted. Use `{ "type": "PerpetualLicense" }` for a perpetual license, or `{ "type": "SubscriptionLicense", "gracePeriod": "..." }` for a subscription with a grace period. The billing recurrence. Use `{ "type": "OneOff" }` for a one-time purchase, or `{ "type": "Recurring", "cycleLength": "Monthly" }` for recurring billing. Supported cycle lengths are `Monthly` and `Yearly`. Optionally include `renewalPrice` to set a different price for renewals. Object mapping currency codes to amounts, e.g. `{ "USD": 29.99, "EUR": 24.99 }`. Array of tiered pricing levels. Each tier has a `minQuantity` (number) and `price` (object mapping currencies to amounts). Override the product-level licensing configuration for this specific variation. Same shape as `licensingConfiguration`. Array of pricing discounts for this product. Replaces the discounts you manage yourself, so send the full list every time. Discounts that a promotion put on the product are left out of this: any entry carrying a `promotionId` is ignored here, and the product re-attaches the ones its promotions own after the upsert. That makes it safe to read a product, change something, and send the whole thing back without stripping a running sale or duplicating it. Display name for the discount. Description of the discount. The discount type. Use `{ "type": "PercentageOffDiscount", "percentage": 0.2 }` for a percentage discount (value between 0 and 1), or `{ "type": "FlatAmountOffDiscount", "total": { "USD": 5.00 } }` for a flat amount off. Controls which customers can see and use this discount. Array of variation IDs this discount applies to. If omitted, applies to all variations. Time range for when this discount is valid. Contains optional `from` and `to` datetime fields. Number of recurring payments the discount applies to before expiring. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/products/example-product Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "name": "AudioPanel", "tagline": "Professional audio processing plugin", "description": "A versatile audio plugin for mixing and mastering", "purchasable": true, "website": "https://example.org", "licensingConfiguration": { "numberOfActivationsPerLicense": 3, "offlineActivationsEnabled": true, "autoProvisionOnActivation": false }, "trialsConfiguration": { "enabled": true, "requireAccount": false, "numberOfDays": 14 }, "pricingConfiguration": [ { "id": "standard", "name": "Standard", "entitlement": { "type": "PerpetualLicense" }, "recurrence": { "type": "OneOff" }, "price": { "USD": 49.99 } } ], "defaultVariationId": "standard" } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/products/example-product \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "name": "AudioPanel", "tagline": "Professional audio processing plugin", "description": "A versatile audio plugin for mixing and mastering", "purchasable": true, "website": "https://example.org", "licensingConfiguration": { "numberOfActivationsPerLicense": 3, "offlineActivationsEnabled": true, "autoProvisionOnActivation": false }, "trialsConfiguration": { "enabled": true, "requireAccount": false, "numberOfDays": 14 }, "pricingConfiguration": [ { "id": "standard", "name": "Standard", "entitlement": { "type": "PerpetualLicense" }, "recurrence": { "type": "OneOff" }, "price": { "USD": 49.99 } } ], "defaultVariationId": "standard" }' ``` ```json {{ title: 'Response' }} { "id": "example-product", "name": "AudioPanel", "tagline": "Professional audio processing plugin", "description": "A versatile audio plugin for mixing and mastering", "notes": null, "status": "Active", "purchasable": true, "website": "https://example.org", "iconUrl": null, "currentReleaseVersion": null, "defaultVariationId": "standard", "releaseAccessControlLevel": "AllowAnonymous", "licensingConfiguration": { "numberOfActivationsPerLicense": 3, "offlineActivationsEnabled": true, "autoProvisionOnActivation": false }, "trialsConfiguration": { "enabled": true, "requireAccount": false, "numberOfDays": 14 }, "pricingConfiguration": [ { "id": "standard", "name": "Standard", "entitlement": { "type": "PerpetualLicense" }, "recurrence": { "type": "OneOff" }, "price": { "USD": 49.99 } } ], "discounts": [] } ``` --- ## Set custom properties {{ tag: 'PUT', label: '/api/products/{id}/custom-properties', authenticated: true }} Use this endpoint to set custom properties on a product. This replaces all existing custom properties on the product. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ### Request body The body should be a JSON object where each key is the property name, and the value is a custom property object. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/products/example-product/custom-properties Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "category": { "type": "text", "value": "Audio Tools", "public": true, "includeInToken": false } } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/products/example-product/custom-properties \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "category": { "type": "text", "value": "Audio Tools", "public": true, "includeInToken": false } }' ``` ```json {{ title: 'Response' }} { "id": "example-product", "name": "AudioPanel", ... } ``` --- ## Product Releases Product release endpoints let you manage the releases for the products in your Moonbase account. The product release object contains the following: The semantic version of this release. Changelog for this release if any. ISO-8601 date and time for when this release was published if any. URL to download this release, based on the storefront mode of your Moonbase account. URL to download this release using the Moonbase hosted customer portal. List of downloadable files for this release. Each download contains the following properties: File name for this download Unique key for this download. The intended platform for this download. CPU architecture for this download. Omitted when not set. Lets you ship per-architecture binaries on the same release without encoding the arch into the filename. The size of this file in bytes. URL to download this specific file, based on the storefront mode of your Moonbase account. URL to download this specific file using the Moonbase hosted customer portal. Direct URL to download this file, will depend on authentication based on your product security settings. Custom properties attached to this release. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ```json {{ title: 'Product release example' }} { "version": "1.0.0", "description": null, "publishedAt": "2023-10-01T07:28:37.147407Z", "downloadUrl": "https://example.org/?mb_intent=download_product...", "hostedDownloadUrl": "https://demo.moonbase.sh/download?product_id=example-product&version=1.0.0", "downloads": [ { "name": "installer.exe", "key": "demo/1d7c685e-3a4e-4051-8526-6df436b6fd27", "platform": "Windows", "arch": "X64", "size": 2503, "downloadUrl": "https://example.org/?mb_intent=download_product...", "hostedDownloadUrl": "https://demo.moonbase.sh/download?product_id=example-product&version=1.0.0&key=1d7c685e-3a4e-4051-8526-6df436b6fd27", "directUrl": "https://demo.moonbase.sh/api/customer/inventory/products/example-product/download/1.0.0/1d7c685e-3a4e-4051-8526-6df436b6fd27", }, { "name": "installer.exe", "key": "demo/9f0a2c7e-b1d4-4f33-9a8a-5e5c8b1bd910", "platform": "Windows", "arch": "Arm64", "size": 2491, "downloadUrl": "https://example.org/?mb_intent=download_product...", "hostedDownloadUrl": "https://demo.moonbase.sh/download?product_id=example-product&version=1.0.0&key=9f0a2c7e-b1d4-4f33-9a8a-5e5c8b1bd910", "directUrl": "https://demo.moonbase.sh/api/customer/inventory/products/example-product/download/1.0.0/9f0a2c7e-b1d4-4f33-9a8a-5e5c8b1bd910", } ] } ``` --- ## Get product releases {{ tag: 'GET', label: '/api/products/{productId}/releases', authenticated: true }} Use this endpoint to fetch all releases for a product page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/products/example-product/releases Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/products/example-product/releases \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "version": "1.0.0", "description": null, "publishedAt": "2023-10-01T07:28:37.147407Z", ... } ], "hasMore": false, "next": null } ``` --- ## Create release {{ tag: 'POST', label: '/api/products/{productId}/releases/new', authenticated: true }} Creating a new release lets you expose and publish uploaded files to your customers by tying downloads to a product release. ### Required body properties Semantic version of the new release. Object with a number for each part: Major version part Minor version part Patch version part Optional description of what this release is about. List of downloads that this release should contain. For more information on how to prepare the key for these downloads, see [downloads](#Downloads). Each object in this array must contain the following: Name of this file. Unique key generated by the download prepare endpoint. The intended platform for this download. CPU architecture for this download. Omit for arch-agnostic binaries. Two downloads with the same `name` and `platform` but different `arch` will coexist on the release. ### Optional query parameters Flag that can be given to immediately publish this new release. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/products/example-product/releases/new Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "version": { "major": 1, "minor": 0, "patch": 0 }, "downloads": [ { "name": "Installer.exe", "platform": "Windows", "arch": "X64", "key": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa" }, { "name": "Installer.exe", "platform": "Windows", "arch": "Arm64", "key": "f3d24a7b-9c4f-4d61-8e2a-72b1a0e3c4d5" } ] } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/products/example-product/releases/new \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "version": { "major": 1, "minor": 0, "patch": 0 }, "downloads": [ { "name": "Installer.exe", "platform": "Windows", "arch": "X64", "key": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa" }, { "name": "Installer.exe", "platform": "Windows", "arch": "Arm64", "key": "f3d24a7b-9c4f-4d61-8e2a-72b1a0e3c4d5" } ] }' ``` ```json {{ title: 'Response' }} { "version": "1.0.0", "description": null, "publishedAt": null, ... } ``` --- ## Update release {{ tag: 'PUT', label: '/api/products/{productId}/releases/{version}', authenticated: true }} Updating a release replaces the all downloads, and updates the description if given ### Required body properties Optional description of what this release is about. List of downloads that this release should contain. For more information on how to prepare these downloads, see [downloads](#Downloads). Each object in this array must contain the following: Name of this file. Unique key generated by the download prepare endpoint. The intended platform for this download. CPU architecture for this download. Omit for arch-agnostic binaries. Two downloads with the same `name` and `platform` but different `arch` will coexist on the release. ### Optional query parameters Flag that can be given to immediately publish this updated release. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/products/example-product/releases/1.0.0 Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "description": "1.0.0 is the initial release of Example Product", "downloads": [ { "name": "Installer.exe", "platform": "Windows", "key": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa" } ] } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/products/example-product/releases/1.0.0 \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "description": "1.0.0 is the initial release of Example Product", "downloads": [ { "name": "Installer.exe", "platform": "Windows", "key": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa" } ] }' ``` ```json {{ title: 'Response' }} { "version": "1.0.0", "description": "1.0.0 is the initial release of Example Product", "publishedAt": null, ... } ``` --- ## Add download {{ tag: 'POST', label: '/api/products/{productId}/releases/{version}/add-download', authenticated: true }} Using this endpoint, you can add more downloads to a release. These are deduplicated based on the platform, architecture, and file name, and is useful if you have separate build steps per platform or architecture that each add their own download to new releases. If you submit the same `name` and `platform` with a different `arch`, both downloads are kept; submitting the same `name`, `platform`, and `arch` replaces the existing entry. Existing downloads without an `arch` continue to deduplicate by `name` and `platform` as before. ### Required body properties Name of this file. Unique key generated by the download prepare endpoint. The intended platform for this download. CPU architecture for this download. Omit for arch-agnostic binaries. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/products/example-product/releases/1.0.0/add-download Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "name": "Installer.exe", "platform": "Windows", "key": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa" } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/products/example-product/releases/1.0.0/add-download \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "name": "Installer.exe", "platform": "Windows", "key": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa" }' ``` ```json {{ title: 'Response' }} { "version": "1.0.0", "description": null, "publishedAt": "2023-10-01T07:28:37.147407Z", ... } ``` --- ## Publish release {{ tag: 'POST', label: '/api/products/{productId}/releases/{version}/publish', authenticated: true }} Publish an existing product release. This makes the release available to your customers, and also updates the current version of the product to point to this release. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/products/example-product/releases/1.0.0/publish Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/products/example-product/releases/1.0.0/publish \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "version": "1.0.0", "description": null, "publishedAt": null, ... } ``` --- ## Set custom properties {{ tag: 'PUT', label: '/api/products/{productId}/releases/{version}/custom-properties', authenticated: true }} Use this endpoint to set custom properties on a product release. This replaces all existing custom properties on the release. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ### Request body The body should be a JSON object where each key is the property name, and the value is a custom property object. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/products/example-product/releases/1.0.0/custom-properties Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "min_os_version": { "type": "text", "value": "14.0", "public": true, "includeInToken": false }, "is_prerelease": { "type": "boolean", "value": false, "public": true, "includeInToken": false } } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/products/example-product/releases/1.0.0/custom-properties \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "min_os_version": { "type": "text", "value": "14.0", "public": true, "includeInToken": false }, "is_prerelease": { "type": "boolean", "value": false, "public": true, "includeInToken": false } }' ``` ```json {{ title: 'Response' }} { "version": "1.0.0", "description": null, "publishedAt": "2023-10-01T07:28:37.147407Z", ... } ``` --- ## Downloads Download endpoints let you upload files to your Moonbase account to use in product releases. ## Prepare upload {{ tag: 'POST', label: '/api/downloads/prepare', authenticated: true }} Prepare to upload a file to Moonbase. ### Mandatory query parameters The MIME type of the content in this file. For binary files, you may use `application/octet-stream`. This parameter is important so that customers know what file they are downloading later on. ### Response body The unique key for this file. Use this when creating product releases to connect the file to the release. The URL where you may upload the file. This URL requires no authentication, and you may do a direct PUT with the file content. For example: ```bash curl --upload-file Installer.exe {URL} \ -H 'Content-Type: application/octet-stream' ``` ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/downloads/prepare?contentType=application%2foctet-stream Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/downloads/prepare?contentType=application%2foctet-stream \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "key": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "url": "https://demo.moonbase.sh/..." } ``` --- ## Vouchers Voucher endpoints let you manage vouchers, which are one-time-use codes that grant licenses to products and bundles. The voucher object contains the following: The Moonbase ID of the voucher. The name of the voucher. Description of the voucher. The number of codes associated with this voucher. The number of times codes from this voucher have been redeemed. List of products that this voucher redeems, each wrapped in a quantity/value object. List of bundles that this voucher redeems, each wrapped in a quantity/value object. Custom properties attached to this voucher. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ```json {{ title: 'Voucher example' }} { "id": "58927685-61ad-4caa-ad6f-8613d7200d4f", "name": "Demo voucher", "description": "Used for demo purposes", "numberOfCodes": 100, "numberOfRedemptions": 12, "redeemsProducts": [ { "quantity": 1, "value": { "id": "example-product", "name": "AudioPanel", ... } } ], "redeemsBundles": [], "properties": { "campaign": { "type": "text", "value": "summer_2026", "public": true, "includeInToken": false } } } ``` --- ## Create voucher {{ tag: 'POST', label: '/api/vouchers/create', authenticated: true }} Creates a new voucher with product and bundle entitlements. ### Required body properties Name of the voucher. Description of the voucher. ### Optional body properties A map of product IDs to the number of licenses to grant per redemption. A map of bundle IDs to the number of licenses to grant per redemption. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/vouchers/create Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "name": "Demo voucher", "description": "Used for demo purposes", "productEntitlements": { "example-product": 1 } } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/vouchers/create \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "name": "Demo voucher", "description": "Used for demo purposes", "productEntitlements": { "example-product": 1 } }' ``` ```json {{ title: 'Response' }} { "id": "58927685-61ad-4caa-ad6f-8613d7200d4f", "name": "Demo voucher", ... } ``` --- ## Get vouchers {{ tag: 'GET', label: '/api/vouchers', authenticated: true }} Use this endpoint to fetch all vouchers page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/vouchers Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/vouchers \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "id": "58927685-61ad-4caa-ad6f-8613d7200d4f", "name": "Demo voucher", ... } ], "hasMore": false, "next": null } ``` --- ## Get voucher by ID {{ tag: 'GET', label: '/api/vouchers/{id}', authenticated: true }} Use this endpoint to look up a voucher by its ID. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "58927685-61ad-4caa-ad6f-8613d7200d4f", "name": "Demo voucher", ... } ``` --- ## Add voucher codes {{ tag: 'PUT', label: '/api/vouchers/{id}/codes', authenticated: true }} Adds one or more codes to an existing voucher. Codes are normalized to uppercase. ### Request body The body should be a JSON array of code strings. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/codes Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json [ "VOUCHER-CODE-001", "VOUCHER-CODE-002" ] ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/codes \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '["VOUCHER-CODE-001", "VOUCHER-CODE-002"]' ``` --- ## Get voucher codes {{ tag: 'GET', label: '/api/vouchers/{id}/codes', authenticated: true }} Use this endpoint to fetch codes for a voucher page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. Filter to codes redeemed before this ISO-8601 date and time. Filter to codes redeemed after this ISO-8601 date and time. ### Response content The inner voucher code objects have the following schema: The Moonbase ID of the voucher this code belongs to. The code string. Flag for if this code has been redeemed. The Moonbase ID of the customer who redeemed the code, if redeemed. ISO-8601 date and time of when the code was redeemed, if redeemed. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/codes Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/codes \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "voucherId": "58927685-61ad-4caa-ad6f-8613d7200d4f", "code": "VOUCHER-CODE-001", "isRedeemed": true, "redeemedBy": "49b8da10-2d72-4bdf-bf9c-47e56c184bfa", "redeemedAt": "2026-03-15T10:30:00Z" } ], "hasMore": false, "next": null } ``` --- ## Delete voucher code {{ tag: 'DELETE', label: '/api/vouchers/{id}/codes/{code}', authenticated: true }} Deletes a single code from a voucher. ```http {{ title: 'HTTP' }} DELETE https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/codes/VOUCHER-CODE-001 Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'DELETE' https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/codes/VOUCHER-CODE-001 \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` --- ## Set custom properties {{ tag: 'PUT', label: '/api/vouchers/{id}/custom-properties', authenticated: true }} Use this endpoint to set custom properties on a voucher. This replaces all existing custom properties on the voucher. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ### Request body The body should be a JSON object where each key is the property name, and the value is a custom property object. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/custom-properties Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "campaign": { "type": "text", "value": "summer_2026", "public": true, "includeInToken": false } } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/vouchers/58927685-61ad-4caa-ad6f-8613d7200d4f/custom-properties \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "campaign": { "type": "text", "value": "summer_2026", "public": true, "includeInToken": false } }' ``` ```json {{ title: 'Response' }} { "id": "58927685-61ad-4caa-ad6f-8613d7200d4f", "name": "Demo voucher", ... } ``` --- ## Coupons Coupon endpoints let you manage coupons, which provide discount codes for products and bundles. The coupon object contains the following: The Moonbase ID of the coupon. The name of the coupon. Description of the coupon. The number of codes associated with this coupon. The number of times codes from this coupon have been redeemed. Flag for if this coupon can be combined with product discounts. The discount this coupon applies. Can be either a flat amount off or a percentage off discount. List of products that this coupon can be applied to. List of bundles that this coupon can be applied to. Time range during which this coupon is valid, if restricted. Flag for if this coupon has been deleted. Custom properties attached to this coupon. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ```json {{ title: 'Coupon example' }} { "id": "SUMMER2026", "name": "Summer Sale", "description": "Summer 2026 promotion", "numberOfCodes": 500, "numberOfRedemptions": 42, "combinable": false, "discount": { "type": "PercentageOffDiscount", "percentage": 0.2 }, "applicableProducts": [ ... ], "applicableBundles": [], "validity": null, "isDeleted": false, "properties": { "tracking_id": { "type": "text", "value": "promo_summer_2026", "public": false, "includeInToken": false } } } ``` --- ## Create coupon {{ tag: 'POST', label: '/api/coupons/create', authenticated: true }} Creates a new coupon with a discount, product and bundle applicability, and an optional validity period. ### Required body properties Name of the coupon. Description of the coupon. The discount this coupon applies. This is a discriminated object based on the `type` field: ### Percentage off
Discount type discriminator. The percentage to discount, normalized to between 0 and 1. ### Flat amount off
Discount type discriminator. The flat amount to discount per currency.
### Optional body properties A map of product IDs to an array of pricing variation IDs that this coupon applies to. A map of bundle IDs to an array of pricing variation IDs that this coupon applies to. Time range during which this coupon is valid. ISO-8601 date and time for when the coupon becomes valid. ISO-8601 date and time for when the coupon expires. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/coupons/create Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "name": "Summer Sale", "description": "Summer 2026 promotion", "discount": { "type": "PercentageOffDiscount", "percentage": 0.2 }, "applicableProducts": { "example-product": ["default"] } } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/coupons/create \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "name": "Summer Sale", "description": "Summer 2026 promotion", "discount": { "type": "PercentageOffDiscount", "percentage": 0.2 }, "applicableProducts": { "example-product": ["default"] } }' ``` ```json {{ title: 'Response' }} { "id": "SUMMER2026", "name": "Summer Sale", ... } ```
--- ## Get coupons {{ tag: 'GET', label: '/api/coupons', authenticated: true }} Use this endpoint to fetch all coupons page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/coupons Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/coupons \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "id": "SUMMER2026", "name": "Summer Sale", ... } ], "hasMore": false, "next": null } ``` --- ## Get coupon by ID {{ tag: 'GET', label: '/api/coupons/{id}', authenticated: true }} Use this endpoint to look up a coupon by its ID. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/coupons/SUMMER2026 Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/coupons/SUMMER2026 \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "id": "SUMMER2026", "name": "Summer Sale", ... } ``` --- ## Add coupon codes {{ tag: 'PUT', label: '/api/coupons/{id}/codes', authenticated: true }} Adds one or more codes to an existing coupon. You can optionally set a validity period for the individual codes, separate from the coupon's overall validity. ### Request body The body should be a JSON array of code strings. ### Optional query parameters ISO-8601 date and time for when these codes become valid. ISO-8601 date and time for when these codes expire. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/coupons/SUMMER2026/codes Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json [ "SUMMER-001", "SUMMER-002" ] ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/coupons/SUMMER2026/codes \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '["SUMMER-001", "SUMMER-002"]' ``` --- ## Get coupon codes {{ tag: 'GET', label: '/api/coupons/{id}/codes', authenticated: true }} Use this endpoint to fetch codes for a coupon page by page. For more details on how pagination works, see [pagination](#pagination). ### Optional query parameters Adjust the size of each page returned. ### Response content The inner coupon code objects have the following schema: The code string. The number of times this code has been redeemed. The validity period for this specific code, if set. ISO-8601 date and time for when the code becomes valid. ISO-8601 date and time for when the code expires. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/coupons/SUMMER2026/codes Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/coupons/SUMMER2026/codes \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` ```json {{ title: 'Response' }} { "items": [ { "code": "SUMMER-001", "numberOfRedemptions": 3, "validity": null } ], "hasMore": false, "next": null } ``` --- ## Delete coupon code {{ tag: 'DELETE', label: '/api/coupons/{id}/codes/{code}', authenticated: true }} Deletes a single code from a coupon. ```http {{ title: 'HTTP' }} DELETE https://demo.moonbase.sh/api/coupons/SUMMER2026/codes/SUMMER-001 Api-Key: mb_bbdac119b64649f6937e... ``` ```bash {{ title: 'cURL' }} curl -X 'DELETE' https://demo.moonbase.sh/api/coupons/SUMMER2026/codes/SUMMER-001 \ -H 'Api-Key: mb_bbdac119b64649f6937e...' ``` --- ## Set custom properties {{ tag: 'PUT', label: '/api/coupons/{id}/custom-properties', authenticated: true }} Use this endpoint to set custom properties on a coupon. This replaces all existing custom properties on the coupon. See [custom properties](#custom-properties) for more details on the shape and types of custom properties. ### Request body The body should be a JSON object where each key is the property name, and the value is a custom property object. ```http {{ title: 'HTTP' }} PUT https://demo.moonbase.sh/api/coupons/SUMMER2026/custom-properties Api-Key: mb_bbdac119b64649f6937e... Content-Type: application/json { "tracking_id": { "type": "text", "value": "promo_summer_2026", "public": false, "includeInToken": false } } ``` ```bash {{ title: 'cURL' }} curl -X 'PUT' https://demo.moonbase.sh/api/coupons/SUMMER2026/custom-properties \ -H 'Api-Key: mb_bbdac119b64649f6937e...' \ -H 'Content-Type: application/json' \ -d '{ "tracking_id": { "type": "text", "value": "promo_summer_2026", "public": false, "includeInToken": false } }' ``` ```json {{ title: 'Response' }} { "id": "SUMMER2026", "name": "Summer Sale", ... } ``` --- --- # https://moonbase.sh/docs/concepts/ title: 'Core Concepts', description: 'On this page, you can learn more about the concepts behind the features powering Moonbase.', } { title: 'Licenses', id: 'licenses' }, { title: 'Products', id: 'products' }, { title: 'Bundles', id: 'bundles' }, { title: 'Coupons', id: 'coupons' }, { title: 'Vouchers', id: 'vouchers' }, ] # Core concepts By using Moonbase as the provider for your storefront and delivery needs, it is important to understand the concepts behind the building blocks providing the functionality. ## Licenses A license is the core of the system, defining ownership of products by your customers. Trials also fall in under this category, where trials are practically speaking normal licenses but with more restrictions like expiry date, uniqueness per device, etc. ### License activations Any license will have configuration on it, inherited from the product at the time of creation, that determines how many activations are possible, and if offline activations are allowed. See [product licensing](#product-licensing) for more details on this. It's important to keep in mind that while online activated devices can at any time revoke the activation in order to move the license to different devices, offline activations does not have the same ability. That is because there would be no way for an offline activated application to know that the license activation has been revoked or moved. As a merchant on Moonbase, you can manually revoke offline license activations if necessary. ## Products Having products defined is the only way to create licenses in Moonbase, which means it's usually the first thing you set up when starting up with us. These products define what sort of licenses should be issued, what kind of trials might be allowed and how the product can be purchased. As a delivery platform, products also let you create releases of the software, to be able to deliver versioned binaries to the customers who need the software. ### Product pricing To be able to offer the same product at different rates or through different purchase models, Moonbase allows you to create pricing variations for all products. These variations will all be available through the Storefront APIs and SDKs, to enable you to fetch pricing and details of all options on demand. Variations contain configuration regarding recurrence (one-off purchase or a recurring subscription), entitlements (perpetual access or limited support updates) and pricing.
Every product will have a default variation, which is what will be used when no variation is given as the product is added to the cart. The default variation is also the one being used if you send customers to the immediate checkout flow. Pricing in Moonbase is purposefully simple at the moment, giving you max control over what currencies your customers will see. When you first open an account on Moonbase, you will usually only have one or two currencies enabled, but you can always reach out to support to enable more currencies. VAT and sales taxes are based on the norm of the given currency, which means that for currencies like USD and CAD, the price configured will be exclusive of sales tax, while for currencies like EUR, the price is inclusive sales tax. ### Product licensing {{ id: 'product-licensing' }} Whenever a product is fulfilled by Moonbase, the licenses created will be created on the basis of the product licensing configuration.
Through this configuration you can determine how many activations each license should allow simultaneously, as well as if offline activations should be allowed. While product activations using the online flow is always a floating activation, offline activations can never be moved to another device unless you as a merchant manually alter the activation. ### Product trials {{ id: 'product-trials' }} Similarly to the licensing configuration, trial configuration determine what options customers will have to try out your apps before buying.
Trials are for all intents and purposes a normal license, but with a limited expiry date attached to them. It is the responsibility of the integrating software to ensure the expiry date is still in the future when validating licenses stored locally. ### Product releases If you want to use Moonbase to also deliver the actual installers to your customers, you need to create releases of your products.
Releases let you roll out new versions of your software, making it available for all owners to download new installers or binaries. It's also possible to have un-released binaries stored, for later publishing. ## Bundles Predictably, bundles are collections of products sold together as one. Similarly to products, you can define pricing variations in the exact same way, with the added option of enabling **partial** bundle purchases. ### Partial bundle purchases A common scenario is that you might want to offer bundles fit for a very specific use case, at a discount from the individual price. Something that will quickly come up in that journey for a customer is being faced with a bundle where the customer already owns one or more of the products in the bundle, but would still like to get the general discount to upgrade to the complete package. Partial bundle purchases allow that, by adjusting the price of the bundle based on already owned products from the customer purchasing the bundle. This is one of the big benefits of having a platform that can correctly identify customers as part of the payment flow, to offer relevant discounts and benefits for added incentives to purchase. ## Coupons If you want to offer discounts by using promotional codes on your products and bundles, coupons are the way to go. Coupons will let you define either a percentage based discount, or a flat amount off discount to a set of codes that can be applied to orders in progress.
If you want the coupon to only apply to specific products or bundles, that's also possible to configure on the coupon itself. Discounts applied by coupons are made _after_ any product discounts have been applied, if the coupon is combinable with product discounts. ## Vouchers If instead of a discount, you rather want to give products away entirely, vouchers is the feature you need. Vouchers will let you define a set of products or bundles to give away, and like coupons, add a set of codes that can be redeemed to receive the entitlements of the voucher. Unlike coupon codes, voucher codes have limited uses, only redeemable by one customer, so that you can control how many licenses are issued from the voucher you create. --- # https://moonbase.sh/docs/guides/github-actions-product-releases/ title: 'Automate product releases with GitHub Actions', description: 'On this page, you can learn how to automatically release new versions of your products using GitHub Actions.', } # Automate product releases with GitHub Actions This guide will show you how to automatically release new versions of your software built in GitHub actions on Moonbase using our core API. {{ className: 'lead' }} ## Getting started To access the API for your account, start by creating an API key if you don't have one already by going to your [Moonbase account settings](https://app.moonbase.sh/account-settings). API keys allow access to a slew of endpoints documented in our [Core API reference](/docs/api), and can be used by adding an `Api-Key` header to your requests. ## Basic process ### 1. Prepare downloads A product release in Moonbase can have multiple downloadable files. For each of the files you want part of a release, you need to prepare it and get the URL to upload to: ```bash curl -X 'POST' 'https://demo.moonbase.sh/api/downloads/prepare?contentType=application%2foctet-stream' \ -H 'Api-Key: mb_a52c7...' ``` Be sure to substitute `demo` with your account ID, and the `contentType` query parameter with the content type of whatever you want to upload. For binaries, `application/octet-stream` is usually the right choice, but for other file types there will be other MIME types. The above request will respond with a key and a URL in a JSON object like this: `{ "key": "demo/eaefa...", "url": "https://..." }`. Using these, you can proceed to upload the file with a PUT request to the given URL. ### 2. Upload file Using the URL from the previous response, you can initiate an upload. The API key used previously is not necessary for this step as the URL is pre-authorized: ```bash curl --upload-file file_key.txt $URL \ -H 'Content-Type: application/octet-stream' ``` Repeat the above to steps for each file you want to be part of your release, and preserve the keys returned for each one. ### 3. Create a release Once all the files are uploaded, we can create the release in Moonbase. The request needs to contain an array of all files you want part of the release, with a name and platform indicator for each file. Platform can be one of `Windows | Mac | Linux | Universal`, and is used for interface indicators and suggestions when your customers are downloading the software. You can optionally include an `arch` field per download with values `Universal | X86 | X64 | Arm | Arm64`, which lets you ship a separate binary per architecture on the same release — useful for Apple Silicon vs. Intel builds or Windows arm64 vs. x64 builds. Downloads with the same name and platform but different `arch` coexist on the release. ```bash curl -X 'POST' 'https://demo.moonbase.sh/api/products/example-product/releases/new?publishImmediately=true' \ -H 'Api-Key: mb_a52c7...' \ -H 'Content-Type: application/json' \ -d '{ "version": { "major": 1, "minor": 0, "patch": 0 }, "downloads": [ { "name": "Installer.exe", "platform": "Windows", "key": "'$KEY'" } ] }' ``` Releases in Moonbase are versioned based on semantic versioning to allow for a number of strategies regarding pricing and distribution. The `major`, `minor` and `patch` are all components for this semantic version identifier. To learn more about semantic versioning, read more at [https://semver.org](https://semver.org). If you would rather do a automatic upload, but manual publication, set the `publishImmediately` query parameter to false, or remove it entirely. You can then publish it from the Moonbase app yourself:
## Making a GitHub Action With the above, we can build the following action to automatically release software. ```yaml name: Moonbase Continuous Deployment on: push: branches: - main jobs: moonbase-cd: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@master - name: Build application run: make build # This is dependant on how you build your binaries - name: Prepare download run: | echo "DL_JSON=$(curl -X 'POST' \ 'https://demo.moonbase.sh/api/downloads/prepare?contentType=application%2foctet-stream' \ -H 'Api-Key: ${{ secrets.MOONBASE_API_KEY }}')" \ >> $GITHUB_ENV - name: Upload binary env: URL: ${{ fromJson(env.DL_JSON).url }} run: | curl --upload-file installer.exe $URL \ -H 'Content-Type: application/octet-stream' - name: Create Moonbase release env: KEY: ${{ fromJson(env.DL_JSON).key }} run: | curl -X 'POST' 'https://demo.moonbase.sh/api/products/example-product/releases/new' \ -H 'Api-Key: ${{ secrets.MOONBASE_API_KEY }}' \ -H 'Content-Type: application/json' \ -d '{ "version": { "major": 1, "minor": 0, "patch": 0 }, "downloads": [ { "name": "installer.exe", "platform": "Windows", "key": "'$KEY'" } ] }' ``` Be sure to plug in your favourite versioning tool to automatically increment the semantic version too! ## Bonus: notifying users Once you publish a new release in Moonbase, all customers will immediately have access to download the new version. But how will they know about it? One way is to use the licensing flow that you might already be using, as in the metadata of any license is the current release version of the product that the license has been issued for. If you opt for a flow where you periodically re-validate license activations, new metadata will contain updated product version numbers. Using this metadata, you can build UIs to notify your users about new versions being available. Learn more about the metadata available for license activations in our [licensing API reference](/docs/licensing/api/#licensing). --- # https://moonbase.sh/docs/guides/marketing-revenue-tracking/ title: 'Track revenue generated by marketing campaigns', description: 'Using UTM parameters, you can track revenue generated by marketing campaigns through Moonbase purchases.', } # Track revenue generated by marketing campaigns This guide will show you how you can easily start tracking revenue generated by your marketing campaigns to understand what channels are the most effective tools for you. {{ className: 'lead' }} ## The UTM parameters Moonbase supports tracking marketing campaigns using the well-known UTM parameters, also known as the Urchin Tracking Module. These parameters form a collection of identifiers to identify several dimensions of the channel where you attract customers. You've probably seen them as query parameters before, and Moonbase will track all of them for each purchase made: | Parameter | Purpose | | -------------- | --------------------------------------------------------------- | | `utm_source` | The source site or channel of the campaign | | `utm_medium` | The type of link used, like ad or email CTAs | | `utm_campaign` | An identifier for the specific campaign | | `utm_term` | Search terms used by the customer to find the campaign | | `utm_content` | Description of what brought the customer to the site originally | In case you want to track which site lead to the conversion, Moonbase also supports an optional `utm_referrer` parameter, which can be used alongside the `utm_source` parameter. These parameters can be used both on our hosted purchase pages, as well as through the storefront APIs and SDKs. They can even be used for running targeted discounts, where discounts are exclusive for certain tracking parameters! ## Hosted purchase pages All merchants on Moonbase will have hosted purchase pages available, typically at URLs like [https://demo.moonbase.sh/buy/demo-app](https://demo.moonbase.sh/buy/demo-app). By adding the UTM parameters to these links, the eventual purchase will be tracked automatically by Moonbase, and any UTM targeted discounts will be automatically applied. Simply add the parameters as necessary to the URL: `https://demo.moonbase.sh/buy/demo-app?utm_source=moonbase.sh&utm_content=docs`. This page will also add the referrer domain automatically, to give you as much insight as possible. ## Storefront API & SDK If you are using our storefront APIs and SDKs, there's two endpoints where you should forward any UTM parameters to: 1. `GET /api/customer/storefront`: This endpoint is used for fetching product pricing, and will have UTM exclusive discounts applied only if UTM query parameters are present 1. `PATCH /api/customer/orders/{orderId}`: This endpoint is used for adding products to an ongoing purchase, and can also be used to add UTM parameters to the purchase For more information about these endpoints, check out our [storefront API reference](/docs/storefronts/api). All of the above endpoints expect UTM parameters as query parameters as demonstrated in the above examples. If you are already using our SDKs, all of this will be automatically handled as long as you link to your storefront with UTM parameters included. Check out our SDK documentation for [Vue.js](/docs/storefronts/sdks/vue) and [React.js](/docs/storefronts/sdks/react) to get started. ## Analyze in the Moonbase app Once integrated, log in to your Moonbase account to find marketing analytics on the home page. Here, you can adjust time frames and investigate statistics for all the different UTM parameters:
Through these analytics, you'll be able to see trends across channels, based on quantity and total revenue. ## Bonus: offer targeted discounts You might find yourself in the position that some of your campaigns are doing better than others, and you would like to keep the best performing ones going. Often, those campaigns have specific discounts marketed in them, which is critical to the success of the campaign. Using our discount targeting, you can craft exclusive product discounts that apply only for purchases coming through certain channels:
Be aware that this is not a "secure" solution, since anyone could use the parameters to get the discount. If you want to ensure that the customer has right to the discount, consider targeting by products owned instead. This method utilizes our unique ability to correlate customer inventory with exclusive discounts. --- # https://moonbase.sh/docs/licensing/api/ title: 'Licensing API Reference', description: 'On this page, you can learn how to use the Licensing API to integrate Moonbase into your apps.', } # Licensing API Reference Do you have a specific scenario you need to support or other questions?\ Reach out to us through the support channel, or at [developers@moonbase.sh](mailto:developers@moonbase.sh). ## Authentication The licensing endpoints documented below are made to be called from the apps you create, with the customer initiating any actions.\ Therefore, most of the endpoints are publicly available, only some requiring authentication in the form of JWT tokens.\ These JWT tokens can be obtained by authenticating the user through `/api/customer/identity/sign-in`, and the tokens refreshed by handing them in through `/api/customer/identity/refresh`. --- ## Sign in a customer {{ tag: 'POST', label: '/api/customer/identity/sign-in' }} This endpoint allows you to sign in a customer given a combination of email address and password. The JWT returned contains an access token with a default lifetime of 15 minutes. Refresh tokens can be used to get new access tokens. ### Required query parameters The email of the customer. ### Required body content The password of the customer. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/customer/identity/sign-in?email=test@example.com Content-Type: text/plain Password1234! ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/customer/identity/sign-in?email=test@example.com \ -H 'Content-Type: text/plain' \ -d 'Password1234!' ``` ```json {{ title: 'Response' }} { "id": "e891f4b0-6d73-48ed-8fc1-c6b166c5379a", "name": "Example User", "email": "user@example.com", "tenantId": "demo", "userType": "Customer", "accessToken": "eyJhbGciOiJIUzUxMiIsInR5c...", "refreshToken": "MDAxNDhiZGUtMzY1Yi00MTYx..." } ``` --- ## Refresh a token {{ tag: 'POST', label: '/api/customer/identity/refresh' }} This endpoint allows you to exchange a refresh token + access token for a new pair of tokens. ### Required query parameters The refresh token you got during the last refresh or sign-in if first time refreshing. ### Required body content The access token belonging to the refresh token. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/customer/identity/refresh?token=MDAxNDhiZGUtMzY1Yi00MTYx... Content-Type: text/plain eyJhbGciOiJIUzUxMiIsInR5c... ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/customer/identity/refresh?token=MDAxNDhiZGUtMzY1Yi00MTYx... \ -H 'Content-Type: text/plain' \ -d 'eyJhbGciOiJIUzUxMiIsInR5c...' ``` ```json {{ title: 'Response' }} { "id": "e891f4b0-6d73-48ed-8fc1-c6b166c5379a", "name": "Example User", "email": "user@example.com", "tenantId": "demo", "userType": "Customer", "accessToken": "eyJhbGciOiJIUzUxMiIsInR5c...", "refreshToken": "MDAxNDhiZGUtMzY1Yi00MTYx..." } ``` --- ## Sign up {{ tag: 'POST', label: '/api/customer/identity/sign-up' }} This endpoint allows you to sign up a new customer on your account. Useful if you require accounts to start trials. ### Required body properties Full name of the user. Email address of the user. Will be used as username. Can be changed by the user. The initial password for the user. Must contain lower case characters, uppercase characters, numbers and a symbol. ### Optional body properties A billing address for the user. Will be used when purchasing new products.\ Contains the following properties: ISO 3166-1 alpha-2 two-letter country code. First line of the regular street address. Second line of the regular street address. Postal code of the address. Locality of the address, only required if no region is given.\ Also known as `City`. Region of the address, only required if no locality is given.\ Also known as `State`. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/customer/identity/sign-up Content-Type: application/json { "name": "Example User", "email": "user@example.com", "password": "Password1234!", "address": { "countryCode": "NO", "streetAddress1": "Slottsplassen 1", "streetAddress2": null, "postCode": "0010", "region": "Oslo", "locality": null } } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/customer/identity/sign-up \ -H 'Content-Type: application/json' \ -d '{ "name": "Example User", "email": "user@example.com", "password": "Password1234!", "address": { "countryCode": "NO", "streetAddress1": "Slottsplassen 1", "streetAddress2": null, "postCode": "0010", "region": "Oslo", "locality": null } }' ``` ```json {{ title: 'Response' }} { "id": "e891f4b0-6d73-48ed-8fc1-c6b166c5379a", "name": "Example User", "email": "user@example.com", "tenantId": "demo", "userType": "Customer", "accessToken": "eyJhbGciOiJIUzUxMiIsInR5c...", "refreshToken": "MDAxNDhiZGUtMzY1Yi00MTYx..." } ``` --- ## Licensing License tokens generated by Moonbase are generated as JWT tokens. These tokens are signed with your account's private key, while you ship the public key with your apps. Part of the metadata in these tokens allow you to verify that the token is for the device it's being activated on. If you are using frameworks or languages that don't have easy ways to deserialize and validate JWTs, reach out to us through support to investigate alternatives. ### License token claims Identifier of this unique license activation. Expiration date and time for this license token. You should have a check that this time is still in the future when validating the license. Only applicable for licenses that expire, will always be present on trials, and on any licenses attached to a subscription. This is following the JWT standard and is defined in seconds since epoch. Date and time at which this license or trial was created. This is following the JWT standard and is defined in seconds since epoch. Date and time at which this token becomes valid. Will always be the same as the `iat` claim. This is following the JWT standard and is defined in seconds since epoch. Issuer of the license, will be the ID of your Moonbase account, for example `acme-co`. Audience of the license, will be the ID of the product the license is issued for, same as the `p:id` claim. Identifier of the user that owns this license. If the user is anonymous, this ID will be an empty GUID (`00000000-0000-0000-0000-000000000000`). The name of the user that owns this license. If the user is anonymous, this will say `anonymous`. The email of the user that owns this license. If the user is anonymous, this will say `anonymous`. The ID of the license that activated this license token. One license may have multiple tokens active depending on seat configuration of the product. The ID of the product this license is for, same as the `aud` claim. You should have a check that this is the same as the current product the user is activating. The name of the product this license is for. The current release version of the product. If there is no current release for the product, this claim will not be included. In case the user of this license owns any sub-products of this product, this claim will have a comma-separated list of product IDs of those sub-products. If there are no sub-products owned, this claim will not be included. The ID of the subscription this license belongs to, if any. If this is a perpetual license, this claim will not be included. The activation method used for activating this license token. Based on this you can determine if re-validations of the license should take place. The signature of the device being activated, that was passed in when requesting the license token initially. You should have a check that this signature is the same as the device the user is activating. Date and time of the last time this license token was validated online. You should have a check that this date is not too old for your given re-validation frequency. Similarly to `exp` and `iat`, this claim is also defined in seconds since epoch. Flag for if this license token represents a time-limited trial, or a owned license. For compatability reasons, this value may be presented as a string and not a strict boolean value. Your parsing should handle both. The name of your Moonbase merchant account. Custom properties from the product, where `includeInToken` is set to `true`. Values are flattened to key-value pairs without the type wrapper. Custom properties from the customer (user), where `includeInToken` is set to `true`. Values are flattened to key-value pairs without the type wrapper. Custom properties from the license, where `includeInToken` is set to `true`. Values are flattened to key-value pairs without the type wrapper. Custom properties from the trial, where `includeInToken` is set to `true`. Only present on trial tokens. Values are flattened to key-value pairs without the type wrapper. ``` {{ title: 'License token' }} eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjdhMzg4ZjZiZGE1MTY0NjFmYzcwMzQ2ZDM2ZThlMTI3IiwidmVuZG9yIjoiRGVtbyBDby4iLCJ1OmlkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwidTpuYW1lIjoiYW5vbnltb3VzIiwidTplbWFpbCI6ImFub255bW91cyIsImw6aWQiOiI3YTM4OGY2YmRhNTE2NDYxZmM3MDM0NmQzNmU4ZTEyNyIsInA6aWQiOiJkZW1vLWFwcCIsInA6bmFtZSI6IkRlbW8gQXBwIiwicDpyZWwiOiIxLjAuMCIsIm1ldGhvZCI6Ik9ubGluZSIsInNpZyI6InhMVEkzVkRFeU9qZGRkRTJPakE1TGpZMU5UVXlOVE5hSWl3aWRISnBZV3dpT2lKMGNuVmxJaXdpWVhWa0kiLCJ2ZXIiOiIyMDI1LTAxLTA4VDEyOjE1OjI3Ljg0OTYyNDJaIiwidmFsaWRhdGVkIjoxNzM2MzM4NTI3LCJ0cmlhbCI6InRydWUiLCJhdWQiOiJkZW1vLWFwcCIsImlzcyI6ImRlbW8iLCJleHAiOjE3Mzc1NDgxMjcsImlhdCI6MTczNjMzODUyNywibmJmIjoxNzM2MzM4NTI3fQ.UgQwC4KNhPofTvqwn0LkUhtYlk0PnYUBHFm_-cgLH8BuTj0gcVAJFuAWnjTP4pCtXTBN1l_a6zXePoHzZdNFN7DOkBmd5VbiFdLpNJUTrnRT-2Vzn6EC5wrrJxAKtBZb22uOarL2TZCvInDGdTqiQVjIwhFMK9em8PBOuGQ_U3-mfGWnp8dzo02rcDqxTtOTWTu8tPXt9GOYjqmr7myWH0bIdfswCd35VEeLS2n6FRBBOeUZY-DZHCL0feJvOouXXux2BVBZM8AjAPGqfnMi6UyNCI3vrTZgJ21N0kKJKx01NteC5y3qARXpNQ8_rGm5VzmZ3NoRq792_pazLytZIQ ``` ```json {{ title: 'Deserialized token' }} { "id": "cef3e37be338acabd5da04105099025c", "vendor": "Demo Co.", "u:id": "00000000-0000-0000-0000-000000000000", "u:name": "anonymous", "u:email": "anonymous", "l:id": "cef3e37be338acabd5da04105099025c", "p:id": "demo-app", "p:name": "Demo App", "p:rel": "1.0.0", "p:properties": { "category": "Audio Tools" }, "l:properties": { "seat_name": "Studio A" }, "method": "Online", "sig": "xLTI3VDEyOjE2OjA5LjY1NTUyNTNaIiwidHJpYWwiOiJ0cnVlIiwiYXVkI", "trial": "true", "aud": "demo-app", "iss": "demo", "validated": 1736338527, "exp": 1737548127, "iat": 1736338527, "nbf": 1736338527 } ``` --- ## Request a trial license token {{ tag: 'POST', label: '/api/client/trials/{productId}/request' }} This endpoint allows you to request a trial license token for the current device. If you have an authenticated customer, you may attach the access token in the `Authorization` header to authenticate the call. Will return a `HTTP 401: Unauthorized` if the product requires account to activate trial and you do not have an authenticated customer. ### Required body properties The name of the device being activated. Will be shown in the customer portal in case the customer wants to revoke this license activation. A fingerprint of the current device that will not change. Should be used when validating future license tokens, that they have indeed been issued for the current device. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/client/trials/demo-app/request Content-Type: application/json { "deviceName": "Example Device Name", "deviceSignature": "xLTI3VDEyOjE2OjA5LjY1NTUyNTNaIiwidHJpYWwiOiJ0cnVlIiwiYXVkI" } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/client/trials/demo-app/request \ -H 'Content-Type: application/json' \ -d '{ "deviceName": "Example Device Name", "deviceSignature": "xLTI3VDEyOjE2OjA5LjY1NTUyNTNaIiwidHJpYWwiOiJ0cnVlIiwiYXVkI" }' ``` ``` {{ title: 'Response' }} eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNlZjNlMzdiZTMzOGFjYWJkNWRhMDQxMDUwOTkwMjVjIiwidmVuZG9yIjoiRGVtbyBDby4iLCJ1OmlkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwidTpuYW1lIjoiYW5vbnltb3VzIiwidTplbWFpbCI6ImFub255bW91cyIsImw6aWQiOiJjZWYzZTM3YmUzMzhhY2FiZDVkYTA0MTA1MDk5MDI1YyIsInA6aWQiOiJkZW1vLWFwcCIsInA6bmFtZSI6IkRlbW8gQXBwIiwibWV0aG9kIjoiT25saW5lIiwic2lnIjoieExUSTNWREV5T2pFMk9qQTVMalkxTlRVeU5UTmFJaXdpZEhKcFlXd2lPaUowY25WbElpd2lZWFZrSSIsInZlciI6IjIwMjQtMDEtMjdUMTI6MjU6NTcuNTc4MDkwNloiLCJ0cmlhbCI6InRydWUiLCJhdWQiOiJkZW1vLWFwcCIsImV4cCI6MTcwNzU2Nzk1NywiaXNzIjoiZGVtbyIsImlhdCI6MTcwNjM1ODM1NywibmJmIjoxNzA2MzU4MzU3fQ.cze2gijLNTi9IxcgqpKUMk8Eq90dlG0UOvXVlfAwdeDJ-VCLlK3GIHg23t5BzsQjGBJM0GU6mFV-IXrdIdu8ibFdiEpCIqDS880EoEZfRLyKjzOI2n8heBPNWsdLxGz9z40RxGNOp_jNNf_iSfuOFGf_1evLN1o1BPw5iVlXJzk7TMO0D1kF50sCgF1TagtB_hQoK-ilAu-PWm7i_6CXjj3f9msggNBUcbehlF7nd0GWvM6jm0Aq5qc8iYeENLM8696g2xeKP1cj4WdmLirmOg-EZ-b8uw_VoJr0Nupdcg6qCcTzM5ytyPYWAiIDOhLdBfED5BhBeNWcp8kNFUGXYw ``` --- ## Request a license token {{ tag: 'POST', authenticated: true, label: '/api/client/licenses/{productId}/request' }} This endpoint allows you to request a license token for the current device. Requires an authenticated customer, and that the customers has free license activations on their account to activate the product. ### Required headers Access token for the authenticated customer in your app, in the form of a JWT bearer token. ### Required body properties The name of the device being activated. Will be shown in the customer portal in case the customer wants to revoke this license activation. A fingerprint of the current device that will not change. Should be used when validating future license tokens, that they have indeed been issued for the current device. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/client/licenses/demo-app/request Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJiY... { "deviceName": "Example Device Name", "deviceSignature": "xLTI3VDEyOjE2OjA5LjY1NTUyNTNaIiwidHJpYWwiOiJ0cnVlIiwiYXVkI" } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/client/licenses/demo-app/request \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiJiY...' \ -d '{ "deviceName": "Example Device Name", "deviceSignature": "xLTI3VDEyOjE2OjA5LjY1NTUyNTNaIiwidHJpYWwiOiJ0cnVlIiwiYXVkI" }' ``` ``` {{ title: 'Response' }} eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM2Mjg0YTkwZDQ3ZGY4MTQ1MzBjMDE1ZjBjY2ZkZGZhIiwidmVuZG9yIjoiRGVtbyBDby4iLCJ1OmlkIjoiYmJkNjNhNmYtMmE0MS00MzJkLTg0YjUtNmRjYmFjMGJmZDIwIiwidTpuYW1lIjoiRXhhbXBsZSBVc2VyIiwidTplbWFpbCI6ImV4YW1wbGVAbW9vbndhdGVyLm5vIiwibDppZCI6ImYzZGEyYzlhLWQ3YTAtNGFhOS1hNGI1LTAwNzg2YWU1ZmM5OCIsInA6aWQiOiJkZW1vLWFwcCIsInA6bmFtZSI6IkRlbW8gQXBwIiwibWV0aG9kIjoiT25saW5lIiwic2lnIjoieExUSTNWREV5T2pFMk9qQTVMalkxTlRVeU5UTmFJaXdpZEhKcFlXd2lPaUowY25WbElpd2lZWFZrSSIsInZlciI6IjIwMjQtMDEtMjdUMTI6NDI6NTguODM0MTUzN1oiLCJ0cmlhbCI6ImZhbHNlIiwiYXVkIjoiZGVtby1hcHAiLCJpc3MiOiJkZW1vIiwiaWF0IjoxNzA2MzU5MzA5LCJuYmYiOjE3MDYzNTkzMDl9.jwmAyg2e22mF3Ds4bLMcyrr6e_lO-lpmwhDQz2X9psrwPBlsf3Od8vGGiVlfXZGJJWsTwZBVGTPHx2NpqtexGaytMXlS23rSc0My-nQXsShao-9hyF4nOPHZWVEvUbZkMzk4hbO4AiYlyw2KKyvYGMWA0jITSJKgs-M4YcUl2wQHm5FeXRZyGbc5CzWV7lq74qMqM_EkYZ2nvq6AXEAJ6c9TsP2AEH2zBhi79EbZwE9HWWGEu55pZi8-GBZdeZ1tZfkKz19rOBnWFP8BvKrkCuJDPR7JdRywsYEsRzqI59QANl5zoRAnP9zOn-_b1QQZnZMPOdohn1WVszbDq43bTw ``` --- ## Validate a license token {{ tag: 'POST', label: '/api/client/licenses/{productId}/validate' }} This endpoint allows you to send a license token you have to have it validated. For all `Online` activated tokens, this should be done regularly to be able to deactivate revoked devices fast. You will receive an updated token in return, with new valid validation timestamps if the license is still active. ### Required body content The license token you wish to validate. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/client/licenses/demo-app/validate Content-Type: text/plain eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM2Mjg0YTkwZDQ3ZGY4MTQ1MzBjMDE1Z... ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/client/licenses/demo-app/validate \ -H 'Content-Type: text/plain' \ -d 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM2Mjg0YTkwZDQ3ZGY4MTQ1MzBjMDE1Z...' ``` ``` {{ title: 'Response' }} eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM2Mjg0YTkwZDQ3ZGY4MTQ1MzBjMDE1ZjBjY2ZkZGZhIiwidmVuZG9yIjoiRGVtbyBDby4iLCJ1OmlkIjoiYmJkNjNhNmYtMmE0MS00MzJkLTg0YjUtNmRjYmFjMGJmZDIwIiwidTpuYW1lIjoiRXhhbXBsZSBVc2VyIiwidTplbWFpbCI6ImV4YW1wbGVAbW9vbndhdGVyLm5vIiwibDppZCI6ImYzZGEyYzlhLWQ3YTAtNGFhOS1hNGI1LTAwNzg2YWU1ZmM5OCIsInA6aWQiOiJkZW1vLWFwcCIsInA6bmFtZSI6IkRlbW8gQXBwIiwibWV0aG9kIjoiT25saW5lIiwic2lnIjoieExUSTNWREV5T2pFMk9qQTVMalkxTlRVeU5UTmFJaXdpZEhKcFlXd2lPaUowY25WbElpd2lZWFZrSSIsInZlciI6IjIwMjQtMDEtMjdUMTI6NDI6NTguODM0MTUzN1oiLCJ0cmlhbCI6ImZhbHNlIiwiYXVkIjoiZGVtby1hcHAiLCJpc3MiOiJkZW1vIiwiaWF0IjoxNzA2MzU5MzA5LCJuYmYiOjE3MDYzNTkzMDl9.jwmAyg2e22mF3Ds4bLMcyrr6e_lO-lpmwhDQz2X9psrwPBlsf3Od8vGGiVlfXZGJJWsTwZBVGTPHx2NpqtexGaytMXlS23rSc0My-nQXsShao-9hyF4nOPHZWVEvUbZkMzk4hbO4AiYlyw2KKyvYGMWA0jITSJKgs-M4YcUl2wQHm5FeXRZyGbc5CzWV7lq74qMqM_EkYZ2nvq6AXEAJ6c9TsP2AEH2zBhi79EbZwE9HWWGEu55pZi8-GBZdeZ1tZfkKz19rOBnWFP8BvKrkCuJDPR7JdRywsYEsRzqI59QANl5zoRAnP9zOn-_b1QQZnZMPOdohn1WVszbDq43bTw ``` --- ## Revoke a license token {{ tag: 'POST', label: '/api/client/licenses/{productId}/revoke' }} This endpoint allows you to send a license token you have to have it revoked. By doing this, you will effectively free up a seat for the customer, so that they can activate other devices. Offline activated license tokens will not be accepted by this endpoint as they are not intended to be floating licenses. ### Required body content The license token you wish to revoke. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/client/licenses/demo-app/revoke Content-Type: text/plain eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM2Mjg0YTkwZDQ3ZGY4MTQ1MzBjMDE1Z... ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/client/licenses/demo-app/revoke \ -H 'Content-Type: text/plain' \ -d 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM2Mjg0YTkwZDQ3ZGY4MTQ1MzBjMDE1Z...' ``` --- ## Browser Activations A good alternative to the above endpoints where you need to authenticate the customer in your apps is to request browser based activation instead. By requesting an activation, you can open a browser where the customer is in most cases already logged in, where they can choose what to fulfill the request with. --- ## Request activation {{ tag: 'POST', label: '/api/client/activations/{productId}/request' }} This endpoint allows you to request a activation for the current device. The returned payload will have two URLs that are relevant: This URL can be polled to check for the current status of the request. See `GET /api/client/activations/{requestId}` below. This URL should be opened in the browser of the current device so the customer can complete the activation process. ### Required body properties The name of the device being activated. Will be shown in the customer portal in case the customer wants to revoke this license activation. A fingerprint of the current device that will not change. Should be used when validating future license tokens, that they have indeed been issued for the current device. ```http {{ title: 'HTTP' }} POST https://demo.moonbase.sh/api/client/activations/demo-app/request Content-Type: application/json { "deviceName": "Example Device Name", "deviceSignature": "xLTI3VDEyOjE2OjA5LjY1NTUyNTNaIiwidHJpYWwiOiJ0cnVlIiwiYXVkI" } ``` ```bash {{ title: 'cURL' }} curl -X 'POST' https://demo.moonbase.sh/api/client/activations/demo-app/request \ -H 'Content-Type: application/json' \ -d '{ "deviceName": "Example Device Name", "deviceSignature": "xLTI3VDEyOjE2OjA5LjY1NTUyNTNaIiwidHJpYWwiOiJ0cnVlIiwiYXVkI" }' ``` ```json {{ title: 'Response' }} { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "request": "https://demo.moonbase.sh/api/client/activations/3fa85f64-5717-4562-b3fc-2c963f66afa6", "browser": "https://demo.moonbase.sh/activate/auto?token=3fa85f64-5717-4562-b3fc-2c963f66afa6" } ``` --- ## Get request status {{ tag: 'GET', label: '/api/client/activations/{requestId}' }} This endpoint allows you to check the current status of a activation request. By polling this regularly, you can activate the device as soon as the user fulfills the request in their browser. Be careful to not poll too frequently, to avoid rate limiting restrictions on your account. One request per 5 seconds is considered a safe frequency. Until the customer has fulfilled the request, a `HTTP 204: No content` will be returned. Once fulfilled, the response will contain a license token ready to be used. ```http {{ title: 'HTTP' }} GET https://demo.moonbase.sh/api/client/activations/3fa85f64-5717-4562-b3fc-2c963f66afa6 ``` ```bash {{ title: 'cURL' }} curl -X 'GET' https://demo.moonbase.sh/api/client/activations/3fa85f64-5717-4562-b3fc-2c963f66afa6 ``` ``` {{ title: 'Response' }} eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImNlZjNlMzdiZTMzOGFjYWJkNWRhMDQxMDUwOTkwMjVjIiwidmVuZG9yIjoiRGVtbyBDby4iLCJ1OmlkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwidTpuYW1lIjoiYW5vbnltb3VzIiwidTplbWFpbCI6ImFub255bW91cyIsImw6aWQiOiJjZWYzZTM3YmUzMzhhY2FiZDVkYTA0MTA1MDk5MDI1YyIsInA6aWQiOiJkZW1vLWFwcCIsInA6bmFtZSI6IkRlbW8gQXBwIiwibWV0aG9kIjoiT25saW5lIiwic2lnIjoieExUSTNWREV5T2pFMk9qQTVMalkxTlRVeU5UTmFJaXdpZEhKcFlXd2lPaUowY25WbElpd2lZWFZrSSIsInZlciI6IjIwMjQtMDEtMjdUMTI6MjU6NTcuNTc4MDkwNloiLCJ0cmlhbCI6InRydWUiLCJhdWQiOiJkZW1vLWFwcCIsImV4cCI6MTcwNzU2Nzk1NywiaXNzIjoiZGVtbyIsImlhdCI6MTcwNjM1ODM1NywibmJmIjoxNzA2MzU4MzU3fQ.cze2gijLNTi9IxcgqpKUMk8Eq90dlG0UOvXVlfAwdeDJ-VCLlK3GIHg23t5BzsQjGBJM0GU6mFV-IXrdIdu8ibFdiEpCIqDS880EoEZfRLyKjzOI2n8heBPNWsdLxGz9z40RxGNOp_jNNf_iSfuOFGf_1evLN1o1BPw5iVlXJzk7TMO0D1kF50sCgF1TagtB_hQoK-ilAu-PWm7i_6CXjj3f9msggNBUcbehlF7nd0GWvM6jm0Aq5qc8iYeENLM8696g2xeKP1cj4WdmLirmOg-EZ-b8uw_VoJr0Nupdcg6qCcTzM5ytyPYWAiIDOhLdBfED5BhBeNWcp8kNFUGXYw ``` --- ## Metadata for analytics To be able to have insights into what version of your software is being used, what platforms it is installed on and any other dimensions that might be interesting to you, you may optionally include metadata in the requests for this API. This should be added to query parameters, and supports the following parameters: The semantic version of the application, for example `1.0.0`. The platform that is being used, must match the enum exactly. Any additional dimensions you want to track can be added as a key/value pair to the `meta` parameter. ``` {{ title: 'Request URI' }} /api/client/activations/example-product/request ?appVersion=1.0.0 &platform=Windows &meta[DAW]=Reaper &meta[Format]=VST3 ``` Some of our licensing SDKs may automatically add some relevant context for you based on the framework you are using, and also allow you to add more parameters on demand. --- # https://moonbase.sh/docs/licensing/external-systems/ title: 'External licensing systems', description: 'On this page, you can learn how to integrate Moonbase with external licensing platforms.', } # External licensing systems If you have pre-existing software that relies on other licensing systems than Moonbase, you can integrate the two. We support two main paths to achieve this integration: 1. Using pre-uploaded key codes 1. Using a HTTP API generator To make it easier to transition between systems, our HTTP API generator supports several protocols: 1. A modern JSON-based API integration with HMAC security signatures 1. Compatibility modes for popular platforms like Fastspring, MyCommerce, DigitalRiver, ShareIt and others If you are building APIs for your own licensing backend, using our JSON-based integration is highly recommended, but if you are transitioning from other payment providers, our compatibility mode might let you move over without any changes necessary in your backend systems. Do you have a specific scenario you need to support or other questions?\ Reach out to us through the support channel, or at [developers@moonbase.sh](mailto:developers@moonbase.sh). ## Pre-uploaded key codes In the case your licensing system can pre-generate arbitrary key codes for activating your software, you may opt to use our key code list feature. To get started with this, simply choose the "Key Codes" generator on your product in Moonbase:
At this point, you may start uploading as many key codes as you wish to be used for future purchases. The product overview will indicate whether or not a product has enough key codes remaining, and it's your responsibility to ensure codes don't run out. ## HTTP generator Using our HTTP generator means that Moonbase will call your API on customer purchases to fulfill the order with licenses. It's important that your API is always available, so that customers will get access to products when they make their payment. The API call has a timeout of **5 seconds**, and will retry up to **3 times** if a failure code is returned. Your endpoint should be able to handle the request payload, and return a JSON object with licenses per product: ### Request payload The type of fulfillment being requested, `Order` being purchases, `Voucher` being voucher redemptions and `License` being manual license provisioning from the Moonbase app. A reference to the Order/Voucher/License being fulfilled, this will be unique across all fulfillment requests your backend may receive. Details about the customer who initiated the fulfillment, it may have the following fields: Globally unique customer ID for this customer. Full name of the customer. If the customer is a business, this will have the name of the business. If the customer is a business, this will have the tax ID of the business if given. Email address of the customer, this is always present and used as the login for the customer. Do note that Moonbase supports customers changing the email address of their account. The billing address of the customer if given: ISO 3166-1 alpha-2 two-letter country code. First line of the regular street address. Second line of the regular street address. Postal code of the address. Locality of the address, only required if no region is given.\ Also known as `City`. Region of the address, only required if no locality is given.\ Also known as `State`. A list of license requests with objects of the following shape: The ID of the product that needs licenses. The number of licenses that is requested. ISO 8601 timestamp for when the issued licenses should expire. Present for subscriptions and other time-limited products, and omitted for perpetual licenses. This value already includes any grace period configured on the product. Present when the request fulfills a subscription, and omitted for one-off purchases. It may have the following fields: The ID of the subscription this license belongs to. The billing interval of the subscription. ISO 8601 timestamp for the end of the current billing period (the renewal boundary). This does not include any grace period. ISO 8601 timestamp for when the license actually stops working. For active subscriptions this is `currentPeriodEnd` plus the configured grace period, and for cancelled subscriptions it equals `currentPeriodEnd`. The current status of the subscription. An initial purchase is always reported as `Active`. The number of the current billing cycle of the subscription. Cycles are 1-based, so an initial purchase is cycle `1`, the first renewal is cycle `2`, and so on. For perpetual products both `expiresAt` and `subscription` are omitted. For subscriptions, an initial purchase reports `status` as `Active` and `currentCycle` as `1`, while renewals report the subscription's current values. Treat every subscription field as optional, since each is sent only when it is known. ```http {{ title: 'HTTP' }} POST https://license-backend.your-domain.example/fulfill Content-Type: application/json X-Signature: HNA0AVKUMOF5CXC8GKELYVYTYK37Q4ONGLJEL2TJTGA= { "type" : "Order", "reference" : "b28f721c-c29a-4fc4-9153-04f128283c51", "customer" : { "id" : "2d473e34-fc59-4e6e-9f38-4ad379baa8e9", "name" : "John Doe", "businessName" : null, "taxId" : null, "email" : "john.doe@example.com", "address" : null }, "requests" : [ { "productId" : "example-product", "quantity" : 3, "expiresAt" : "2026-07-14T12:44:00Z", "subscription" : { "subscriptionId" : "f6a3c1e2-9b7d-4c3a-8e21-2b9f0a4d7c11", "interval" : "Monthly", "currentPeriodEnd" : "2026-07-14T10:44:00Z", "licenseExpiresAt" : "2026-07-14T12:44:00Z", "status" : "Active", "currentCycle" : 2 } } ] } ``` ### Expected response When responding to license requests, your backend should respond with a dictionary where the key is the product ID being fulfilled, and the value an array of licenses to issue to the customer. Each license returned may either be a string which can contain line feed characters, but should not contain any sort of rich text content or HTML, or a file. In case you return a file object, make sure to include all of the following fields: The name of the file being returned, including file extension. The MIME type of the file being returned, e.g. `application/pdf`. The file data, encoded as a Base64 string. In the example response listed, we return three licenses for the product with ID `example-product`, where the first two are simple license keys represented as strings, and the third is a text file containing the license key. It's imperative that the number of values in the list matches the requested quantity of licenses for the given product. ```json { "example-product" : [ "LICENSE-KEY-1", "LICENSE-KEY-2", { "fileName" : "license-key-3.txt", "contentType" : "text/plain", "data" : "TElDRU5TRS1LRVktMw==" } ] } ``` ## Revoking licenses Besides fulfilling orders, Moonbase can notify your backend whenever a license is revoked, so you can deactivate it in your own system as well. This is optional: when configuring the HTTP generator, set a separate **Revocation Endpoint** URL. When it's set, Moonbase sends a revocation request to it whenever a license is revoked, either manually from the Moonbase app or automatically when an order is refunded. When an order is only partially refunded, only the licenses for the refunded units are revoked, so you will receive revocation requests for those licenses only. If you leave the Revocation Endpoint blank, revocations are not forwarded and no action is taken on your backend. Revocation requests are signed with the same **HMAC-SHA256** signature as fulfillment requests (see the Security section below) and are retried up to **3 times** on failure. Acknowledge a revocation by returning any `2xx` status code; no response body is required. Because Moonbase may retry, your handler should be idempotent. The type of request, this is always `License` for revocations. The ID of the license being revoked. The ID of the customer who owns the license. The ID of the product the license is for. The license content originally issued for this license, in the same form your backend returned it during fulfillment: either a string, or a file object with `fileName`, `contentType` and `data`. Use this to identify which license to deactivate. Present when the revoked license belongs to a subscription, using the same shape as the `subscription` object on fulfillment requests above. Omitted for one-off licenses. ```http {{ title: 'HTTP' }} POST https://license-backend.your-domain.example/revoke Content-Type: application/json X-Signature: HNA0AVKUMOF5CXC8GKELYVYTYK37Q4ONGLJEL2TJTGA= { "type" : "License", "reference" : "8f2c1b4e-3a6d-4f51-9c7a-1d2e3f4a5b6c", "customerId" : "2d473e34-fc59-4e6e-9f38-4ad379baa8e9", "productId" : "example-product", "license" : "LICENSE-KEY-1", "subscription" : { "subscriptionId" : "f6a3c1e2-9b7d-4c3a-8e21-2b9f0a4d7c11", "interval" : "Monthly", "currentPeriodEnd" : "2026-07-14T10:44:00Z", "licenseExpiresAt" : "2026-07-14T10:44:00Z", "status" : "Cancelled", "currentCycle" : 4 } } ``` ## Security To ensure your fulfillment and revocation endpoints are secure and safe from request forgery, we attach a signature to every fulfillment and revocation request being sent. This signature is using a **HMAC-SHA256** authentication code using the secret set up while configuring the generator in the Moonbase app. Using the raw request body and the secret, you can calculate a signature, and make sure that the signature you calculate is the same as ours. ```csharp using var algorithm = new HMACSHA256(Encoding.UTF8.GetBytes(expectedSecret)); var json = await request.Content.ReadAsStringAsync(); var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(json)); var signature = Convert.ToBase64String(hash).ToUpperInvariant(); var sentSignature = request.Headers.GetValues("X-Signature").Single(); Assert.Equal(sentSignature, signature); ``` If you're using out compatibility modes for other platforms, security patterns may differ. Reach out to us for more information about maintaining secure endpoints for license fulfillment. --- # https://moonbase.sh/docs/licensing/offline-activations/ title: 'Offline activations', description: 'Moonbase licensing supports offline activations, read on to learn more about how it works and how to integrate.', } # Offline activations As described in [activation flows](/docs/licensing/activation-flows/#offline-activation), offline activations works by having the offline device generate a **device token** to be uploaded on your customer portal to exchange it for a **license token** that can be returned to your software. This enables customers to move tokens between devices without them being connected to the internet, but still employ strong cryptography. ## Device tokens The device token is a file containing enough metadata for the Moonbase backend to be able to issue a license for the specific product and device. To generate this file, you should first construct a basic JSON payload containing the following details: The unique signature for this device, this is what will be returned as the `sig` claim in license tokens. User-friendly name of the device, which will be shown for your customer when managing license activations. The ID of the product being activated. The expected license format to be returned, must be `"JWT"`. ```json {{ title: 'Raw device token' }} { "id": "cef3e37be338acabd5da04105099025c", "name": "Example Device", "productId": "example-product", "format": "JWT", } ``` Once constructed, you may place the [Base64](https://en.wikipedia.org/wiki/Base64) encoded result of the JSON payload into a file, and present the file to the user for upload to the customer portal. The file should have a `.dt` extension to be recognized by the Moonbase hosted customer portal. If using our embedded storefront or APIs, you may choose the extension you want to use as you also control the file input. The above example would result in a file like this: ``` {{ title: 'device-token.dt' }} ewogICAgImlkIjogImNlZjNlMzdiZTMzOGFjYWJkNWRhMDQxMDUwOTkwMjVjIiw KICAgICJuYW1lIjogIkV4YW1wbGUgRGV2aWNlIiwKICAgICJwcm9kdWN0SWQiOi AiZXhhbXBsZS1wcm9kdWN0IiwKICAgICJmb3JtYXQiOiAiSldUIiwKfQ== ``` ## Exchanging tokens Once customers have their device token, they may upload it and exchange it for an offline activated license token. ### Hosted portal If using the hosted portal, you may redirect the customer to `https://.moonbase.sh/activate`, where they will be prompted to upload their device token (also known colloquially as a **machine file**): ![](/assets/docs/offline-activation.png) This process will lead the customer to download a license token in a `license-token.mb` file. ### Embedded storefront If using the embedded storefront, you should add a button to your website or otherwise trigger the `activate_product` intent in the library. This will prompt the customer to sign in before allowing a device token upload. ```html {{ title: 'index.html' }} ``` If your licensing integration is producing files with an extension other than the default `.dt` extension, you may configure the module to allow other files to be uploaded. Similarly, you can also configure what the license token being downloaded should be called: ```html {{ title: 'index.html' }} ``` Learn more in our documentation page for the [embedded storefront](/docs/storefronts/embedded). ### Custom storefront If using our frontend SDKs or APIs directly, you can read more on: * **Vue.js SDK**: use the `activateProduct` on the `useInventory` composable, [learn more here](/docs/storefronts/sdks/vue/#use-inventory). * **API**: use the `/api/customer/inventory/activate` endpoint, [learn more here](/docs/storefronts/api/#activate-product). You will be in full control over what files to accept for upload, as well as what to name the downloaded license token file. ## Loading the license token Once the customer have their **license token** downloaded from the portal, they should be able to load it into your software, at which point you may treat it as any normal license token you would have gotten from the API. The key differences are: * The `method` claim is set to `Offline` * You should not attempt to validate the token online --- # https://moonbase.sh/docs/licensing/ title: 'Software licensing', description: 'Find out exactly how to integrate with Moonbase for software licensing through these docs.', } # Software licensing By using Moonbase as your licensing provider, you have the ability to offer multiple flows for your customers to activate their software. Our APIs and SDKs have support for browser based license activations, in-app license activations or offline file-based activations. To learn more about the activation flows, see [Activation flows](./activation-flows). In the spirit of simplifying and saving time, we've built some SDKs to ensure that you can integrate Moonbase into your software as easy as possible. If you want absolute control and full customization, you can use our API directly. Check out the [API reference](./api) for documentation, and reach out to us if you have any questions or feature requests. If you are using one of the languages or frameworks we've built SDKs for, we advise you to check them out as they make integrating much simpler since they contain framework specific utilities and abstractions. ## Using external licensing platforms If you are using Moonbase for your payments and e-commerce needs, you can also configure your Moonbase products to provision licenses in external systems. We support multiple integration patterns, including pre-uploading lists of key codes, to more advanced HTTP API integrations. To learn how to get started with integrating your licensing platform with Moonbase, see [External systems](./external-systems). --- # https://moonbase.sh/docs/licensing/sdks/cpp/ title: 'C++ SDK', description: 'Header-only C++17 SDK for integrating Moonbase license activation into native C++ apps, with two JUCE integration paths.', } # C++ SDK A header-only C++17 SDK for integrating Moonbase license activation into your native applications. It runs on Windows, macOS, and Linux, and ships with two JUCE integration paths for plug-in developers. {{ className: 'lead' }} The SDK is open-source under the MIT license and lives at [`Moonbase-sh/moonbase-cpp`](https://github.com/Moonbase-sh/moonbase-cpp). It exposes the activation primitives directly, so you can use it from any C++ application: CLI tools, Qt apps, custom plug-in formats, or JUCE plug-ins through the [JUCE module](../juce/) that ships in the same repository. It supports: * Browser-based activations with polling * Local RS256 JWT validation against an embedded public key * Online re-validation with configurable cadence and grace period * Cross-SDK device identity implementing the Moonbase device fingerprint spec, so an activation made from a web or Electron app validates in your native app * Overridable license storage (in-memory by default, file-backed store included) * Server-side activation revocation * Two JUCE paths: the drop-in [`moonbase_licensing` module](../juce/) with a built-in activation UI, and a copy-paste bridge wrapping `juce::OnlineUnlockStatus` ## Requirements * CMake 3.20 or newer * A C++17 compiler * Windows, macOS, or Linux * `CURL::libcurl` and OpenSSL (`OpenSSL::SSL`, `OpenSSL::Crypto`) * `nlohmann_json` 3.11+ — fetched automatically if not found on the system ## Installation The fastest way to consume the SDK is through CMake's `FetchContent`. For a system-wide install, build and install from source and pick it up with `find_package`. For vendored copies in your repo, `add_subdirectory` works the same way as `FetchContent`. ```cmake {{ title: 'FetchContent' }} include(FetchContent) FetchContent_Declare(moonbase_cpp GIT_REPOSITORY https://github.com/Moonbase-sh/moonbase-cpp.git GIT_TAG v4.0.0) set(MOONBASE_BUILD_TESTS OFF) set(MOONBASE_BUILD_EXAMPLES OFF) FetchContent_MakeAvailable(moonbase_cpp) target_link_libraries(your_app PRIVATE moonbase::licensing) ``` ```cmake {{ title: 'find_package' }} find_package(moonbase_cpp REQUIRED) target_link_libraries(your_app PRIVATE moonbase::licensing) ``` ```bash {{ title: 'Install from source' }} git clone https://github.com/Moonbase-sh/moonbase-cpp.git cd moonbase-cpp cmake -B build -DMOONBASE_BUILD_TESTS=OFF -DMOONBASE_BUILD_EXAMPLES=OFF cmake --build build cmake --install build --prefix /your/prefix ``` The exported `moonbase::licensing` target propagates the include directory and transitive dependencies on libcurl, OpenSSL, and nlohmann_json, so your project doesn't need to repeat `find_package` for any of them. Building the SDK as a top-level project also produces `moonbase_device_id`, a diagnostic that prints the current machine's device ID and how it was derived. Enable it in a subproject build with `-DMOONBASE_BUILD_DEVICE_ID_TOOL=ON`. ## Basic usage Configure the SDK with your account endpoint, product ID, and embedded RS256 public key, then request an activation and poll until it's fulfilled in the browser: ```cpp #include moonbase::licensing_options options; options.endpoint = "https://your-account.moonbase.sh"; options.product_id = "your-product"; options.public_key = embedded_public_key_pem; options.account_id = "account-id"; // optional issuer check moonbase::licensing licensing(options); auto request = licensing.request_activation(); std::cout << "Open: " << request.browser_url << "\n"; std::optional license; while (!license) { std::this_thread::sleep_for(std::chrono::seconds(1)); license = licensing.get_requested_activation(request); } licensing.store().store_local_license(*license); ``` The default license store is in-memory. Wire up `moonbase::file_license_store` (or your own implementation) to persist licenses across launches — see [Custom fingerprinting and storage](#custom-fingerprinting-and-storage) below. ## Startup validation On every launch, run `validate_token_online` against the stored token. It performs the local checks first (signature, device fingerprint, expiry) and then re-validates against the Moonbase API as needed: ```cpp if (auto local = licensing.store().load_local_license()) { auto validated = licensing.validate_token_online(local->token); licensing.store().store_local_license(validated); // persist refreshed token } ``` Two `licensing_options` knobs control API cadence and offline tolerance: * `online_validation_min_interval` (default 5 minutes) — skip the API call when the local `validated_at` is newer than this. Keeps the method cheap to call frequently. * `online_validation_grace_period` (default 7 days) — maximum age the local token may reach without a successful online check. Within grace, transient transport failures fall back to the cached local result; beyond grace, the failure propagates. Definitive server rejections (`license_invalid_error`, `license_expired_error`) always propagate regardless of grace. Offline-activated tokens (`activation_method::offline`) are validated locally even from `validate_token_online` — the SDK never contacts the API for them. Use `validate_token_local` directly when you only want the local check. ## Revoking activations Wire `revoke_activation` to a **Deactivate** or **Sign out** button so users can free up the activation seat for the current device: ```cpp if (auto local = licensing.store().load_local_license()) { licensing.revoke_activation(local->token); } ``` On success the SDK tells the server to release the seat and clears the matching license from the local store. Revoke is only meaningful for online-activated paid licenses; calling it for offline or trial tokens raises `operation_not_supported_error` without contacting the API. ## Custom device IDs and storage The default device ID resolver, `moonbase::moonbase_device_id_resolver`, implements the cross-SDK Moonbase device fingerprint spec, reading SMBIOS on Windows, `IOPlatformUUID` on macOS, and `machine-id` plus world-readable DMI fields on Linux. Because every Moonbase SDK computes the same ID, an activation made from a web or Electron app validates in your native app and the other way round. Override it when you need an exact legacy fingerprint or any other application-specific device ID: ```cpp class my_resolver final : public moonbase::device_id_resolver { public: std::string device_name() const override { return "Studio Mac"; } std::string device_id() const override { return "stable-device-id"; } }; auto store = std::make_shared("licenses/license.mb"); auto resolver = std::make_shared(); moonbase::licensing licensing(options, store, resolver); ``` A custom resolver's ID is compared literally, so it does not need to follow the spec's `mbd2_` stamp format, and it gives up cross-SDK compatibility by definition. These types were renamed in 4.0.0: `fingerprint_provider` is now `device_id_resolver`, `static_fingerprint_provider` is `static_device_id_resolver`, and `licensing::fingerprint()` is `licensing::device_resolver()`. The old names remain as deprecated aliases and will be removed in 5.0.0. Define `MOONBASE_DISABLE_DEPRECATED_ALIASES` to find every remaining use in your codebase. `file_license_store` persists a JSON representation of the validated license at the path you give it. For per-user storage, point it at your platform's app-data directory (e.g. `~/Library/Application Support/YourApp/license.mb` on macOS). ## JUCE integration There are two JUCE paths, both built on the same `moonbase::licensing` core SDK. The **[`moonbase_licensing` module](../juce/)** is the recommended choice for new projects. It's a drop-in JUCE 8 module with a built-in, themeable activation UI and no third-party dependencies, and it ships in this same repository under `modules/moonbase_licensing/`. See the [JUCE Module](../juce/) page for the full guide. The **`OnlineUnlockStatus` bridge** below is the alternative for products already built on `juce::OnlineUnlockStatus`, teams still on JUCE 7, or anyone who would rather build their own activation UI against the core SDK. ### OnlineUnlockStatus bridge The SDK includes a copy-paste bridge under [`examples/juce/MoonbaseJuceBridge.h`](https://github.com/Moonbase-sh/moonbase-cpp/blob/main/examples/juce/MoonbaseJuceBridge.h). It provides: * `moonbase::juce_bridge::MoonbaseUnlockStatus` — subclass of `juce::OnlineUnlockStatus` driven by Moonbase's JWT flow. * `MoonbaseJuceDeviceIdResolver`: sources the device ID from `juce::SystemStats::getUniqueDeviceID()` instead of the spec fingerprint, for products that already bound licenses to it. * `applyJuceMetadata(options)` — populates activation metadata from JUCE's system and host helpers (DAW, plug-in format, OS, CPU, JUCE version). * `tryLoadStoredLicenseAsync(callback)` — non-blocking startup validation. State mutation and the callback are always marshalled to the JUCE message thread, so it's safe to call from `AudioProcessor`'s constructor. * `revokeActivationAsync(callback)`: releases the device's activation seat server-side, for a **Deactivate** or **Sign out** control. A runnable standalone example ships with the SDK and is opt-in to avoid pulling JUCE into your build by default: ```bash cmake -B build -DMOONBASE_BUILD_JUCE_EXAMPLE=ON cmake --build build --target MoonbaseJuceExample ``` ## Reference implementation: HALO by Corino For a complete, end-to-end example of the JUCE bridge wired into a real application, see the open-source **HALO** reference app. It's a JUCE 8 standalone built to look like a harmonic saturator plug-in — transfer curve, drive knob, animated I/O meters — but it doesn't process audio. The whole point of the project is the surrounding **license-gate workflow**: synchronous local JWT check on launch so the UI unlocks immediately, async online re-validation against the Corino demo account on a background thread, and a cog-menu **Sign out** that calls `revokeActivationAsync` to release the seat server-side. Screenshot of the HALO activation screen HALO demonstrates the full set of bridge features in a shippable shape — `tryLoadStoredLicenseAsync` on startup, the browser activation handshake, file-backed license storage under `~/Library/Application Support/Corino/HALO/`, and `revokeActivationAsync` wired to a UI control. It also includes the macOS + Windows CI and release pipeline that publishes signed builds straight to the Moonbase account as product downloads. For the equivalent reference on the module path, see **DRIFT** on the [JUCE Module](../juce/) page. ## Releases and source The SDK is MIT-licensed and follows [Conventional Commits](https://www.conventionalcommits.org/) — every push to `main` is released automatically by [semantic-release](https://semantic-release.gitbook.io/), with versioned GitHub Releases and source tarballs at `https://github.com/Moonbase-sh/moonbase-cpp/archive/refs/tags/v.tar.gz`. --- # https://moonbase.sh/docs/licensing/sdks/dotnet/ title: '.NET Licensing SDK', description: 'On this page, you can learn how to use the .NET SDK to integrate Moonbase licensing into your .NET apps.', } # .NET SDK This guide will get you all set up with our .NET SDK to integrate the licensing part of Moonbase into your .NET based apps. {{ className: 'lead' }} ## Getting started Start by adding the [NuGet package](https://www.nuget.org/packages/Moonbase.Net) to your project: ```bash dotnet package add Moonbase.Net ``` If you haven't already, create a product in the Moonbase app, and check out the **Implementation guide** to get the relevant cryptography keys, endpoints and configuration. An example configuration could look like this: ```csharp var licensing = LicensingFactory.CreateInstance( opts => { opts.Endpoint = new Uri("https://demo.moonbase.sh"); opts.ProductId = "demo-app"; opts.PublicKey = @" -----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAutOqeUiPMgYjAwQ53CyKhJSqojr2bejce0CshQi9Hd8mNZbkoROx oS56eIzehFSlX4YwHnF47AR1+fPOe7Q33Cgzd6d9xqksiMH7sWK2mADIlB66vZdW uk3Me0UMB22Biy1RQbSRMivu79MxCofsympoL/5CFjJLd1u37kxjuRWVLjJS84Rr 3L2W7R7Exnno/giC+L/Dv711mjgstmtlAQm5ZINvFvoLA1eFTDs6nlCs3dpJSiq3 fsBUMT9FtudzS5As54jeT/8MB66fJJ0A1LQ/v5CW8ACQYseFSIoOKErD3xU7QLIJ ERUn++6CVMPvZo67jVbTY+GCXYfW4gGVZQIDAQAB -----END RSA PUBLIC KEY-----"; // Optionally adjust the license store with path // to where the license should be stored. opts.LicenseStore = new FileLicenseStore( new FileLicenseStore.Options { FullPath = Path.Combine( Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "license.mb"), }); }); ``` {/* If you have custom domains enabled for your customer portal, use that instead of the moonbase.sh domain. */} This snippet sets up an instance of the licensing client where you will find all the features described below. The `licensing` instance will come with four main features: 1. `ILicenseStore` that handles license token persistence, either to file or in-memory 1. `ILicenseClient` which is a HTTP client built around the Moonbase licensing API 1. `ILicenseValidator` that can validate license tokens with the given public key 1. `IDeviceIdResolver` which by default generates unique device fingerprints using a number of factors Like described in [activation flows](../../activation-flows), multiple activation flows are possible through Moonbase, and the .NET SDK supports them all. Let's take a look at how each would be implemented using the SDK. ## Browser based activations Using browser based activation is the recommended flow, and the easiest to get going with. To start, request an activation using the SDK: ```csharp var activationRequest = await licensing.Client.RequestActivation(); ``` This `activationRequest` contains a `browser` URL that the customer can use to fulfill the request, so let's open a browser: ```csharp Process.Start(new ProcessStartInfo(activationRequest.Browser.ToString()) { UseShellExecute = true }); ``` While the customer is activating the license or requesting a trial, we can poll for completion: ```csharp License? activation = null; do { await Task.Delay(5000); activation = await licensing.Client.GetRequestedActivation(activationRequest); } while (activation == null); ``` As soon as the customer has fulfilled the request, a `License` will be ready for us. The Moonbase SDK will ensure any `License` coming through the API client contains a valid signature, and matches the current device automatically. Keep in mind that this license activation might be a full license, or a time-scoped trial. To make sure the customer doesn't have to keep doing this, it's best to persist the license to disk: ```csharp await licensing.Store.StoreLocalLicense(activation); ``` That way you can easily add a check when your app starts, to see if you can skip product activation entirely: ```csharp var localLicense = await licensing.Store.LoadLocalLicense(); var validatedLicense = await licensing.Client.ValidateLicense(localLicense); // License has been re-validated, store updated license for next check await licensing.Store.StoreLocalLicense(validatedLicense); ``` --- ## In-App activations If you prefer to not redirect customers to the browser, it's perfectly possible to keep the activation flow contained to the app you're building. For this you need to build your own UI interface to let the customer choose what they want to accomplish. ### Starting a trial If the user wishes to trial the product, and you have trials enabled in the Moonbase merchant app, then it's as simple as requesting a trial: ```csharp var trialLicense = await licensing.Client.RequestTrial(); // Also persist to store so we don't have to repeat this at next startup await licensing.Store.StoreLocalLicense(trialLicense); ``` In the case that you have restricted trials to registered customers only, you need to sign the customer in, or sign them up first: ### Authenticating customers Signing in existing customers requires you to build a form to collect email address and password, and authenticate those: ```csharp var user = await licensing.Client.SignIn(email, password); ``` In the case the customer doesn't already have an account, you can also make them one using the SDK: ```csharp var user = await licensing.Client.Register(name, email, password); ``` By authenticating using either of these methods, the SDK will keep the authenticated credentials until the app stops. That way you are free to call authenticated methods like the above `.RequestTrial()` as the signed in customer. ### Activating an owned license This path requires an authenticated user, so make sure you've followed the above steps first. To request activation of an owned license, simply call the appropriate method: ```csharp var license = await licensing.Client.RequestLicense(); // Also persist to store so we don't have to repeat this at next startup await licensing.Store.StoreLocalLicense(license); ``` ### Check for existing license on startup As with the browser based activation flow, after you're received a license from the API, you should persist it to the license store. Whenever the app starts up again, be sure to check for existing licenses to avoid having to re-activate the product: ```csharp var localLicense = await licensing.Store.LoadLocalLicense(); // Validate the license with the online API to ensure it has not been revoked: var validatedLicense = await licensing.Client.ValidateLicense(localLicense); // Optionally, you may perform a local-only license check to ensure // device ID and signature match the product, without being online: // var validatedLicense = await licensing.Validator.ValidateLicense(localLicense.Token); // License has been re-validated, store updated license for next check: await licensing.Store.StoreLocalLicense(validatedLicense); ``` --- ## Offline activations You might have customers that need to be able to activate devices without connection to the internet. To facilitate this, Moonbase signs all license tokens we issue with the unique signature of the device being activated. Since offline devices cannot transmit this device signature over the internet, the app needs to generate a device token. This device token contains the necessary information to generate a valid license token for offline activations, and can easily be exchanged for a license by the customer in the customer portal. To start, generate a device token: ```csharp var bytes = await licensing.GenerateDeviceToken(); var path = Path.Combine( Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "device.dt"); await File.WriteAllBytesAsync(path, bytes); ``` The snippet above generates the token, and then writes the result to a `device.dt` file. You can change the file name, but keep the extension to ensure compatability with the customer portal interface. It's up to you to provide the necessary instructions to the customer for how to upload the device token to your customer portal. What customers receive when they upload the device token in the customer portal is a `license.mb` file. This file contains a valid offline activated license token that you can read in using the SDK: ```csharp var licenseBytes = await File.ReadAllBytesAsync(path); var license = await licensing.ReadRawLicense(licenseBytes); // License has been read, store license for next check await licensing.Store.StoreLocalLicense(validatedLicense); ``` Keep in mind that this license will have a `ActivationMethod` of `Offline`, which means trying to re-validate the license is not necessary. --- # https://moonbase.sh/docs/licensing/sdks/juce/ title: 'JUCE Module', description: 'Add Moonbase license activation, with a built-in activation UI, to any JUCE 8 app or plugin using the moonbase_licensing module.', } # JUCE Module `moonbase_licensing` is a drop-in JUCE module that adds Moonbase license activation, and a built-in activation UI, to any JUCE 8 app or plugin. Add the module, fill in three fields, show one component. {{ className: 'lead' }} It ships inside the open-source [`Moonbase-sh/moonbase-cpp`](https://github.com/Moonbase-sh/moonbase-cpp) repository (MIT licensed) at `modules/moonbase_licensing/`, and talks to the Moonbase licensing API natively. It is not a `juce::OnlineUnlockStatus` wrapper. Already shipping with our proprietary `moonbase_JUCEClient` module? It stays supported, and nothing you have released changes. This page covers the newer `moonbase_licensing` module, which is what we recommend for new projects. The proprietary module is also still available if you would rather use it, with source access granted under your Moonbase service agreement. Either way, reach out through the support channel or at [developers@moonbase.sh](mailto:developers@moonbase.sh). The module supports: * Browser-based activations and offline machine-file activations * Time-based trials, with a days-remaining screen and an included-features list * Automatic license re-validation with a configurable grace period * Server-side deactivation, so users can free up an activation seat * Cross-SDK device identity, so a license activated in a web or Electron app validates in your plugin * In-app update notifications with release notes and an installer download * A themeable UI covering every activation state, plus a headless controller if you would rather build your own Here is the first thing a customer sees when they open an unlicensed build, with your own product name, logo and accent colour: ![The welcome screen: a product name and logo, an "Activate Solstice" heading, an "Activate online" button, and a "No internet? Activate offline" link](/assets/docs/juce/welcome.webp) ## Requirements * JUCE 8.0.4 or newer * A C++17 compiler * macOS, Windows, Linux, iOS or Android The module has no third-party dependencies. Everything it needs comes from either JUCE or the operating system: | Concern | How | | --- | --- | | HTTP | `juce::WebInputStream`, so no CURL | | JSON | a bundled `nlohmann/json` single header | | RS256 token verification | OS-native: Security.framework (macOS, iOS), CNG/bcrypt (Windows), system libcrypto (Linux) | There is nothing to `brew`, `vcpkg` or `apt` install. The one platform caveat is that on Linux the module links the always-present system `libcrypto`. ## Installation Pull `moonbase-cpp` into your project, as a git submodule or through `FetchContent`, and point your build at the module folder. On Apple platforms the module compiles an Objective-C++ translation unit for its Security.framework crypto backend, so your CMake project needs the Objective-C languages enabled. ```cmake {{ title: 'Submodule' }} if(APPLE) enable_language(OBJC OBJCXX) endif() juce_add_module(external/moonbase-cpp/modules/moonbase_licensing) target_link_libraries(MyPlugin PRIVATE moonbase_licensing) target_compile_definitions(MyPlugin PRIVATE JUCE_USE_CURL=0) ``` ```cmake {{ title: 'FetchContent' }} if(APPLE) enable_language(OBJC OBJCXX) endif() include(FetchContent) FetchContent_Declare(moonbase_cpp GIT_REPOSITORY https://github.com/Moonbase-sh/moonbase-cpp.git GIT_TAG v4.0.0 SOURCE_SUBDIR modules) # populate only, skip the SDK's root CMake FetchContent_MakeAvailable(moonbase_cpp) juce_add_module(${moonbase_cpp_SOURCE_DIR}/modules/moonbase_licensing) target_link_libraries(MyPlugin PRIVATE moonbase_licensing) target_compile_definitions(MyPlugin PRIVATE JUCE_USE_CURL=0) ``` Two details are worth knowing about the `FetchContent` variant. `SOURCE_SUBDIR modules` points at a directory that has no `CMakeLists.txt`, so the repository is populated but the C++ SDK's root CMake is never added: that root exposes an interface target pulling in OpenSSL and CURL, and the module needs neither. `JUCE_USE_CURL=0` keeps JUCE on its own HTTP stack, which is the transport the module uses. For Projucer projects, use *Modules → Add a module → Add a module from a specified folder…* and select `modules/moonbase_licensing`. The bundled SDK headers and `nlohmann/json` resolve from the module's own search paths, so there is nothing else to configure. ## Configuration The module is configured in code through `ActivationConfig`. Only three fields are required, and you will find all of them in the **Implementation guide** for your product in the Moonbase app. ```cpp #include using namespace moonbase::juce_integration; ActivationConfig config; config.endpoint = "https://your-account.moonbase.sh"; config.productId = "your-product"; config.publicKey = embeddedPublicKeyPem; // your product's RSA public key ``` In a plugin build, `productName`, `manufacturerName` and `applicationVersion` fill themselves in from the `JucePlugin_Name`, `JucePlugin_Manufacturer` and `JucePlugin_VersionString` macros. Set them yourself in a plain app. Misconfiguration does not throw out of construction. A missing or malformed `endpoint`, `productId` or `publicKey` puts the component into its error state and reports the underlying reason through the diagnostics sink described below. ## Showing the UI Add `ActivationComponent` as a modal over your editor, which locks the plugin until it is activated: ```cpp auto activation = std::make_unique(config); activation->onClose = [this] { /* dismiss the modal */ }; activation->onActivationChanged = [this](bool isActivated) { /* enable or disable UI */ }; addAndMakeVisible(*activation); ``` Or pop it from a menu item as a standalone window: ```cpp ActivationDialog::show(config, [](bool wasActivated) { /* update UI */ }); ``` ### The screens `ActivationComponent` owns an `ActivationController`, a headless state machine that decides which of these to show. Transitions use JUCE 8's animation API, so screens cross-fade rather than snap. * **Welcome.** Activate online through the browser, or activate offline. * **Activating.** Opens the browser and polls for the fulfilled activation. A device chip shows the local fingerprint and platform, and Cancel aborts. * **Success.** An animated confirmation with a mini license card. * **Offline.** The two-step machine-file flow: save the request file, then load the response file, which is validated locally. * **Trial.** Days remaining, a progress bar, the included and excluded feature lists, and an unlock action that routes into online activation. * **Trial expired.** A locked screen offering unlock or offline activation. * **License details.** Who the license is issued to, the plan, activation type, expiry, a seat counter, and a deactivate action that releases the seat server-side. * **Update available.** Shown when the license reports a newer released version than the running build. ![The trial screen: a free-trial panel with days remaining, a progress bar, and an "Unlock full version" button](/assets/docs/juce/trial.webp) ![The license details screen: licensed-to name, email, plan, activation type, expiry, seat count, and a "Deactivate this device" button](/assets/docs/juce/license.webp) Network calls run on a controller-owned thread pool, while every state change and repaint happens on the message thread. Destroying the controller cancels any in-flight request and joins its workers, so you can call `start()` straight from an editor constructor and destroy the editor at any point, including during plugin scanning or rapid open and close, without guarding it. ## Gating your plugin In a plugin, license state has to outlive the editor. Give the **processor** the controller and let the **editor** share it, rather than re-syncing two copies: ```cpp // In your AudioProcessor: ActivationController activation { makeConfig() }; // persistent // activation.start(); // load any stored license // In createEditor(): share the processor's controller with the UI. auto* editor = new ActivationComponent (processor.activation); // non-owning overload ``` Gate the audio thread on the lock-free flag: ```cpp void processBlock (juce::AudioBuffer& buffer, ...) override { if (! activation.licensedFlag().load()) buffer.clear(); } ``` Or use `LicenseGate` for a click-free fade when the license state changes. The module never silences audio itself, so the gating stays yours: ```cpp LicenseGate gate; // a member of your processor void prepareToPlay (double sr, int) override { gate.prepare (sr); gate.reset (activation.licensedFlag().load()); } void processBlock (juce::AudioBuffer& b, ...) override { gate.process (b.getArrayOfWritePointers(), b.getNumChannels(), b.getNumSamples(), activation.licensedFlag().load()); } ``` For richer decisions, `controller().license()` is the full `moonbase::license`, including `trial`, `expires_at`, `issued_to.email`, `owned_sub_product_ids` and any custom `properties`. Read it on the message thread. `ActivationController` is a `juce::ChangeBroadcaster`, so observing it means reading `screen()` and `license()` on change and repainting. The validated license is persisted to `userApplicationDataDirectory///license.mb`, which you can override with `config.licenseFile`. ## Offline activations The offline flow is built into the Welcome and Offline screens, including drag-and-drop for the response file, so most integrations need no code at all. Set `config.enableOffline = false` to hide it. If you are driving the controller from your own UI, the flow is three calls: `saveOfflineRequest()` writes the device token to a file the customer uploads, `setOfflineResponse()` takes the file they get back, and `activateOffline()` validates it locally and persists it. Offline licenses are permanent and are never re-validated against the API. See [Offline activations](/docs/licensing/offline-activations/) for the customer-facing side of the flow. ## Branding and theming Everything in `ActivationConfig` after the connection fields is presentation: the product and manufacturer names, an `accent` colour, a logo `Drawable`, overridable copy through `config.strings`, the `trialLengthDays` and `trialFeatures` list shown on the trial screens, the activation URL, and `showMoonbaseBadge` for the Moonbase co-brand in the footer. ```cpp config.accent = juce::Colour(0xff186cdc); config.logo = juce::Drawable::createFromImageData(...); config.trialLengthDays = 14; config.showMoonbaseBadge = false; ``` For a deeper re-skin, mutate `ActivationLookAndFeel::palette`, where every colour in the UI is an individual token, and point the `heading`, `body` and `mono` font helpers at your own typefaces. ## In-app updates A validated license carries the product's current released version in its claims. On launch and after every re-validation, the controller compares that against the running version and, when a newer release exists, shows the update screen instead of the license or trial screen. It loads the release notes from the Moonbase inventory API and downloads the installer for the current platform in-app, with progress. ![The update available screen: an "Update available" pill, a "Solstice 1.0.0 is ready" heading, a "What's new" changelog card, a Download button, and a "Skip this update" link](/assets/docs/juce/update.webp) ```cpp config.applicationVersion = "2.3.1"; // or rely on JucePlugin_VersionString config.enableUpdatePrompt = true; // default, set false to never prompt config.autoPresentUpdate = true; // default, present it when the plugin opens config.downloadDirectory = {}; // default, the user's Downloads folder ``` Downloads respect the release access-control level you set on the product. A trial cannot download from an owners-only release, so in that case the screen swaps the download button for an unlock call to action instead of letting the user hit a permission error. "Skip this update" is remembered until a newer version ships, and the license screen keeps a clickable **Update available** badge for anyone who dismissed it. ## Device identity By default the module identifies the device with `moonbase::moonbase_device_id_resolver`, which implements the cross-SDK Moonbase device fingerprint spec (version 2, ids stamped `mbd2_`). A license activated in a web or Electron app built on `@moonbase.sh/licensing` validates in your plugin, and the other way round. Nothing is shelled out to and no privileged file is read, so the same id comes back inside a sandboxed host and whether or not the process is elevated. Inspect what it resolved to with `controller().describeDevice()`, which returns the id, spec version, platform tag, and the *names* of the contributing parameters but never their values. That is safe to put behind a "Copy diagnostics" button. On iOS and Android there is no identifier that unrelated apps can read, so those platforms get a *scoped* id stamped `mbd2s_`, derived from `identifierForVendor` and `ANDROID_ID`. It is stable for the device within the platform's own scope, but it is deliberately not cross-SDK, and `config.allowDeviceNameFallback` is forbidden there rather than merely ignored. Elsewhere, a machine with no usable hardware identity (a cloned VM image, a minimal container) fails activation rather than binding something weak. Set `config.allowDeviceNameFallback = true` to accept a weaker id derived from the computer name instead; those are stamped `mbd2n_` so they can be told apart from real hardware bindings. Supply your own resolver with `config.deviceIdResolver` when you need an application-specific device ID. **Upgrading a plugin that already shipped on 3.x.** The device id changed in 4.0.0. Earlier versions used `juce::SystemStats::getUniqueDeviceID()`, so every already-activated user's license is bound to that value and will fail to validate, locking them out until they re-activate, which consumes a fresh activation seat and resets any device-scoped trial. One line avoids it: ```cpp config.deviceIdResolver = std::make_shared( // Current resolver, binds new activations. Pass the platform default, NOT // moonbase_device_id_resolver directly: on iOS and Android that has no // identity to read and throws, and a migrating resolver asks its current // resolver for an id before consulting any historical one, so hard-coding // it locks mobile users out of validation and activation. ActivationConfig::defaultDeviceIdResolver(), // Historical resolver, still accepted when validating existing licenses. std::make_shared()); ``` New activations bind the spec id, existing licenses keep validating, and the fleet migrates as devices naturally re-activate. Drop the wrapper in a later release. Because `getUniqueDeviceID()` is JUCE's own derivation rather than a published format, the historical resolver only vouches for a binding if the plugin still ships the JUCE version that created it, so do not combine this upgrade with a JUCE major bump. ## Re-validation and entitlements The module never polls on a timer. It validates on launch, and again whenever you ask it to, throttled to no more than once per `onlineCheckInterval`. A license stays usable offline until `onlineGracePeriod` elapses since its last successful online validation. ```cpp config.onlineCheckInterval = std::chrono::hours (1); // default 5 minutes config.onlineGracePeriod = std::chrono::hours (24 * 30); // default 7 days config.httpConnectTimeout = std::chrono::seconds (5); config.httpRequestTimeout = std::chrono::seconds (15); ``` When a user buys something mid-session, an add-on or an upgrade, re-validate so the new entitlements load without a restart: ```cpp activation->controller().refreshLicense (/* force */ true, [] (bool refreshed) { if (refreshed) reloadFeatures(); // read controller().license() again }); ``` This runs asynchronously and silently, with no screen change. `force` bypasses the throttle, which is what you want straight after a purchase; pass `false` for a polite background re-check. A network failure is non-fatal: the current license is kept and the reason goes to the diagnostics sink. ## Diagnostics and telemetry The UI shows friendly, end-user-facing copy. To see the underlying reason behind a failure, whether that is bad configuration, a rejected token, an unreachable server or a failed write, wire up a diagnostic sink. It is invoked on the message thread. ```cpp config.onDiagnostic = [] (const juce::String& message) { juce::Logger::writeToLog ("[activation] " + message); }; ``` Telemetry is off by default. One flag attaches JUCE system and host metadata to every activation and validation request, and you can add fields of your own: ```cpp config.analytics.enabled = true; // OS, CPU, JUCE version, memory config.analytics.includeHostInfo = true; // DAW host and plugin format config.analytics.includeLocaleInfo = true; // language and region config.metadata["app.channel"] = "beta"; config.onCollectMetadata = [] (std::map& m) { m["cohort"] = abTestCohort(); }; ``` Host and plugin fields are only captured when `juce_audio_processors` is part of the build, so they light up automatically in a plugin and are skipped in a plain app. ## Sample app and reference plugin A runnable standalone sample lives in the repository at `examples/juce-native/` and runs against the public Moonbase demo account. It fetches JUCE on first configure and adds the module with `juce_add_module`, exactly the way a downstream project consumes it: ```bash cmake -B build -DMOONBASE_BUILD_JUCE_NATIVE_EXAMPLE=ON cmake --build build --target MoonbaseActivationNative ``` For a complete plugin, **DRIFT** is an open-source JUCE 8 audio plugin built around the module, with the full CMake setup, the processor-owned controller, and macOS and Windows release pipelines. The [full module reference](https://github.com/Moonbase-sh/moonbase-cpp/blob/main/docs/juce-module.md) lives alongside the source. ## The OnlineUnlockStatus bridge There is a second JUCE integration path, built on the same core SDK: a copy-paste reference header that wraps `juce::OnlineUnlockStatus`. It has no UI of its own, and it is the right choice if your product is already built on `OnlineUnlockStatus`, or you are still on JUCE 7. | | Native module | `OnlineUnlockStatus` bridge | | --- | --- | --- | | **Form** | Drop-in JUCE module | Copy-paste reference header | | **Built-in UI** | Yes, themeable and animated | No, you build it | | **JUCE integration** | Native Moonbase API | `juce::OnlineUnlockStatus` wrapper | | **JUCE version** | 8.0.4+ | 7+ | | **Device fingerprint** | Spec v2, cross-SDK | Spec v2, cross-SDK | | **Third-party deps** | None | Inherits the core SDK's CURL and OpenSSL | | **Entry point** | `ActivationComponent` / `ActivationDialog` | `MoonbaseUnlockStatus` | | **Best for** | New plugins wanting a ready-made UI | Products already on `OnlineUnlockStatus`, or JUCE 7 | See the [C++ SDK](../cpp/) page for the bridge, and for using Moonbase licensing from non-JUCE C++ applications. --- # https://moonbase.sh/docs/licensing/sdks/node/ title: 'Node.js Licensing SDK', description: 'On this page, you can learn how to use the Node.js SDK to integrate Moonbase licensing into your node-based apps.', } # Node.js SDK This guide will get you all set up with our Node.js SDK to integrate the licensing part of Moonbase into your node-based apps. {{ className: 'lead' }} Using this SDK is great if you are building Electron apps or other app running on devices directly. It is not suitable for web-apps, since we need to fingerprint the underlying device, and we are currently relying on system information to do that. Check out our simple Electron sample app for an example of how this integration can work: ## Getting started Start by adding the [npm package](https://www.npmjs.com/package/@moonbase.sh/licensing) to your project: ```bash {{ title: 'npm' }} npm install @moonbase.sh/licensing --save ``` ```bash {{ title: 'yarn' }} yarn add @moonbase.sh/licensing ``` If you haven't already, create a product in the Moonbase app, and check out the **Implementation guide** to get the relevant cryptography keys, endpoints and configuration. An example configuration could look like this: ```typescript const licensing = new MoonbaseLicensing({ productId: 'demo-app', endpoint: 'https://demo.moonbase.sh', publicKey: `-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAutOqeUiPMgYjAwQ53CyKhJSqojr2bejce0CshQi9Hd8mNZbkoROx oS56eIzehFSlX4YwHnF47AR1+fPOe7Q33Cgzd6d9xqksiMH7sWK2mADIlB66vZdW uk3Me0UMB22Biy1RQbSRMivu79MxCofsympoL/5CFjJLd1u37kxjuRWVLjJS84Rr 3L2W7R7Exnno/giC+L/Dv711mjgstmtlAQm5ZINvFvoLA1eFTDs6nlCs3dpJSiq3 fsBUMT9FtudzS5As54jeT/8MB66fJJ0A1LQ/v5CW8ACQYseFSIoOKErD3xU7QLIJ ERUn++6CVMPvZo67jVbTY+GCXYfW4gGVZQIDAQAB -----END RSA PUBLIC KEY-----`, // Optionally adjust the license store with path // to where the license should be stored, or use // alternate storage mechanisms to persist the token. licenseStore: new FileLicenseStore(), }) ``` {/* If you have custom domains enabled for your customer portal, use that instead of the moonbase.sh domain. */} This snippet sets up an instance of the licensing client where you will find all the features described below. The `licensing` instance will come with four main features: 1. `ILicenseStore` that handles license token persistence, either to file or in-memory 1. `ILicenseClient` which is a HTTP client built around the Moonbase licensing API 1. `ILicenseValidator` that can validate license tokens with the given public key 1. `IDeviceIdResolver` which by default generates unique device fingerprints using a number of factors Like described in [activation flows](../../activation-flows), multiple activation flows are possible through Moonbase, and the Node.js SDK currently supports browser based activations and offline activations only. Let's take a look at how each would be implemented using the SDK. ## Browser based activations Using browser based activation is the recommended flow, and the easiest to get going with. To start, request an activation using the SDK: ```typescript const activationRequest = await licensing.client.requestActivation() ``` This `activationRequest` contains a `browser` URL that the customer can use to fulfill the request, so let's open a browser: ```typescript open(activationRequest.browser) ``` While the customer is activating the license or requesting a trial, we can poll for completion: ```typescript let license: License | null = null do { await new Promise((resolve) => setTimeout(() => resolve(void 0), 5000)) license = await licensing.client.getRequestedActivation(activationRequest) } while (license == null) ``` As soon as the customer has fulfilled the request, a `License` will be ready for us. The Moonbase SDK will ensure any `License` coming through the API client contains a valid signature, and matches the current device automatically. Keep in mind that this license activation might be a full license, or a time-scoped trial. To make sure the customer doesn't have to keep doing this, it's best to persist the license to disk: ```typescript await licensing.store.storeLocalLicense(license) ``` That way you can easily add a check when your app starts, to see if you can skip product activation entirely: ```typescript const localLicense = await licensing.store.loadLocalLicense(); const validatedLicense = await licensing.client.validateLicense(localLicense); // License has been re-validated, store updated license for next check await licensing.store.storeLocalLicense(validatedLicense); ``` Our Electron app sample has a more complete startup guard that might be helpful: [withLicensing](https://github.com/Moonbase-sh/electron-sample-app/blob/main/src/main.ts#L13-L39) --- ## Offline activations You might have customers that need to be able to activate devices without connection to the internet. To facilitate this, Moonbase signs all license tokens we issue with the unique signature of the device being activated. Since offline devices cannot transmit this device signature over the internet, the app needs to generate a device token. This device token contains the necessary information to generate a valid license token for offline activations, and can easily be exchanged for a license by the customer in the customer portal. To start, generate a device token: ```typescript const bytes = await licensing.generateDeviceToken() const tokenPath = path.join(path.resolve(), 'device.dt') await fs.writeFile(tokenPath, bytes) ``` The snippet above generates the token, and then writes the result to a `device.dt` file. You can change the file name, but keep the extension to ensure compatability with the customer portal interface. It's up to you to provide the necessary instructions to the customer for how to upload the device token to your customer portal. What customers receive when they upload the device token in the customer portal is a `license.mb` file. This file contains a valid offline activated license token that you can read in using the SDK: ```typescript const licenseBytes = await fs.readFile(path); const license = await licensing.readRawLicense(licenseBytes); // License has been read, store license for next check await licensing.store.storeLocalLicense(validatedLicense); ``` Keep in mind that this license will have a `ActivationMethod` of `Offline`, which means trying to re-validate the license is not necessary. --- # https://moonbase.sh/docs/licensing/sdks/ title: 'Licensing SDKs', description: 'On this page, you can learn how to use the Licensing SDKs to integrate Moonbase into your apps.', } --- # https://moonbase.sh/docs/licensing/sdks/rust/ title: 'Rust Licensing SDK', description: 'On this page, you can learn how to use the Rust SDKs to integrate Moonbase licensing into your Rust-based apps.', } # Community-maintained Rust crates Thanks to our community of developers, there are a number of Rust crates available that make it easy to integrate Moonbase licensing into your Rust-based applications. These crates provide functionality for license token validation, API interfaces, device fingerprinting and more, and are primarily maintained by the community. --- # https://moonbase.sh/docs/ title: "Moonbase documentation", description: "Find out exactly how to integrate with Moonbase through these docs.", }; { title: "Guides", id: "guides" }, { title: "Resources", id: "resources" }, ]; # Moonbase Developer Documentation Use our APIs to achieve the licensing and payments flow you want, either through our SDKs, or by integrating with our APIs directly. {{ className: 'lead' }} ![](/assets/docs/overview.png) For your apps to integrate with Moonbase, we have SDKs ready to use, but also well documented APIs for the cases where you want to build your own integration. The same is true for your storefront, where we have multiple patterns available to you. If you are new to the Moonbase platform, we recommend to start by learning about the core concepts powering the Moonbase features.
## Demo If you want to get a guided tour of the platform, and see how it works in practice, you can [book a demo](/demo) with our team, and we will walk you through the platform and answer any questions you might have. You can also check out the video below: