Campaign records, approval workflow, budget guardrails against the ad account minimum, and Meta ad preview. Creates nothing in Meta. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MaskanX Backend
Independent Python and FastAPI backend for MaskanX.
Service Contract
- Default API URL:
http://127.0.0.1:8088 - PostgreSQL database:
campaign - Frontend origin: configured with
MASKANX_CORS_ORIGINS - Maskan CRM: connected through its versioned REST API and optional MCP tools
This repository never connects directly to the Maskan CRM PostgreSQL database.
Local Setup
-
Create the database:
CREATE DATABASE campaign; -
Install dependencies and configure the environment:
python -m venv .venv .\.venv\Scripts\python.exe -m pip install -e ".[dev]" npm install Copy-Item .env.local.example .env.local -
Migrate, seed, and start:
npm run local:migrate npm run local:seed npm run local
Open API documentation at http://127.0.0.1:8088/docs when documentation is
enabled.
Upgrading from JSON storage
Chats and cron jobs were originally stored as JSON files in the working
directory (~/.adclaw/chats.json and ~/.adclaw/jobs.json). PostgreSQL is now
the default backend, so those records are no longer read and existing chats,
sessions, and scheduled jobs appear to be missing even though nothing was
deleted.
Copy them into PostgreSQL once:
npm run local:import-json -- --dry-run # report what would be imported
npm run local:import-json # perform the import
The import upserts by id and never deletes, so it is safe to re-run. The JSON files are left untouched as a backup.
Campaigns
Campaign records live in PostgreSQL. Apply the schema before first use:
npm run local:migrate
Set META_ADS_ACCESS_TOKEN in Settings > Environments, and enable the
meta_ads MCP client if you also want campaign tools in chat.
What Phase 1 does and does not do
Phase 1 owns campaign records, the approval workflow, budget guardrails and ad preview. It creates nothing in Meta. The only Meta calls are two reads:
GET /act_<id>for balance, spend andmin_daily_budgetGET /act_<id>/generatepreviewsto render ad previews
There is deliberately no launch or sync endpoint. A campaign moves
draft -> pending_approval -> approved and stops there; pushing objects to
Meta arrives in Phase 2.
Ad previews are rendered from a creative spec and create no Meta objects, so the wizard can show exactly how an ad will look across desktop feed, mobile feed, Instagram feed, Instagram story, Facebook story and right column before anything exists.
Budget minimums
Each ad account publishes a min_daily_budget in minor units. The API rejects
a lower daily budget with 422 and a readable message rather than letting Meta
reject it opaquely later. If the account cannot be read (no token, rate limit),
the check is skipped and a warning is logged: the budget guardrail is not
enforced in that case.
Delivery requires a payment method
Meta will not deliver ads until a funding source is attached to the ad account. This cannot be done from MaskanX — the Marketing API does not allow creating funding instruments, so it must be done in Meta Business Manager. The Campaigns page shows a warning when no payment method is present.
Clearing and restoring credentials
Stop MaskanX before clearing credentials. A running app holds its
configuration in memory and can rewrite config.json and providers.json
after the clear, silently restoring the values that were just removed. The
clear script now verifies the result and exits non-zero if anything
secret-looking survives.
Credentials live in three places, and all of them are cleared:
~/.adclaw.secret/providers.json- LLM provider keys~/.adclaw.secret/envs.json- integration keys~/.adclaw/companies/<id>/- a per-company copy of both, plus LinkedIn OAuth
Because each company keeps its own copy, an intact company store can be used to recover the active one:
npm run secrets:restore -- --from default --dry-run
npm run secrets:restore -- --from default
Credential files are copied wholesale; config.json is patched surgically, so
only missing MCP env values and disabled clients are restored and
company-specific settings are preserved. A backup is written to
~/.adclaw.secret/restore-backups/ first.
Commands
| Command | Purpose |
|---|---|
npm start |
Start using .env or injected environment variables |
npm run build |
Build a Python wheel in dist/ |
npm run local |
Local API with reload |
npm run local:migrate |
Apply local PostgreSQL migrations |
npm run local:migrate:undo |
Roll back the newest migration |
npm run local:migrate:undo:all |
Roll back every migration |
npm run local:seed |
Add starter data |
npm run local:seed:undo |
Remove starter data |
npm run local:import-json |
Import legacy JSON chats/cron jobs into PostgreSQL |
npm run secrets:dry-run |
Report which credentials a clear would remove |
npm run secrets:clear |
Clear runtime credentials (creates a backup) |
npm run secrets:clear:all |
Also blank secret lines in .env files (creates a backup) |
npm run secrets:delete:all |
Clear everything including .env files (creates a backup) |
npm run secrets:delete:all:no-backup |
Same, with no backup - irreversible |
npm run secrets:restore |
Restore credentials from an intact company store |
npm run local:reset |
Rebuild and seed the local schema |
npm run dev |
Development API with reload |
npm run dev:migrate |
Apply development migrations |
npm run dev:reset |
Reset the development schema |
npm test |
Run Pytest using .env.testing |
npm run test:migrate |
Apply test database migrations |
npm run test:reset |
Reset the test schema |
npm run prod |
Start the production API |
npm run prod:migrate |
Apply production migrations |
npm run prod:seed |
Apply production starter data |
The command names match the platform deployment contract. They invoke the native MaskanX Python migration runner, not Sequelize.
Docker
Copy-Item .env.local.example .env
docker compose up -d --build
For a managed PostgreSQL deployment, provide DATABASE_URL through the hosting
secret manager and run npm run prod:migrate as a release step.