Skip to content

HTTP API

This page documents the HTTP surface an integrator can call directly. The SDKs make these calls for you, so most integrations never need it — until a request fails and you want to know exactly what came back. Base URLs are in Endpoints.

There are two credentials and two sets of paths, and each credential reaches only its own.

The account API key rides in the dub-apiKey header. It is your server-side identity as a seller, and it reaches the checkout operations and the management surfaces:

POST /licensing/checkout/embedded HTTP/1.1
Host: api.monaiq.com
Content-Type: application/json
dub-apiKey: <your API key>

A runtime token — the RuntimeToken inside a SIDUB_LIC_ credential — rides as a bearer and reaches the runtime paths under /licensing/runtime and /consumption/runtime. Those are the paths a distributed application calls:

POST /licensing/runtime/GenerateLicenseAuthorization HTTP/1.1
Host: api.monaiq.com
Content-Type: application/json
Authorization: Bearer <RuntimeToken>

The runtime paths take no dub-apiKey, no Ocp-Apim-Subscription-Key and no subscription at all; the gateway strips both key headers before the request reaches the backend. A runtime credential carries no account authority, and the account key reaches no runtime path.

Headers are the one documented way to present either value. Never put a credential in a URL: URLs land in access logs, proxies and browser history. Where each comes from, and the encoded SIDUB_LIC_ credential the SDKs use, are covered in Credentials and keys.

Every response carries an X-Correlation-Id header. Send your own x-correlation-id request header and the platform adopts your value, so a failing call stitches into your own traces; otherwise one is generated. The same value appears in the error body below — quote it when you raise a support request.

Every endpoint returns one JSON shape on failure. This is the product-wide contract — it applies across the platform’s HTTP surface, including the invoice and billing endpoints:

{
"Code": "ValidationError",
"Message": "RequestId, OfferingId, CorrelationId, and CustomerEmail are required.",
"CorrelationId": "0f8fad5bd9cb469fa16570867728950e",
"Details": [
{ "Code": "ValidationError", "Message": "CustomerEmail is required.", "Field": "CustomerEmail" }
]
}
  • Code is machine-readable and PascalCase. Branch on it, never on Message.
  • Message is for humans; its wording may change without notice.
  • CorrelationId repeats the X-Correlation-Id header value.
  • Details is optional: per-field entries, present on validation failures.
Code HTTP status Meaning
ApiKeyMissing 401 No dub-apiKey header on the request.
ApiKeyInvalid 403 The key does not resolve to an account.
RuntimeTokenMissing 401 A runtime path was called with no bearer.
RuntimeTokenInvalid 401 The bearer did not validate — signature, issuer, audience or scope.
CredentialUnknown 403 The token is genuine, but the credential it names is not known to this license issuer.
CredentialRevoked 403 The credential was revoked. Issue a new one.
IssuerMismatch 403 The body asserts an issuer that is not the key’s account.
AuthorizationDenied 403 The license exists but authorization is refused — expired, or its billing plan is suspended or cancelled. Message carries the reason.
NotFound 404 Nothing here for that identifier.
InvalidPayload 400 The request body could not be read at all.
ValidationError 400 The body was read but a field fails validation; see Details.
CheckoutFailed 400 The checkout could not proceed; Message states why.
RequestIdInUse 409 The RequestId already belongs to a checkout for another buyer or seller.
ServerError 500 An unexpected platform failure. Retry later, and quote the correlation id if it persists.

A bearer the gateway itself refuses never reaches the backend, so it answers with the gateway’s own 401 and a WWW-Authenticate: Bearer realm="monaiq-runtime", scope="license.runtime" header rather than an ApiError body.

POST /licensing/checkout/embedded creates a checkout session so a customer can buy an offering without leaving your application. The seller-side story is in Deliver to your customers; this is the wire contract underneath it.

{
"RequestId": "5f2d0c44-0000-0000-0000-000000000000",
"OfferingId": "b3b1f6e0-0000-0000-0000-000000000000",
"CorrelationId": "order-8842",
"CustomerEmail": "buyer@example.com",
"SuccessUrl": "https://app.example.com/thanks",
"CancelUrl": "https://app.example.com/pricing"
}
Field Required or optional Meaning
OfferingId Required The offering being bought.
CorrelationId Required Your own reference for this checkout, up to 128 characters. It comes back with the completed result so you can match the sale to your side.
CustomerEmail Required The address the offer is for. It is validated for shape and never looked up, so an address with a Monaiq account and one without do identical work and return the same shape. Nothing is provisioned for it until the buyer is proven — by the claim on a free offering, by the payment on a paid one.
IssuerClientId Optional Your seller client id. The platform resolves the issuer from the API key, so you may omit this; supply it only as a cross-check, because a mismatch is refused with 403 IssuerMismatch.
SuccessUrl Optional Absolute HTTP(S) URL to send the buyer to after payment. A session_id query parameter is appended for you.
CancelUrl Optional Absolute HTTP(S) URL for a buyer who backs out.
RequestId Optional A GUID identifying this checkout attempt, generated for you when the field is absent. Send the same value on a retry and the existing checkout comes back instead of a second one. Sending the empty GUID explicitly is refused with 400 ValidationError.

A malformed CustomerEmail is 400 ValidationError. There is no 404 for an address without an account: that refusal is gone.

A successful create returns 200 with the session. A paid offering carries the Stripe URL:

{ "SessionId": "cs_a1B2...", "SessionUrl": "https://checkout.stripe.com/c/pay/cs_a1B2..." }

Redirect the buyer there to complete payment. The typed address is passed to Stripe as customer_email; the account is resolved or created when the payment settles, and the delivery email deep-links the buyer to their license page, where their first sign-in links the account.

A free or trial offering carries a claim URL instead, and nothing has been provisioned yet:

{ "SessionId": "free-9c1e...", "SessionUrl": null, "ClaimUrl": "https://monaiq.com/claim/..." }
Field Meaning
SessionId What you poll, on both paths.
SessionUrl Stripe Checkout URL; null on the free path.
ClaimUrl Where the buyer accepts the offer. Present only on the free typed-address path, absent everywhere else.

The same claim link is emailed to the address, subject “Accept your {product} license from {seller}”, and it is good for seven days. Show your copy of it beside whatever “check your email” message your buyer is looking at. The license is created when the buyer opens the link, signs in and presses Accept — not before.

A repeated create with the same RequestId returns the existing session; on the free path that includes a freshly minted ClaimUrl, and no second email is sent. A RequestId that belongs to a different buyer or seller is 409 RequestIdInUse.

Claim notices are capped at five per address per hour across all sellers, and one open intent is kept per seller, offering and address. Over the cap the checkout still succeeds and the link is still returned — only the email is withheld.

POST /licensing/checkout/session/result with the session id:

{ "SessionId": "cs_a1B2..." }

A known session always answers 200, and Status tells you where it stands:

{
"Status": "completed",
"CorrelationId": "order-8842",
"EncodedCredential": "SIDUB_LIC_...",
"LicenseId": "c9d2a7e1-0000-0000-0000-000000000000",
"OfferingId": "b3b1f6e0-0000-0000-0000-000000000000"
}
Status Meaning
pending Nothing terminal has happened yet: payment has not settled, or the buyer has not accepted the claim. Poll again; the response carries a Retry-After: 3 header (seconds).
completed The license exists. EncodedCredential is the buyer’s credential — treat it as the secret it is.
failed The checkout did not complete; an Error field carries the reason. A claim whose seven days lapsed reads "Claim expired."

The status vocabulary is exactly these three values and does not change with the claim path.

An unknown or expired session id answers 404 NotFound.

POST /licensing/runtime/GenerateLicenseAuthorization is the call behind the SDKs’ getAuthorization / GetAuthorization. It takes the runtime token as a bearer, and the body names the license:

{
"RequestId": "5f2d0c44-0000-0000-0000-000000000000",
"LicenseId": "c9d2a7e1-0000-0000-0000-000000000000"
}

A successful call returns 200 with the signed authorization — authorization id, license id, classification (numeric on the wire: 0 trial, 1 subscription, 2 perpetual), issue and expiry dates, and the feature list — whose signature the SDKs verify before trusting it. An enforcement denial answers 403 AuthorizationDenied with the reason in Message.

The body’s LicenseId must be the license the token names, or the answer is 404 NotFound (never a 403, which would confirm the id exists elsewhere). A credential authorizes its own license and no other.

POST /consumption/runtime/messages is the ingestion endpoint for metered usage. It takes the runtime token as a bearer. Both SDKs batch and send consumption for you once ConsumptionServiceUri / consumptionServiceUri is configured. It is not designed for hand-rolled calls; Consumption and metering covers the model.

The gateway binds the batch to the token: a batch whose LicenseId is not the license the credential names is refused 400 before it reaches ingestion, and the credential id is stamped onto the batch so usage reported under a revoked credential is dropped rather than billed.

Monaiq issues the runtime tokens, so it publishes its own discovery documents. Both are anonymous and both are public by construction — an issuer string, a URL, and the elliptic-curve public points:

  • GET /licensing/runtime/.well-known/openid-configuration
  • GET /licensing/runtime/.well-known/jwks

Signing key versions are additive and never disabled, so the JWKS serves every version that has ever signed. You do not need either document to integrate — the SDKs never fetch them — but they are there for a proxy or a gateway of your own that wants to verify a token itself. The per-environment URLs are in Endpoints.

Per license, keyed on the license the token names:

Path Limit
POST /licensing/runtime/GenerateLicenseAuthorization 300 requests per minute
POST /consumption/runtime/messages 1200 requests per minute

A coarser per-IP limit runs ahead of token validation as a volume guard.