Skip to content

Applications

Applications — /api/org/{slug}/applications

Section titled “Applications — /api/org/{slug}/applications”
Method · RoutePurposeSource
GET /List applicationsApplicationEndpoints.cs:22
GET /{appId}Get one application:35
POST /Create application:49
PUT /{appId}Update application (incl. OIDC/SAML client config):82
DELETE /{appId}Delete application:128
PATCH /{appId}/statusToggle status:160
POST /{appId}/rotate-secretRotate client secret:187

An application’s public key is its client key (client_..., one per platform × environment) — it is what the SDK’s clientId and the hosted login URL carry. OIDC client config (PKCE requirement, allowed grant types, scopes, post-logout redirects) and SAML SP registration (EntityID, ACS URL) live on the Application — see OIDC and SAML.

An App Group is the product axis: it holds hosted-login branding (colors, social providers, passkey/magic-link toggles, hero copy), the Passkey RP ID, and — when the group sells something — its storefront and payout links.

Method · RoutePurposeSource
GET / · GET /{groupId}List / getAppGroupEndpoints.cs:24, :37
POST / · PUT /{groupId} · DELETE /{groupId}App-group CRUD (branding, login methods, hero):51, :93, :245
POST /{groupId}/appsAdd app to group:294
POST / with monetizationEnabled: trueCreate the group with Axowl Payments on (collection through App Group Payments on the marketplace). Omitted or false = the group connects its own payment gateway (KG Inicis, Toss Payments, Razorpay and others) while Axowl runs products, subscriptions, vouchers and entitlements; the gateway connection UI follows. The dashboard picks the default from the organization’s country: in the 44 countries where Stripe opens seller accounts it defaults to Axowl Payments, elsewhere Axowl Payments is shown as not yet available, and in Korea only your own gateway is offered. Turning it on at creation passes the same two checks as PUT — owner-only (view.billing, else 403 PERMISSION_DENIED) and the plan’s collecting-group limit (else 403 UPGRADE_REQUIRED).AppGroupEndpoints.cs
POST · DELETE /{groupId}/iconApp-group icon. PNG, JPEG, WebP or SVG up to 2MB. The type is read from the file’s bytes and an SVG is cleaned before it is stored.AppGroupIconEndpoints.cs:17, :90

These routes lead to money (payout accounts, prices), so they sit behind the same owner-only gate as billing — hiding a button in the dashboard is not enough.

Method · RoutePurposeSource
POST /{groupId}/gateway-linkupRegister the app group as a brand with the partner storefront (Bullmark). The partner app comes from configuration, never from the request. Refused for a group with no products or on the plan floor.AppGroupEndpoints.cs:349
POST /{groupId}/gateway-listingOne-time session to the brand’s listing screen (review status, voucher economy). No re-login; the destination is fixed server-side.:441
POST /{groupId}/payout-sessionEmbedded payout onboarding (Stripe Connect) — returns a short-lived client secret, never cached. Brand slug is resolved server-side, not accepted from the caller.:505
GET /{groupId}/payout-statusOnboarding progress and remaining requirements.:632
POST /{groupId}/products · GET /{groupId}/productsCreate a price / list what the group sells. Reading is open to any member who can see the group; writing a price is owner-only.:731, :796
POST /{groupId}/products/{productId}/syncPush a price to the partner storefront; idempotent (already-registered rows are not re-sent).:843
POST /{groupId}/products/importImport the partner’s existing prices; skips ones already present (by ProductKey, else name).:907
GET /{groupId}/products/template.xlsxDownload the bulk-registration template (Products sheet with headers and two example rows, Guide sheet). Example currency is KRW when the group has its own gateway connected. Owner-only.AppGroupProductBulkEndpoints
POST /{groupId}/products/bulkRegister many prices at once from a multipart file (.xlsx or .csv, up to 200 rows, 2MB). Columns: name, type (Subscription · OneTimeAddon · Usage), currency (USD · KRW), price, yearly_discount_percent, price_url, product_key, usage_unit_label, description. Each row goes through the same path as POST /{groupId}/products; a bad row does not stop the others. Returns { total, created, registered, drafts, failed, rows: [{ row, name, status, detail }] }. Owner-only.AppGroupProductBulkEndpoints
POST /{groupId}/products/{productId}/repriceChange a price, and optionally name, description, priceUrl and usageUnitLabel (omit a field to keep it; the key and type cannot change — to change the key, delete the product and create it again). Responds changed: false without touching anything when nothing differs. A registered product is withdrawn and published again; the new one is reported as “not registered” until re-synced.:1010
DELETE /{groupId}/products/{productId}Remove a price (owner-only).:1099

Own payment gateway (owner-only, view.billing gate, passkey seal)

Section titled “Own payment gateway (owner-only, view.billing gate, passkey seal)”

For organizations outside the 44 countries where Stripe opens seller accounts (Korea first), a brand can connect its own payment gateway instead of Axowl Payments: customers pay straight into the brand’s gateway account while Axowl runs products, subscriptions, vouchers and entitlements. Korean gateways connect through PortOne (KG Inicis, Toss Payments, NHN KCP, NICE, Smartro, KSNET, KPN, Welcome Payments, KICC, plus Kakao Pay, Naver Pay and Toss Pay).

Method · RoutePurposeSource
GET /{groupId}/payment-gatewayCurrent connection: gateway, store id, channel keys, webhook URL. Secrets are never returned.AppGroupPaymentGatewayEndpoints.cs
POST /{groupId}/payment-gatewayConnect: pgProvider, storeId, paymentChannelKey, optional billingChannelKey, apiSecret, webhookSecret (whsec_…), isTest, and assertionResponseJson (passkey seal bound to this group). The API secret is checked against PortOne before anything is stored; secrets are stored encrypted. Replaces the previous connection.
POST /{groupId}/payment-gateway/disconnectDisconnect (passkey seal required). The brand’s webhook URL stops working at once.
POST /api/v1/webhooks/portone/brand/{token}Per-brand PortOne webhook. Verified with that brand’s webhook secret (Standard Webhooks, ±5 min), idempotent per webhook-id, and never trusted on its own: Axowl re-reads the payment with the brand’s API secret and only a PAID payment of that store is recorded and forwarded to the marketplace ledger.WebhookEndpoints.cs

Events appgroup.payment_gateway.connected / .disconnected (AGG001/AGG002) are sealed in the audit timeline; they carry the gateway id and store id, never a secret.

What changes once a gateway is connected. Connecting also tells the marketplace ledger which store and channel the brand sells through (no secrets travel); if the ledger cannot be told, nothing is connected and the call returns 502 GATEWAY_MIRROR_FAILED. From then on:

  • Prices in won. POST /{groupId}/products accepts currency — USD (default) or KRW. KRW is allowed only while a gateway is connected (409 GATEWAY_NOT_LINKED otherwise) and must be a whole number of won; the retailPriceUsd field then carries won (the field name is kept — it is the sealed name on the ledger). GET …/products returns currency per row.
  • Checkout answers differ. POST /api/org/{slug}/marketplace/apps/{groupKey}/checkout returns provider: "portone" and an intent (paymentId, orderId, orderName, amount, currency, storeId, channelKey, billingChannelKey, issueBillingKey, mode, billingCycle) instead of a Stripe redirectUrl. The buyer’s browser opens the PortOne payment window with those values; one-time purchases are paid in the window, subscriptions register a card (billing key).
  • Subscriptions. POST /api/org/{slug}/marketplace/apps/{groupKey}/own-gateway/billing-key with orderId, paymentId and the billingKey the window returned (buyer’s owner or admin). Axowl checks the key with the brand’s gateway, stores it encrypted, charges the first period and records the payment in the same ledger as the webhook. Renewals are charged by Axowl every period with that key; a declined card marks the subscription past due on the marketplace and the installed app ends after 21 unpaid days. Plan changes are not available for these subscriptions yet (409 NOT_SUPPORTED_FOR_OWN_GATEWAY); cancel and subscribe to the other plan.
  • Payment confirmations reach the marketplace as external-paid notifications; the brand’s own gateway handles refunds and chargebacks. Axowl bills the brand an engine fee on the processed volume (accrued per payment; invoicing follows).

Collection and settlement rules for what an app group sells are in Billing & Monetization.

Marketplace (owner or admin, passkey seal)

Section titled “Marketplace (owner or admin, passkey seal)”

An app group can be listed on marketplace.axowl.com. The owner decides here; nothing is listed until it is published. The marketplace lists web apps (SaaS and PaaS) only. The server checks these conditions every time you publish:

  • Plan. The organization is on the Pro plan or higher (feature feature.marketplace.listing). Publishing, updating the listing and translating return 403 UPGRADE_REQUIRED on Free. Unpublishing works on any plan. If an organization moves below Pro, its apps drop out of the public list until it upgrades again.
  • Service model and field. The app group is marked SaaS or PaaS and has a field (category) chosen.
  1. The app group has at least one active Web application. Groups with only mobile, desktop or server-to-server apps cannot be listed, and a published group drops out of the list if its last web app is removed.
  2. A domain for this app group is verified in Axowl (the Custom Domain card).
  3. The app address you enter is on that company domain — app.acme.com when login.acme.com is verified. Under a two-letter country domain the common second level (co, com, or, ne, ac, go …) is kept, so acme.co.kr never widens to co.kr.
  4. The app address opens over https (no 4xx/5xx). The request goes through the SSRF-guarded client.

Registering payments is not a condition.

Method · RoutePurposeSource
GET /{groupId}/marketplaceCurrent app address, publish time, verified domain, screenshotsAppGroupMarketplaceEndpoints.cs:37
POST /{groupId}/marketplace/checkRun the four checks for an address without publishing. Returns each check as passed, failed or not reached.:38
PUT /{groupId}/marketplacePublish or unpublish. Body: appUrl, publish, assertionResponseJson, and optionally translations (below). The seal is bound to this group and to publish-or-unpublish (step-up purpose appgroup.marketplace). A publish that fails a check returns 409 CONDITIONS_NOT_MET with the checks.:39
POST /{groupId}/marketplace/translateAI translation draft for review. Returns source (the texts to translate) and langs (language → the same keys, translated). Pro or higher. The model is called only when the source changed since the last translation, at most 10 times a day per app group (429 TRANSLATE_LIMIT); otherwise the saved draft is returned with cached: true. 400 NOTHING_TO_TRANSLATE when the app group has no description.AppGroupMarketplaceEndpoints.cs
POST /{groupId}/marketplace/screenshotsAdd a screenshot or a mockup to the listing (multipart field image). Up to 3 per app group, under 3MB each. PNG, JPEG, WebP or SVG. The type is read from the file’s bytes, not from the declared content type, and an SVG is cleaned before it is stored (scripts, event attributes, javascript: links and foreignObject are removed). Saved at once, no seal. 409 SCREENSHOTS_FULL, 400 SCREENSHOT_TYPE, 400 SCREENSHOT_TOO_LARGE. Returns screenshots.AppGroupMarketplaceEndpoints.cs
DELETE /{groupId}/marketplace/screenshots/{index}Remove one screenshot by its position (0-based). Returns the remaining screenshots. 404 when the position does not exist.AppGroupMarketplaceEndpoints.cs
GET /{groupId}/marketplace/salesPayments for this app, one line per payment: gross, take rate, marketplace commission, net, first payment or renewal. Query days (default 30, up to 365).AppGroupMarketplaceEndpoints.cs

The listing uses the app group’s name, description and icon. Up to three screenshots or mockups, uploaded on the Marketplace card, are shown at the top of the product page. If the app group has a Bullmark introduction that has passed review, its text, logo and photos are added. Bullmark does not decide what is listed.

Translation, reviewed before it goes live. Publishing and updating the listing start with a translation into eight languages (en, ko, ja, zh-Hant, de, es, fr, pt). The texts translated are the description, the Bullmark introduction and details, and each plan’s description. The app name and plan names are fixed values and are not translated; plan names and descriptions are edited on the Products tab, the introduction in Bullmark. The owner can also rewrite the description during review: /translate accepts description (not saved yet) and translates that text, and the sealed publish saves it through the normal app group update. The owner reviews each language, edits it or leaves a field empty (that field then shows the original), and sends the result as translations, with description, in the sealed publish. The draft from /translate is never shown on the marketplace on its own. If the source text changes later, the public list shows the original until the listing is updated again.

Published apps are readable without authentication at GET /api/public/marketplace/apps (cached for five minutes; publishing or unpublishing clears the cache) — AppGroupMarketplaceEndpoints.cs:41. Each app carries plans: the products you registered on the app group that can be bought there (connected to payments, price above zero, type Subscription or OneTimeAddon). An empty list means the app is free. Each app also carries i18n: language → description, introDescription, details, plan.{id}.description (plan id without dashes), present only when the owner reviewed the translation and the source has not changed since. A missing key means the original text. Each app also carries screenshots (up to three image URLs, empty when none were uploaded) and publisherSlug (the selling organization).

Across your organizations. The console’s System home has a Marketplace tab with two lists. Both cover every organization where you are an active owner or admin; other organizations are not included. Publishing from there opens the same Marketplace card, so the checks and the seal above are unchanged.

Method · RoutePurposeSource
GET /api/me/marketplace/appsPer organization (slug, name), its app groups with id, key, name, iconUrl, brandColor, webApp (name of an active web app, or null), verifiedDomain, appUrl, publishedAt. webApp and verifiedDomain follow checks 0 and 1; they are a hint, and the server runs all four checks again when you publish.AppGroupMarketplaceEndpoints.cs
GET /api/me/marketplace/subscriptionsPer organization, what it turned on or installed from the marketplace: kind (service for an Axowl service, app for another organization’s app), key (app: + group key for apps), name, publisher, openUrl, productId (the product page id on the marketplace), enabledAt, paid. Connectors such as Slack are not included.AppGroupMarketplaceEndpoints.cs

Both return 401 when the token has no user.

Another organization installs your app from its product page. The price comes from the products you register on the app group. The marketplace does not keep its own price list.

  • Free app (no plans): an owner or admin of the buying organization installs it with a passkey seal, using the same call as turning on any integration.
  • Paid app: turning it on directly returns 402 PAYMENT_REQUIRED. The buyer picks a plan, can enter a voucher code, seals with a passkey and pays by card. The app is installed when the payment is confirmed. A voucher applies to the first payment only. Subscription plans charge every month until canceled. OneTimeAddon plans charge once.
  • After installing: the buyer can move to another Subscription plan of the same app (the difference is charged at once, prorated) and can buy a OneTimeAddon while the app stays installed.
  • Uninstalling a paid app ends the subscription at the end of the period already paid for. The app stays installed until that date. If there is no running subscription, it is removed at once.

Money goes to your own Stripe account. The marketplace commission is taken at the source, in addition to your plan’s take rate. See Billing & Monetization.

Method · RoutePurposeSource
POST /api/org/{slug}/marketplace/apps/{groupKey}/checkoutStart a purchase for the organization in slug. Body: productId, voucherCode (optional), assertionResponseJson. The seal is the same one as installing (integration.enable, subject slug|app:{groupKey}|enable). Returns redirectUrl (the card payment page), mode, priceUsd, firstAmountUsd. 409 ALREADY_INSTALLED when the app is already installed and the product is a Subscription; a OneTimeAddon can be bought while installed.AppGroupMarketplacePurchaseEndpoints.cs
POST /api/org/{slug}/marketplace/apps/{groupKey}/billing-portalOwner or admin of the buying organization. Opens the card-payment provider’s billing page for this app’s subscription: change the card, see receipts, cancel (at the end of the period) or take a cancellation back. Body: assertionResponseJson — the same seal as uninstalling (integration.enable, subject slug|app:{groupKey}|disable). Returns url. 404 NO_ACTIVE_SUBSCRIPTION for a one-time or free install.AppGroupMarketplacePurchaseEndpoints.cs
GET /api/org/{slug}/marketplace/apps/{groupKey}/subscriptionOwner or admin of the buying organization. The plan this organization is subscribed to (subscription, with productId) and the one-time add-ons it bought (addons). subscription is null for a free or one-time install.AppGroupMarketplacePurchaseEndpoints.cs
POST /api/org/{slug}/marketplace/apps/{groupKey}/change-planMove the running subscription to another Subscription plan of the same app. Body: productId, assertionResponseJson — the same seal as uninstalling (integration.enable, subject slug|app:{groupKey}|disable). The difference is invoiced at once, prorated. 404 NOT_INSTALLED, 404 PRODUCT_NOT_AVAILABLE, 409 SAME_PLAN.AppGroupMarketplacePurchaseEndpoints.cs
GET /api/org/{slug}/marketplace/revenueOwner or admin of the selling organization. What buyers paid for this organization’s apps and vouchers: thisMonth, thisYear and total (each grossUsd and netUsd), byDay (30 days), byMonth (12 months), byYear, byApp and byProduct. gatewayOk: false with zeros when payments cannot be read. See Billing & Monetization.AppGroupMarketplaceEndpoints.cs
DELETE /api/org/{slug}/integrations/app:{groupKey}/enableUninstall. For a running subscription this answers 202 with endsAt and leaves the app installed until then. If payments cannot be reached it answers 502 SUBSCRIPTION_CANCEL_FAILED and changes nothing.IntegrationModuleEnabledEndpoints.cs
POST /api/public/v1/marketplace/purchasesPayment engine only (platform key). event: paid installs the app for buyerOrgId; event: past_due (a renewal failed) leaves it installed and notifies both sides; event: canceled (subscription ended or refunded) removes it. Safe to resend. canceled is accepted after the app group or the buying organization was deleted; paid is not.PublicApiEndpoints.cs
POST /api/public/v1/marketplace/issuers/statusPayment engine only (platform key). Asks whether an app’s publisher, or a buying organization, still exists. Body: issuers[] with groupKey, orgId, or both (up to 200). Answers, in the same order, status: active, suspended, deleted or unknown, and a reason (app_group_deleted, org_deleted, org_suspended) when it is not active. No names or plans are returned.PublicApiEndpoints.cs
GET /api/org/{slug}/app-groups/{groupId}/marketplace/customersOwner or admin of the selling organization. The organizations that installed this app, now or before: name, installed or removed, paid or free, since, until. The same list appears on the Marketplace card in the console.AppGroupMarketplaceEndpoints.cs

Emails. Access events are sent by Axowl as an in-app notification and an email:

  • App installed: to the owners and admins of the buying organization, and to the owners of the selling organization (“new customer”).
  • App removed: to the same people.
  • Buying organization deleted: its running marketplace subscriptions are canceled by the payment engine and end at the end of the period already paid for. The person who paid gets the cancellation email.
  • App deleted by its publisher: to the owners and admins of every organization that had it installed. The install is removed at once. A running subscription is canceled by the payment engine and ends at the end of the period already paid for.
  • Listed on or taken off the marketplace: to the owners of the selling organization.

Money events are sent by the payment engine to the person who paid and to the seller:

  • Receipts, including every monthly renewal
  • A failed renewal, with a link to pay. Axowl also sends an in-app notification to the owners and admins of both organizations.
  • A plan change, with the prorated amount. Both organizations also get an in-app notification.
  • A cancellation that is scheduled, with the end date
  • The subscription ending
  • Refunds

Test tools (test environment only). When Marketplace:TestTools is on, GET /api/public/marketplace/test-tools returns enabled: true. The buying organization’s owner or admin can then use two endpoints:

  • POST /api/org/{slug}/marketplace/apps/{groupKey}/test-clock with action:
    • status: show where the subscription stands.
    • advance_month: move the subscription’s Stripe test clock to the next renewal.
    • fail_next: make the next renewal payment fail.
    • succeed_next: make the next renewal payment go through.
  • GET .../reconcile: compare Stripe’s payments with the payment ledger line by line, and show Axowl’s install state.

Both answer 404 everywhere else.