Recipes

Working scripts for the things people actually build. Each one names the decision
it supports, not just the endpoint it calls.

Assumes $ANALIFY_TOKEN and $ORG are set — see Quickstart.
Money on the Analytics API is integer piasters; on the Core API it's the
platform's unit (Core concepts §8).


1. Daily profit brief

Decision: did yesterday make money, and is the trend moving?

One call, bundled comparison, no second request:

YESTERDAY=$(date -u -v-1d +%F 2>/dev/null || date -u -d yesterday +%F)

curl -s -G "https://analify.scalyax.ai/api/analytics/blended/business-overview" \
  -H "Authorization: Bearer $ANALIFY_TOKEN" \
  --data-urlencode "organizationId=$ORG" \
  --data-urlencode "from=$YESTERDAY" \
  --data-urlencode "to=$YESTERDAY" \
  --data-urlencode "compare=true" \
| python3 -c '
import sys, json
d = json.load(sys.stdin)["data"]
cur, prev = d.get("current", d), d.get("previous") or {}
egp = lambda v: f"EGP {v/100:,.2f}" if isinstance(v, (int, float)) else "n/a"
for k in ("netProfitMinor", "revenueMinor", "adSpendMinor"):
    if k in cur:
        delta = ""
        if k in prev and prev[k]:
            delta = f"  ({(cur[k]-prev[k])/prev[k]*100:+.1f}% vs prior)"
        print(f"{k:20s} {egp(cur[k])}{delta}")
'

Run it before touching anything else — a profit brief that arrives in your inbox
beats a dashboard you have to remember to open. Field names vary by endpoint;
inspect the payload once and pin the ones you need.

Yesterday is a fully-past range, so it's the most stable window to report
on and it caches for 300s.


2. Reconcile Bosta settlements against your own numbers

Decision: how much delivered revenue is still sitting with the courier?

Two different lenses, deliberately:

# Analify's computed settlement view — the cash gap, quantified
curl -s -G "https://analify.scalyax.ai/api/analytics/integration/$BOSTA_INTEGRATION_ID/rich/shipping-cod-settlement" \
  -H "Authorization: Bearer $ANALIFY_TOKEN" \
  --data-urlencode "organizationId=$ORG" \
  --data-urlencode "from=2026-08-01" \
  --data-urlencode "to=2026-08-16"

# Bosta's own cash cycles, untouched — the source of truth for shipping fees
curl -s -G "https://analify.scalyax.ai/api/core/bosta/analytics/cash-cycles" \
  -H "Authorization: Bearer $ANALIFY_TOKEN" \
  --data-urlencode "organizationId=$ORG" \
  --data-urlencode "start_date=2026-08-01" \
  --data-urlencode "end_date=2026-08-16"

Note the parameter names differ — from/to on the Analytics API,
start_date/end_date on the Bosta pass-through, because that's what Bosta
itself accepts. Core API never renames a platform's params.

Cash cycles is where per-delivery shipping fees actually live; the delivery
search endpoint doesn't expose them, because Bosta bills them in batches. If you
need true contribution margin, this is the number to use.

Get $BOSTA_INTEGRATION_ID from /api/core/integrations.


3. Nightly COGS sync from your ERP

Decision: keep profit trustworthy without anyone remembering to update a
spreadsheet.

costPerUnit is piasters — an integer. Sending 125.00 instead of 12500
understates cost 100× and silently inflates every profit number downstream.

# your ERP export → [{sku, productName, costPerUnit}] in piasters
python3 - <<'PY' > /tmp/cogs.json
import csv, json
items = []
with open("/path/to/erp-export.csv") as f:
    for row in csv.DictReader(f):
        items.append({
            "sku": row["sku"],
            "productName": row["name"],
            "costPerUnit": int(round(float(row["unit_cost_egp"]) * 100)),  # → piasters
        })
print(json.dumps({"items": items}))
PY

curl -s -X POST "https://analify.scalyax.ai/api/core/cogs/bulk?organizationId=$ORG" \
  -H "Authorization: Bearer $ANALIFY_WRITE_TOKEN" \
  -H "Content-Type: application/json" \
  --data @/tmp/cogs.json

# then confirm the catalogue is actually covered
curl -s "https://analify.scalyax.ai/api/core/cogs/coverage?organizationId=$ORG" \
  -H "Authorization: Bearer $ANALIFY_WRITE_TOKEN"

This needs a write-scoped token. items must be non-empty — an empty array
is rejected rather than treated as a no-op, because an empty bulk write is
almost always a bug in the caller.

costPerUnit: 0 is valid and is stored — a free sample is a real cost of zero,
not a missing value.


4. Pause what's losing money after returns

Decision: kill the campaigns that only look profitable.

Read with the profit-true lens, then act through the platform:

# 1. Analify's true profit per campaign — after RTO, not platform ROAS
curl -s -G "https://analify.scalyax.ai/api/analytics/ads/true-profit" \
  -H "Authorization: Bearer $ANALIFY_TOKEN" \
  --data-urlencode "organizationId=$ORG" \
  --data-urlencode "from=2026-08-01" \
  --data-urlencode "to=2026-08-16" \
  > /tmp/true-profit.json

# 2. inspect, decide, and only then mutate — Meta's own field, forwarded as-is
curl -s -X POST "https://analify.scalyax.ai/api/core/meta/campaigns/$CAMPAIGN_ID?organizationId=$ORG" \
  -H "Authorization: Bearer $ANALIFY_WRITE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "PAUSED"}'

The shape of this recipe is the point: decide on the Analytics API, act on the
Core API.
A campaign at 3.5× platform ROAS can be underwater once returns are
counted, and Ads Manager will never tell you that.

Step 2 changes a live ad account immediately and has no dry-run. Keep a human in
the loop, or gate it on a threshold you've watched for a couple of weeks.


5. Export orders for finance

Decision: hand accounting the real order list without a CSV download.

Shopify pagination is a cursor, not page numbers:

CURSOR=""
while :; do
  RESP=$(curl -s -G "https://analify.scalyax.ai/api/core/shopify/orders" \
    -H "Authorization: Bearer $ANALIFY_TOKEN" \
    --data-urlencode "organizationId=$ORG" \
    --data-urlencode "first=100" \
    --data-urlencode "query=created_at:>=2026-08-01" \
    ${CURSOR:+--data-urlencode "after=$CURSOR"})

  echo "$RESP" >> /tmp/orders.ndjson

  CURSOR=$(echo "$RESP" | python3 -c '
import sys, json
d = json.load(sys.stdin).get("data", {})
# Core API returns Shopify\'s own body — walk to the connection\'s pageInfo
def find(o):
    if isinstance(o, dict):
        if "pageInfo" in o: return o["pageInfo"]
        for v in o.values():
            r = find(v)
            if r: return r
    return None
pi = find(d) or {}
print(pi.get("endCursor","") if pi.get("hasNextPage") else "")
')
  [ -z "$CURSOR" ] && break
done

Money here is Shopify's unit, not piasters. If you're reconciling against an
Analytics API figure, scale one side before comparing.

For aggregate revenue rather than a row dump, POST /api/core/shopify/analytics/shopifyql
is one call — but note FROM sales returns EGP major units.


Patterns worth copying

  • Decide on Analytics, act on Core. One layer tells you the truth about
    money; the other changes the thing. Don't make decisions from raw platform
    numbers on a COD store.
  • Ask for the comparison, don't compute it. compare=true returns
    { current, previous } in one response. Two calls for two periods is a bug.
  • Prefer fully-past ranges for anything you report on — stable, and cached
    for 300s instead of 60s.
  • Check COGS coverage before quoting profit. A precise-looking number over a
    half-priced catalogue is worse than no number.
  • Read before you write. Every Core API mutation hits a live account with no
    dry-run.

Did this page help you?