Skip to content
Open Portal

REST API

The management API behind the Developer Portal.

Applies to Appylar v1.0

The Appylar platform API is the REST API behind the Developer Portal. Everything the portal does — apps, campaigns, creatives, analytics and billing — is available here. This page documents every endpoint that exists in v1. There are no fictional or future endpoints.

#Conventions

  • Base URL: all routes are under the /v1 prefix, e.g. https://api.appylar.com/v1/campaigns.
  • Auth: send your token as Authorization: Bearer <token>. Get one from register or login.
  • Content type: send Accept: application/json; send JSON bodies as Content-Type: application/json (creative upload uses multipart/form-data).
  • Tenancy: every request is scoped to your organization. Rows from other organizations are invisible, so requesting one returns 404.
curl https://api.appylar.com/v1/me \
  -H "Authorization: Bearer <token>" \
  -H "Accept: application/json"

#Errors

StatusMeaning
401Missing or invalid token
403Admin-only endpoint, non-admin caller
404Not found (or not in your organization)
422Validation failed
429Rate limit exceeded

Validation errors use the standard Laravel shape:

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

Collections that paginate (apps, campaigns) return { "data": [...], "links": {...}, "meta": {...} } at 15 per page. A single resource returns { "data": {...} }. Auth, reports and billing reads return raw JSON (no data wrapper), as noted per endpoint.

#Rate limits & stability

Auth, token, serve and events endpoints are rate-limited per minute; exceeding a limit returns 429 — back off and retry. This is v1: the surface documented here is stable, and any breaking change will be announced in the Changelog with advance notice and a migration note before it ships. Build against the documented contract and you won't be surprised.

#Authentication

#POST /v1/auth/register

Create a user, its default organization, and return an API token.

FieldRules
namerequired, string, max 255
emailrequired, email, unique
passwordrequired, min 8
organization_nameoptional, string, max 255
curl -X POST https://api.appylar.com/v1/auth/register \
  -H "Accept: application/json" \
  -d "name=Ada Lovelace&email=ada@studio.com&password=secret123"
{
  "token": "1|abc...",
  "user": {
    "id": 1, "name": "Ada Lovelace", "email": "ada@studio.com",
    "is_admin": false,
    "organization": { "id": 1, "name": "Ada Lovelace's Organization" }
  }
}

Returns 201. Errors: 422, 429.

#POST /v1/auth/login

Exchange email + password for a token. Body: email (required), password (required). Returns 200 with the same shape as register. Bad credentials return 422.

#POST /v1/auth/logout

Revoke the current token. Requires auth. Returns 200 { "message": "Logged out." }.

#GET /v1/me

The current user and their organization. Requires auth.

{ "user": { "id": 1, "name": "Ada Lovelace", "email": "ada@studio.com", "is_admin": false, "organization": { "id": 1, "name": "…" } } }

#Organization

#GET /v1/orgs/current

Your current organization. Returns raw { "id": 1, "name": "…", "network_opt_in": true }.

#PATCH /v1/orgs/current

Update org settings. Body: network_opt_in (optional, boolean). Returns the updated organization.

#Apps

An app resource:

{
  "id": 1, "name": "My Game", "platform": "android",
  "store_url": null, "category": null,
  "app_key": "app_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "network_opt_in": true,
  "created_at": "…", "updated_at": "…"
}

#GET /v1/apps

List your apps (paginated, 15/page, newest first).

#POST /v1/apps

Create an app; the app_key is generated for you.

FieldRules
namerequired, string, max 255
platformoptional, android
store_urloptional, url, max 2048
categoryoptional, string, max 255
network_opt_inoptional, boolean

Returns 201 { "data": App }.

#GET /v1/apps/{id}

Show one app. Returns { "data": App } or 404.

#PATCH /v1/apps/{id}

Update name, store_url, category or network_opt_in (all optional). platform can't be changed. Returns the updated app.

#POST /v1/apps/{id}/key/rotate

Issue a new app_key, invalidating the previous one immediately. Returns the app with its new key.

Rotating a key breaks any released build still using the old one until you ship an update. See Troubleshooting.

#Campaigns

A campaign resource:

{
  "id": 1, "name": "Summer promo", "status": "draft",
  "reach_type": "network",
  "target_geo": ["US", "GB"], "frequency_cap_per_day": 3,
  "promoted_app_id": 1, "promoted_app": { "id": 1, "name": "My Game" },
  "created_at": "…", "updated_at": "…"
}

#GET /v1/campaigns

List campaigns (paginated, 15/page).

#POST /v1/campaigns

Create a campaign. Starts in draft.

FieldRules
namerequired, string, max 255
promoted_app_idrequired, must be one of your apps
reach_typerequired, portfolio or network
target_geooptional, array of ISO-3166 alpha-2 codes (US, GB, …)
frequency_cap_per_dayoptional, int 1–1000
curl -X POST https://api.appylar.com/v1/campaigns \
  -H "Authorization: Bearer <token>" -H "Accept: application/json" \
  -d "name=Summer promo&promoted_app_id=1&reach_type=network" \
  --data-urlencode "target_geo[0]=US"

Returns 201 { "data": Campaign }.

#GET /v1/campaigns/{id}

Show one campaign. Returns { "data": Campaign } or 404.

#PATCH /v1/campaigns/{id}

Partial update — same fields as create, all optional. status is not settable here (use activate/pause).

#DELETE /v1/campaigns/{id}

Delete a campaign. Returns 204. An active campaign can't be deleted — pause it first, or you get 422:

{ "errors": { "status": ["An active campaign cannot be deleted; pause it first."] } }

#POST /v1/campaigns/{id}/activate

Move draft/pausedactive. Returns the campaign. 422 if already active.

#POST /v1/campaigns/{id}/pause

Move activepaused. Returns the campaign. 422 if not active.

#Creatives

Each campaign has at most one creative. A creative resource:

{
  "id": 1, "campaign_id": 1,
  "moderation_status": "pending", "moderation_reason": null,
  "original_url": "https://…/original.png",
  "variants": [
    { "format": "banner", "width": 320, "height": 50, "url": "https://…" },
    { "format": "interstitial", "width": 320, "height": 480, "url": "https://…" }
  ],
  "created_at": "…", "updated_at": "…"
}

#GET /v1/campaigns/{id}/creative

Show the campaign's creative. Returns { "data": Creative }, or 404 if the campaign has no creative yet.

#POST /v1/campaigns/{id}/creative

Upload or replace the creative (multipart/form-data).

FieldRules
imagerequired, image, mimes jpeg,jpg,png,webp,gif, max 5120 KB
curl -X POST https://api.appylar.com/v1/campaigns/1/creative \
  -H "Authorization: Bearer <token>" -H "Accept: application/json" \
  -F "image=@banner.png"

Returns 201 on first upload, 200 when replacing. See Creatives.

#DELETE /v1/campaigns/{id}/creative

Delete the creative. Returns 204.

#Reports

#GET /v1/reports

Aggregated impressions, clicks, installs and derived CTR.

QueryRules
dimensiontotal (default), app, campaign, day
fromYYYY-MM-DD (default: 29 days ago)
toYYYY-MM-DD, ≥ from (default: today)
app_idoptional filter
campaign_idoptional filter
curl "https://api.appylar.com/v1/reports?dimension=day&from=2026-07-01&to=2026-07-15" \
  -H "Authorization: Bearer <token>" -H "Accept: application/json"
{
  "dimension": "day", "from": "2026-07-01", "to": "2026-07-15",
  "rows": [
    { "date": "2026-07-01", "impressions": 1800, "clicks": 48, "installs": 6, "ctr": 0.0267 }
  ]
}

app rows add app_id + label; campaign rows add campaign_id + label; total is a single summed row. ctr is clicks / impressions rounded to 4 decimals (0 when there are no impressions). See Analytics.

#Billing

Everything is measured in impressions. Network serving draws from the current period's plan allowance first, then the top-up balance. See Billing for the model.

#GET /v1/billing/summary

The current picture: plan, this period's allowance (granted/used/remaining), top-up balance and total available. Raw JSON.

{
  "plan": { "key": "growth", "name": "Growth", "monthly_impressions": 500000 },
  "subscription": { "status": "active", "current_period_start": "…", "current_period_end": "…", "cancel_at_period_end": false },
  "allowance": { "granted": 500000, "used": 148200, "remaining": 351800 },
  "topup_balance": 47000,
  "total_available": 398800
}

#GET /v1/billing/plans · GET /v1/billing/packs

The active plan catalog and top-up pack catalog. Each returns { "data": [ … ] }.

#POST /v1/billing/subscribe

Start a subscription checkout. Body: plan (required, an active plan key). Returns raw { "url": "https://checkout…" } — redirect the user there.

#POST /v1/billing/top-up

Start a one-off top-up checkout. Body: pack (required, an active pack key). Returns raw { "url": "https://checkout…" }.

#GET /v1/billing/balance

The top-up balance only: raw { "balance": 47000 }. (The full picture is in /billing/summary.)

#GET /v1/billing/transactions

The latest 100 top-up ledger entries.

{ "data": [
  { "id": 2, "delta": -320, "reason": "network_impression", "balance_after": 46680, "created_at": "…" },
  { "id": 1, "delta": 50000, "reason": "purchase", "balance_after": 47000, "created_at": "…" }
] }

reason is purchase (top-up bought) or network_impression (impressions drawn from the top-up balance).

#Data plane (used by the SDKs)

These endpoints are what the SDKs call internally. You normally don't call them directly — they're documented for completeness. Together they form the serving protocol; each request carries the Appylar-Protocol and Appylar-SDK headers (both optional and ignored if absent — legacy clients keep working).

  • POST /v1/token — exchange an app_key for a short-lived serving token ({ "token": "…", "expires_in": 3600 }). Public.
  • GET /v1/sdk/config — SDK config after init, including protocol_version. Serving token required.
  • POST /v1/serve — return at most one eligible ad, or { "fill": false }. Accepts optional device_id, os (android/ios) and store (e.g. google_play, apple_app_store, amazon_appstore) to route the ad's destination_url to the right store listing. Network serves consume 1 impression from the advertiser (allowance first, then top-up). Serving token required.
  • POST /v1/events — batch impression/click/install events (idempotent). Serving token required.

See the Serving Protocol for the full field reference, store-routing rules, versioning and backward-compatibility guarantees.

#Admin

Moderation endpoints under /v1/admin/* require an administrator account (403 otherwise) and are operated by the Appylar team — creatives are approved or rejected here. They aren't part of the developer-facing surface.