Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Preparing Ring content
Preparing Ring content
Preparing Ring content
Use the Founder / Developer tabs in the docs sidebar to filter this page. Founders get the business flow and the operator knobs. Developers get the services, routes, env vars and a local test run.
News Kingdom is the promotion layer on top of Ring's news module. Members with the member role or higher write articles that live on their personal space at /{username}/{slug}. An author can then ask for a place on the main /news feed. Ring scores the article, asks the author to pay a price based on that score, and sends it to platform admins in Telegram for a final yes or no.
Every promotion request moves through one field on the article, mainPageStatus:
| Stage | What happens | mainPageStatus |
|---|---|---|
| Submit | Author asks for main-page placement | submitted |
| Score | AI or heuristic scores ethics, spam, duplicates and merit | ai_scored |
| Hard block | Ethics too low, spam or duplicate risk too high | published_blog_only |
| Checkout | Price is set and a PaymentConductor checkout opens | payment_pending |
| Paid | Webhook confirms payment, admins get a Telegram card | awaiting_admin_approval |
| Approved | Article goes site-wide on the main feed | published_main |
| Rejected | Admin declines. The article stays on the author's blog | rejected |
Same-workflow: where the article lives before and after a promotion request.
Prerequisite: the news module, editor and main feed that News Kingdom promotes into.
Depends-on: how the news_promotion checkout and webhooks are routed.
See-also: the admin bot that carries the Approve, Reject and Counter buttons.
Use the Founder / Developer tabs in the docs sidebar to filter this page. Founders get the business flow and the operator knobs. Developers get the services, routes, env vars and a local test run.
News Kingdom is the promotion layer on top of Ring's news module. Members with the member role or higher write articles that live on their personal space at /{username}/{slug}. An author can then ask for a place on the main /news feed. Ring scores the article, asks the author to pay a price based on that score, and sends it to platform admins in Telegram for a final yes or no.
Every promotion request moves through one field on the article, mainPageStatus:
| Stage | What happens | mainPageStatus |
|---|---|---|
| Submit | Author asks for main-page placement | submitted |
| Score | AI or heuristic scores ethics, spam, duplicates and merit | ai_scored |
| Hard block | Ethics too low, spam or duplicate risk too high | published_blog_only |
| Checkout | Price is set and a PaymentConductor checkout opens | payment_pending |
| Paid | Webhook confirms payment, admins get a Telegram card | awaiting_admin_approval |
| Approved | Article goes site-wide on the main feed | published_main |
| Rejected | Admin declines. The article stays on the author's blog | rejected |
Same-workflow: where the article lives before and after a promotion request.
Prerequisite: the news module, editor and main feed that News Kingdom promotes into.
Depends-on: how the news_promotion checkout and webhooks are routed.
See-also: the admin bot that carries the Approve, Reject and Counter buttons.
Use the Founder / Developer tabs in the docs sidebar to filter this page. Founders get the business flow and the operator knobs. Developers get the services, routes, env vars and a local test run.
News Kingdom is the promotion layer on top of Ring's news module. Members with the member role or higher write articles that live on their personal space at /{username}/{slug}. An author can then ask for a place on the main /news feed. Ring scores the article, asks the author to pay a price based on that score, and sends it to platform admins in Telegram for a final yes or no.
Every promotion request moves through one field on the article, mainPageStatus:
| Stage | What happens | mainPageStatus |
|---|---|---|
| Submit | Author asks for main-page placement | submitted |
| Score | AI or heuristic scores ethics, spam, duplicates and merit | ai_scored |
| Hard block | Ethics too low, spam or duplicate risk too high | published_blog_only |
| Checkout | Price is set and a PaymentConductor checkout opens | payment_pending |
| Paid | Webhook confirms payment, admins get a Telegram card | awaiting_admin_approval |
| Approved | Article goes site-wide on the main feed | published_main |
| Rejected | Admin declines. The article stays on the author's blog | rejected |
Same-workflow: where the article lives before and after a promotion request.
Prerequisite: the news module, editor and main feed that News Kingdom promotes into.
Depends-on: how the news_promotion checkout and webhooks are routed.
See-also: the admin bot that carries the Approve, Reject and Counter buttons.
The checkout that charges the promotion fee with your configured processor.
You set prices in UAH with server env vars. If you leave them unset, these defaults apply:
| Setting | Default | Effect |
|---|---|---|
| Base price | 50 UAH | Minimum price for a high-merit article |
| Scale | 2000 UAH | How much the price rises as merit drops |
| Maximum | 5000 UAH | Price cap |
| Low-merit floor | merit below 0.25 | Price is raised to at least 4999 UAH |
| Admin counter-offer | +25% | Telegram Counter button sets a new amount the author must pay |
lib/telegram/admin-bot/news-callback-handler.tsapp/api/telegram/admin-bot/webhook/route.ts| Member promotion UI | features/news/components/news-promotion-panel.tsx on /my-news |
| Status and field types | features/news/types/index.ts (MainPageStatus, NewsContentType, NewsVisibility) |
| JSONB row mapper | lib/news/map-news-document.ts |
| Brand and absolute URLs | lib/site-branding.ts (localeBlogArticleUrl, absoluteSiteUrl) |
| Method and path | Auth | Role |
|---|---|---|
POST /api/news/promotion/submit | Session, article author only | Marks promoteToMainPage, scores, opens checkout |
PATCH /api/news/promotion/submit | Header x-news-promotion-secret | Dev or test: mark paid and notify admins |
POST /api/payments/{wayforpay,stripe,paypal}/webhook | Processor signature | Canonical paid confirmation |
POST /api/news/promotion/wayforpay-webhook | Processor payload | Deprecated delegate to the canonical WayForPay webhook |
POST /api/mcp/v1/news/[id]/{score,approve,reject,publish} | Service token (withMcpGuard) | Agent and MCP operations on the same workflow |
scoreNewsForMainPage() calls OpenRouter when OPENROUTER_API_KEY is set. Without a key it keeps heuristic defaults (ethics 0.7, spam 0.2, merit 0.5) and records model: 'heuristic'.
slug or siteWideSlug. 0.9 if a published article (first 200) has the same title.ethics < NEWS_ETHICS_MIN or spamRisk > NEWS_SPAM_MAX or duplicateRisk > NEWS_DUPLICATE_MAX. Result: published_blog_only and no checkout.clamp(base, max, base + (1 - merit) * scale). Merit below NEWS_MERIT_SOFT_REJECT raises the price to at least 4999. Low merit does not change the status.None of the NEWS_* keys are in env.local.template. They are read in code with these defaults.
| Variable | Default | Read in |
|---|---|---|
NEWS_PROMO_BASE_UAH / NEWS_PROMO_SCALE_UAH / NEWS_PROMO_MAX_UAH | 50 / 2000 / 5000 | news-scoring-service.ts |
NEWS_ETHICS_MIN / NEWS_SPAM_MAX / NEWS_DUPLICATE_MAX | 0.35 / 0.85 / 0.9 | news-scoring-service.ts |
NEWS_MERIT_SOFT_REJECT | 0.25 | scoring and workflow |
OPENROUTER_API_KEY, OPENROUTER_MODEL_PRIMARY, OPENROUTER_MODEL_FALLBACK | unset | news-scoring-service.ts |
PAYMENT_NEWS_PROCESSOR | falls back to PAYMENT_DEFAULT_PROCESSOR, then wayforpay | lib/payments/payment.config.ts |
NEWS_PROMOTION_WEBHOOK_SECRET | unset | PATCH /api/news/promotion/submit |
ADMIN_BOT_TOKEN | unset | Telegram callback answers |
All four tables use the JSONB document model (id, data jsonb, timestamps) in data/schema.sql (flattened v4.1.0): news, news_categories, news_likes, news_submission_audit. Every transition appends to data.mainPageStatusHistory and writes one news_submission_audit row. data/migrations/002_news_content_schema.sql and 003_news_kingdom_upgrade.sql are historical. Fresh installs apply schema.sql only.
approveMainPagePublication() sets status: 'published' and visibility: 'site-wide'. For user-authored stories it then queues enqueueRewardCreditAddEvent({ trigger: 'newsStoryApproved' }) and fans out generateArticleTranslations(). Telegram recipients are users with a platform admin role and a communication.telegramId.
Apply the schema and create a member article
Apply data/schema.sql and sign in as a user with role member or higher. Create an article at /news/create. It is stamped with blogUsername and appears at /{username}/{slug} once published.
Submit for promotion
The response carries status, aiScore, and either a redirect or payment fields for the checkout.
Simulate payment in dev
Approve
Press Approve on the Telegram card, or call POST /api/mcp/v1/news/<id>/approve with a service token. Check that mainPageStatus is published_main and that a news_submission_audit row exists.
The checkout that charges the promotion fee with your configured processor.
You set prices in UAH with server env vars. If you leave them unset, these defaults apply:
| Setting | Default | Effect |
|---|---|---|
| Base price | 50 UAH | Minimum price for a high-merit article |
| Scale | 2000 UAH | How much the price rises as merit drops |
| Maximum | 5000 UAH | Price cap |
| Low-merit floor | merit below 0.25 | Price is raised to at least 4999 UAH |
| Admin counter-offer | +25% | Telegram Counter button sets a new amount the author must pay |
lib/telegram/admin-bot/news-callback-handler.tsapp/api/telegram/admin-bot/webhook/route.ts| Member promotion UI | features/news/components/news-promotion-panel.tsx on /my-news |
| Status and field types | features/news/types/index.ts (MainPageStatus, NewsContentType, NewsVisibility) |
| JSONB row mapper | lib/news/map-news-document.ts |
| Brand and absolute URLs | lib/site-branding.ts (localeBlogArticleUrl, absoluteSiteUrl) |
| Method and path | Auth | Role |
|---|---|---|
POST /api/news/promotion/submit | Session, article author only | Marks promoteToMainPage, scores, opens checkout |
PATCH /api/news/promotion/submit | Header x-news-promotion-secret | Dev or test: mark paid and notify admins |
POST /api/payments/{wayforpay,stripe,paypal}/webhook | Processor signature | Canonical paid confirmation |
POST /api/news/promotion/wayforpay-webhook | Processor payload | Deprecated delegate to the canonical WayForPay webhook |
POST /api/mcp/v1/news/[id]/{score,approve,reject,publish} | Service token (withMcpGuard) | Agent and MCP operations on the same workflow |
scoreNewsForMainPage() calls OpenRouter when OPENROUTER_API_KEY is set. Without a key it keeps heuristic defaults (ethics 0.7, spam 0.2, merit 0.5) and records model: 'heuristic'.
slug or siteWideSlug. 0.9 if a published article (first 200) has the same title.ethics < NEWS_ETHICS_MIN or spamRisk > NEWS_SPAM_MAX or duplicateRisk > NEWS_DUPLICATE_MAX. Result: published_blog_only and no checkout.clamp(base, max, base + (1 - merit) * scale). Merit below NEWS_MERIT_SOFT_REJECT raises the price to at least 4999. Low merit does not change the status.None of the NEWS_* keys are in env.local.template. They are read in code with these defaults.
| Variable | Default | Read in |
|---|---|---|
NEWS_PROMO_BASE_UAH / NEWS_PROMO_SCALE_UAH / NEWS_PROMO_MAX_UAH | 50 / 2000 / 5000 | news-scoring-service.ts |
NEWS_ETHICS_MIN / NEWS_SPAM_MAX / NEWS_DUPLICATE_MAX | 0.35 / 0.85 / 0.9 | news-scoring-service.ts |
NEWS_MERIT_SOFT_REJECT | 0.25 | scoring and workflow |
OPENROUTER_API_KEY, OPENROUTER_MODEL_PRIMARY, OPENROUTER_MODEL_FALLBACK | unset | news-scoring-service.ts |
PAYMENT_NEWS_PROCESSOR | falls back to PAYMENT_DEFAULT_PROCESSOR, then wayforpay | lib/payments/payment.config.ts |
NEWS_PROMOTION_WEBHOOK_SECRET | unset | PATCH /api/news/promotion/submit |
ADMIN_BOT_TOKEN | unset | Telegram callback answers |
All four tables use the JSONB document model (id, data jsonb, timestamps) in data/schema.sql (flattened v4.1.0): news, news_categories, news_likes, news_submission_audit. Every transition appends to data.mainPageStatusHistory and writes one news_submission_audit row. data/migrations/002_news_content_schema.sql and 003_news_kingdom_upgrade.sql are historical. Fresh installs apply schema.sql only.
approveMainPagePublication() sets status: 'published' and visibility: 'site-wide'. For user-authored stories it then queues enqueueRewardCreditAddEvent({ trigger: 'newsStoryApproved' }) and fans out generateArticleTranslations(). Telegram recipients are users with a platform admin role and a communication.telegramId.
Apply the schema and create a member article
Apply data/schema.sql and sign in as a user with role member or higher. Create an article at /news/create. It is stamped with blogUsername and appears at /{username}/{slug} once published.
Submit for promotion
The response carries status, aiScore, and either a redirect or payment fields for the checkout.
Simulate payment in dev
Approve
Press Approve on the Telegram card, or call POST /api/mcp/v1/news/<id>/approve with a service token. Check that mainPageStatus is published_main and that a news_submission_audit row exists.
The checkout that charges the promotion fee with your configured processor.
You set prices in UAH with server env vars. If you leave them unset, these defaults apply:
| Setting | Default | Effect |
|---|---|---|
| Base price | 50 UAH | Minimum price for a high-merit article |
| Scale | 2000 UAH | How much the price rises as merit drops |
| Maximum | 5000 UAH | Price cap |
| Low-merit floor | merit below 0.25 | Price is raised to at least 4999 UAH |
| Admin counter-offer | +25% | Telegram Counter button sets a new amount the author must pay |
lib/telegram/admin-bot/news-callback-handler.tsapp/api/telegram/admin-bot/webhook/route.ts| Member promotion UI | features/news/components/news-promotion-panel.tsx on /my-news |
| Status and field types | features/news/types/index.ts (MainPageStatus, NewsContentType, NewsVisibility) |
| JSONB row mapper | lib/news/map-news-document.ts |
| Brand and absolute URLs | lib/site-branding.ts (localeBlogArticleUrl, absoluteSiteUrl) |
| Method and path | Auth | Role |
|---|---|---|
POST /api/news/promotion/submit | Session, article author only | Marks promoteToMainPage, scores, opens checkout |
PATCH /api/news/promotion/submit | Header x-news-promotion-secret | Dev or test: mark paid and notify admins |
POST /api/payments/{wayforpay,stripe,paypal}/webhook | Processor signature | Canonical paid confirmation |
POST /api/news/promotion/wayforpay-webhook | Processor payload | Deprecated delegate to the canonical WayForPay webhook |
POST /api/mcp/v1/news/[id]/{score,approve,reject,publish} | Service token (withMcpGuard) | Agent and MCP operations on the same workflow |
scoreNewsForMainPage() calls OpenRouter when OPENROUTER_API_KEY is set. Without a key it keeps heuristic defaults (ethics 0.7, spam 0.2, merit 0.5) and records model: 'heuristic'.
slug or siteWideSlug. 0.9 if a published article (first 200) has the same title.ethics < NEWS_ETHICS_MIN or spamRisk > NEWS_SPAM_MAX or duplicateRisk > NEWS_DUPLICATE_MAX. Result: published_blog_only and no checkout.clamp(base, max, base + (1 - merit) * scale). Merit below NEWS_MERIT_SOFT_REJECT raises the price to at least 4999. Low merit does not change the status.None of the NEWS_* keys are in env.local.template. They are read in code with these defaults.
| Variable | Default | Read in |
|---|---|---|
NEWS_PROMO_BASE_UAH / NEWS_PROMO_SCALE_UAH / NEWS_PROMO_MAX_UAH | 50 / 2000 / 5000 | news-scoring-service.ts |
NEWS_ETHICS_MIN / NEWS_SPAM_MAX / NEWS_DUPLICATE_MAX | 0.35 / 0.85 / 0.9 | news-scoring-service.ts |
NEWS_MERIT_SOFT_REJECT | 0.25 | scoring and workflow |
OPENROUTER_API_KEY, OPENROUTER_MODEL_PRIMARY, OPENROUTER_MODEL_FALLBACK | unset | news-scoring-service.ts |
PAYMENT_NEWS_PROCESSOR | falls back to PAYMENT_DEFAULT_PROCESSOR, then wayforpay | lib/payments/payment.config.ts |
NEWS_PROMOTION_WEBHOOK_SECRET | unset | PATCH /api/news/promotion/submit |
ADMIN_BOT_TOKEN | unset | Telegram callback answers |
All four tables use the JSONB document model (id, data jsonb, timestamps) in data/schema.sql (flattened v4.1.0): news, news_categories, news_likes, news_submission_audit. Every transition appends to data.mainPageStatusHistory and writes one news_submission_audit row. data/migrations/002_news_content_schema.sql and 003_news_kingdom_upgrade.sql are historical. Fresh installs apply schema.sql only.
approveMainPagePublication() sets status: 'published' and visibility: 'site-wide'. For user-authored stories it then queues enqueueRewardCreditAddEvent({ trigger: 'newsStoryApproved' }) and fans out generateArticleTranslations(). Telegram recipients are users with a platform admin role and a communication.telegramId.
Apply the schema and create a member article
Apply data/schema.sql and sign in as a user with role member or higher. Create an article at /news/create. It is stamped with blogUsername and appears at /{username}/{slug} once published.
Submit for promotion
The response carries status, aiScore, and either a redirect or payment fields for the checkout.
Simulate payment in dev
Approve
Press Approve on the Telegram card, or call POST /api/mcp/v1/news/<id>/approve with a service token. Check that mainPageStatus is published_main and that a news_submission_audit row exists.