NewRich
License API
Marketplace

License API

Implement redemption-code licensing so NewRich App Store can fulfill marketplace purchases, including recurring subscriptions and account-management links.

License API

This page defines the redemption-code licensing API your app must expose so App Store can fulfill purchases. Implement these endpoints so customers receive access without NewRich storing pre-generated passwords.

API version: v1
Base path: /api/v1
Content type: application/json
Authentication: Caller domain allowlist + X-Licensing-Api-Key (see below)

Implement the request/response shapes, authentication rules, and idempotency behavior defined on this page. App Store sends both the license API key and caller-domain headers — your endpoints must validate both before processing the body.

Endpoints App Store calls

App Store stores the full versioned API root per product (for example https://dashboard.yourapp.com/api/v1) and appends resource paths:

App Store actionHTTP call
Purchase fulfilledPOST {license_api_url}/redemption_codes
Paid subscription period extendedPOST {license_api_url}/redemption_codes/renew
Refund / cancel / past duePOST {license_api_url}/redemption_codes/revoke
Access restored after revokePOST {license_api_url}/redemption_codes/reactivate
Backfill account-management linkPOST {license_api_url}/redemption_codes/sync_management

One-time vs recurring offerings

Each marketplace app is either all one-time or all subscription. Recurring plans on the same app may still use different cadences (monthly, quarterly, 6 months, annual).

App Store tells your API which model applies on every licensing POST via additive fields:

FieldOne-timeSubscription
access_duration"lifetime""subscription"
billing_intervalnull"monthly" | "quarterly" | "biannual" | "annual"
term"lifetime"Same value as billing_interval

Treat these fields as required going forward. Older integrations that only read email / order_id / product_sku continue to work for issue, but you must accept the recurrence fields to support subscription offerings correctly.

Authenticate App Store requests

Apply both checks below on all five licensing endpoints before processing the request body.

Private credentials. NewRich provides the license API key through a secure channel during marketplace onboarding. Store it in server configuration only — never in source control, client-side code, or public docs.

License API key

App Store sends a shared secret on every request. Your API must reject requests with a missing or incorrect key.

Request header: X-Licensing-Api-Key

Your configuration:

LICENSING_API_KEY=<provided privately by NewRich>

Compare the header value to your configured key using a constant-time comparison.

HTTPmsgWhen
401License API key required.Header absent or empty
403License API key is invalid.Header present but does not match
503Licensing API is not configured.LICENSING_API_KEY is not set

Caller domain allowlist

Your API must also reject requests that do not come from an authorized App Store hostname.

Your configuration:

LICENSING_ALLOWED_DOMAINS=apps.newrich.com

App Store production requests identify themselves with hostname apps.newrich.com.

Resolve the caller hostname

Read the caller in this order (first match wins):

  1. X-Licensing-Caller-Domain — preferred for server-to-server calls from App Store
  2. Origin — hostname extracted from the URL
  3. Referer — hostname extracted from the URL

Expect a hostname only (apps.newrich.com), not a full URL. Normalize case and strip ports when comparing.

Reject invalid callers

HTTPmsgWhen
401Caller domain required. Send X-Licensing-Caller-Domain, Origin, or Referer.No resolvable caller hostname
403Caller domain is not authorized.Hostname not in allowlist

Response envelope

Use the same JSON shape on all licensing endpoints.

Success:

{
  "success": true,
  "msg": "ok",
  "data": { }
}

Application error:

{
  "success": false,
  "msg": "Human-readable error message"
}

Validation error (HTTP 422):

{
  "message": "The email field is required.",
  "errors": {
    "email": ["The email field is required."]
  }
}

Shared request fields

App Store sends this identity + recurrence body on generate, renew, revoke, reactivate, and sync_management. Lookup keys are email + order_id (plus optional product_sku). Do not switch identity to nr_order_uuid.

FieldRequiredTypeNotes
emailyesstring (email)Billing email from checkout (lowercased and trimmed). Must match at customer registration.
order_idyesstringApp Store correlation id NR-{software_order_id}-{software_order_item_id} — unique per line item; idempotency / lookup key for generate. Not the public marketplace order number and not valid in management URLs.
product_skunostring | nullPlan SKU for audit / tier mapping; may be null
access_durationyesstring"lifetime" or "subscription"
billing_intervalyesstring | nullFor subscriptions: "monthly", "quarterly", "biannual", or "annual". null for lifetime
termyesstringCatalog term: "lifetime", "monthly", "quarterly", "biannual", or "annual" (matches billing_interval when subscription)
nr_order_uuidno*string (UUID)Marketplace order number shown on the NewRich receipt. Present when App Store can resolve the marketplace order. *sync_management always includes it.
manage_urlno*string (URL)Deep link to that order’s management / receipt screen. Prefer storing and using this as-is. *sync_management always includes it.
reasonnostringPresent on some lifecycle calls (for example "refunded", "cancelled", "past_due", "paid")
period_ends_atnostringPresent on renew when App Store knows the new period end (ISO-8601 timestamp)
On generate / renew / revoke / reactivate, nr_order_uuid and manage_url are additive. Older APIs that ignore unknown JSON keys keep working. Do not require these fields in your request validation for those routes — App Store omits them when it cannot resolve the order, and synthetic verification curls may omit them too. Treat a missing nr_order_uuid as “no management link yet,” not an error.For sync_management, App Store only calls your API when it has a marketplace order UUID, so expect both fields on that route and persist them.

For NewRich-billed subscriptions, your app’s account settings should send buyers to NewRich to cancel, resume, or update payment — not a second subscription manager inside your product.

  1. On generate, persist nr_order_uuid and manage_url with the issued license / order row.
  2. On later renew / revoke / reactivate / sync_management calls, refresh manage_url when App Store sends it.
  3. In account settings (or billing), render a Manage subscription link that opens NewRich.

Preferred: use the stored manage_url value exactly as App Store sent it.

Fallback if you only stored nr_order_uuid:

{NEWRICH_WEB_ORIGIN}/marketplace/orders/apps/{nr_order_uuid}

Examples:

  • Production: https://newrich.com/marketplace/orders/apps/{nr_order_uuid}
  • Relative (if origin is unknown): /marketplace/orders/apps/{nr_order_uuid}

App Store builds manage_url from its configured NewRich web origin. When that origin is missing, manage_url may already be the relative path above — store and use it rather than hardcoding a host.

Do not build /marketplace/orders/apps/{order_id} with the license correlation id (for example NR-42-17). Only the UUID in nr_order_uuid belongs in that path. On the NewRich order page, that UUID is labeled Order number; the numeric App Store id labeled Order ID is not for this link.

POST /redemption_codes — Generate

Issue an email-bound redemption code after purchase.

Request body (one-time)

POST /api/v1/redemption_codes HTTP/1.1
Host: dashboard.yourapp.com
Content-Type: application/json
X-Licensing-Caller-Domain: apps.newrich.com
X-Licensing-Api-Key: <your-license-api-key>

{
  "email": "[email protected]",
  "order_id": "NR-42-17",
  "product_sku": "yourapp-pro",
  "access_duration": "lifetime",
  "billing_interval": null,
  "term": "lifetime",
  "nr_order_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "manage_url": "https://newrich.com/marketplace/orders/apps/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
}

Request body (subscription)

{
  "email": "[email protected]",
  "order_id": "NR-42-17",
  "product_sku": "yourapp-pro",
  "access_duration": "subscription",
  "billing_interval": "monthly",
  "term": "monthly",
  "nr_order_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "manage_url": "https://newrich.com/marketplace/orders/apps/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
}

Minimum success response (HTTP 2xx):

{
  "success": true,
  "data": {
    "code": "YOUR-ABCD-EFGH-IJKL"
  }
}

App Store requires HTTP 2xx, "success": true (boolean), and a non-empty string data.code. A fuller response is fine:

{
  "success": true,
  "msg": "ok",
  "data": {
    "code": "YOUR-ABCD-EFGH-IJKL",
    "email": "[email protected]",
    "order_id": "NR-42-17",
    "status": "pending",
    "current_upgrade": "Pro"
  }
}

Behavior

  • Idempotent on order_id: duplicate generate calls return the same code (HTTP 200).
  • Revoked order: if the order was revoked, generate returns HTTP 409 with msg: "Order has been revoked."
  • Code format: your choice; reference apps use a prefixed alphanumeric format.
  • status: start as pending until the customer registers.
  • Recurring: store access_duration, billing_interval, and term with the issued code so renew / revoke can apply the correct access rules.
  • Account management: when present, persist nr_order_uuid and manage_url with the issued code so your app can deep-link to NewRich subscription management.
If your API errors or times out (default 15 seconds), the order still completes but the customer may not receive a redemption code. Monitor your endpoint and logs closely after launch.

POST /redemption_codes/renew — Renew

Extend access after a paid subscription period. App Store calls this for recurring offerings when a period is renewed.

Request body

{
  "email": "[email protected]",
  "order_id": "NR-42-17",
  "product_sku": "yourapp-pro",
  "access_duration": "subscription",
  "billing_interval": "monthly",
  "term": "monthly",
  "nr_order_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "manage_url": "https://newrich.com/marketplace/orders/apps/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "period_ends_at": "2026-10-04T00:00:00Z"
}
FieldNotes
Shared identity + recurrence fieldsSame as generate
nr_order_uuid / manage_urlPresent when App Store knows the marketplace order; refresh stored values if sent
period_ends_atOptional; when present, treat it as the new access period end

Success: HTTP 2xx with "success": true. data.code is not required.

Behavior

  • Locate the license by order_id (and email as needed).
  • Extend or refresh the customer’s access for another billing period.
  • If period_ends_at is present, persist it as the authoritative period end.
  • Idempotent for the same renewal window when practical (safe to retry).

POST /redemption_codes/revoke — Revoke

Disable a code or linked user account (refunds, cancellations, past-due subscriptions).

Request body

{
  "email": "[email protected]",
  "order_id": "NR-42-17",
  "product_sku": "yourapp-pro",
  "access_duration": "subscription",
  "billing_interval": "monthly",
  "term": "monthly",
  "nr_order_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "manage_url": "https://newrich.com/marketplace/orders/apps/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "reason": "refunded"
}

App Store always sends the shared identity + recurrence fields. nr_order_uuid / manage_url appear when the marketplace order is known. reason is included when available (for example "refunded", "cancelled", "past_due").

For local tooling you may also accept { "code": "YOUR-ABCD-EFGH-IJKL" } (code case-insensitive), but production App Store calls use email + order_id.

Success (200) — "success": true. Optional data matching generate is fine.

Code stateEffect
pendingStatus → revoked; registration blocked
redeemedLinked user disabled / access removed; code → revoked
already revokedIdempotent — return current state (200)
HTTPmsg
404Redemption code not found.

POST /redemption_codes/reactivate — Reactivate

Restore a previously revoked code or account.

Request body — same shared fields as revoke (including optional reason, for example "paid").

Prior stateEffect
Revoked, never redeemedStatus → pending (customer can register)
Revoked after redemptionUser re-enabled; code → redeemed
Not revokedIdempotent — return current state (200)
HTTPmsg
404Redemption code not found.

POST /redemption_codes/sync_management — Sync management link

Backfill or refresh the NewRich account-management link on an existing license. App Store / NewRich ops call this when a purchase already has a redemption code but is missing nr_order_uuid / manage_url (for example licenses issued before those fields existed).

Request body

{
  "email": "[email protected]",
  "order_id": "NR-42-17",
  "product_sku": "yourapp-pro",
  "access_duration": "subscription",
  "billing_interval": "monthly",
  "term": "monthly",
  "nr_order_uuid": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
  "manage_url": "https://newrich.com/marketplace/orders/apps/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
}
FieldNotes
Shared identity + recurrence fieldsSame as generate
nr_order_uuid / manage_urlAlways present on this route — persist or refresh them on the matching license

Success: HTTP 2xx with "success": true. data.code is not required.

Behavior

  • Locate the license by order_id (and email as needed).
  • Store or update nr_order_uuid and manage_url so account settings can deep-link to NewRich.
  • Do not change access status (pending / redeemed / revoked) or revoke/reactivate the user.
  • Idempotent — repeating the same sync is safe and should return success.
HTTPmsg
404Redemption code not found.

Redemption code lifecycle

stateDiagram-v2
    [*] --> pending: POST /redemption_codes
    pending --> redeemed: Customer registers
    pending --> revoked: POST .../revoke
    redeemed --> revoked: POST .../revoke
    redeemed --> redeemed: POST .../renew (subscription)
    revoked --> pending: POST .../reactivate (never redeemed)
    revoked --> redeemed: POST .../reactivate (was redeemed)
StatusMeaning
pendingIssued; awaiting one-time registration
redeemedCustomer created an account
revokedCancelled, refunded, past due, or otherwise disabled

Customer registration (your app)

Deliver the code from generate to the purchaser (App Store includes it in the order confirmation email). The customer completes registration at your public registration page.

Enforce at registration:

  • Code must exist and be pending
  • Email must match the email sent to generate (case-insensitive)
  • Code is one-time use for registration
  • Revoked codes are rejected

This step is outside the license API. App Store never calls a validate or activate endpoint.

For subscription codes, grant access according to billing_interval / term, and keep renewing or revoking that access when App Store calls those endpoints later.

Implementation checklist

  • X-Licensing-Api-Key validation on all five routes (LICENSING_API_KEY, constant-time compare)
  • Caller domain allowlist on all five routes (LICENSING_ALLOWED_DOMAINS)
  • POST /api/v1/redemption_codes accepting email, order_id, optional product_sku, plus access_duration, billing_interval, term
  • Accept optional nr_order_uuid and manage_url on generate / renew / revoke / reactivate (do not require them in validation)
  • POST .../sync_management persisting nr_order_uuid and manage_url without changing access status
  • Store nr_order_uuid / manage_url on the vendor order / license row when present
  • Account settings (or billing) uses manage_url or the UUID recipe for NewRich-billed subscriptions
  • Unique order_id constraint and idempotent generate
  • HTTP 409 when re-generating a revoked order
  • POST .../renew extending subscription access (honor period_ends_at when present)
  • POST .../revoke and POST .../reactivate keyed by email + order_id (optional reason)
  • Disable linked user / access on revoke; re-enable on reactivate
  • Persist recurrence fields so renew/revoke apply the correct access model
  • Public registration page with email-bound, one-time code redemption
  • Share license API base URL and customer app URL with NewRich for listing setup

Before go-live

NewRich verifies your integration before your listing goes live — authentication, code generation, idempotency, registration, renew/revoke/reactivate/sync_management, and an end-to-end purchase. During marketplace onboarding, the team will share a private verification checklist and test steps for your endpoints. Do not publish license API keys or ad-hoc test commands in public documentation or support channels.