App Activity API v1
Emperor pulls registered users and daily activity from applications over a shared contract. Any application can implement it, not only first-party apps. Emperor stores the application as the source, updates data on later syncs, and does not create a duplicate Person when the same human appears in more than one app.
This page is for teams that implement the contract. The machine-readable OpenAPI 3.1 document is the App Activity API spec.
v1 is a draft and may change until Emperor finalizes it. After finalization, breaking changes ship as /v2.
Resources
The application serves three GET resources under /v1, relative to the App Activity API URL:
/v1/manifest— contract version (1), application name, IANA timezone used to bucket activity days, and the activity-key catalog with human-readable labels./v1/users— registered users. Query:updatedSince,cursor,limit./v1/activity— daily aggregates, one row per user, calendar day, and metric key from the catalog. Same query parameters as users.
If contractVersion is not 1, Emperor rejects the connection with an explicit error.
Cursors and activity overwrite
updatedSince is an ISO-8601 datetime with an offset or Z. Return records whose updatedAt is greater than or equal to that value. Omit it for a full export. cursor is an opaque string from the previous page's nextCursor. limit defaults to 100 and must not exceed 100.
List responses are { "data": [...], "nextCursor": null | string }. nextCursor is null when the page is the last.
A later activity row for the same (externalUserId, day, metricKey) replaces the stored count. Applications send daily totals, not raw events.
Email or phone
Each user must include email, phone (E.164, for example +79991234567), or both. A record with neither is rejected by the contract. Emperor counts it as rejected in the sync run and does not create a Person.
How Emperor matches contacts
A person in CRM is Person. Email and telephone are contacts. The application identity lives on a separate link, so one person can come from several apps.
- Email is normalized (trim and lowercase). Phone is stored in E.164.
- An identity already linked to a Person (
LINKED) updates the identity slice and activity only. Emperor does not rename the Person. - A discarded identity is left unmatched.
- A new identity with no matching Person email or phone creates a Person and email / phone contacts.
- Any match against an existing Person, including an email/phone conflict or a second application with the same email, goes to
PENDING_REVIEW. Emperor does not auto-merge people and does not create a second Person.
Reviewing matches
Open CRM → Application matches. The queue lists matches waiting for review. Each row shows the application profile slice, email and phone, candidate Persons, and why they matched (email, phone, both, or an email/phone conflict).
Compare the incoming slice with each candidate's contacts and linked applications, then choose one action:
- Link to a selected candidate. Missing email and phone are added as contacts. The identity becomes
LINKED. Emperor does not create a second Person and does not rename the Person. - Create a new Person, the same way as a sync with no matches. The identity becomes
LINKED. - Discard. The identity becomes
DISCARDED. The next sync does not put it back in the queue. Emperor records who decided and when.
Connection checklist
- Publish
/v1/manifest,/v1/users, and/v1/activityover HTTPS in production. - Issue a high-entropy Bearer token per project–application pair, with read-only rights to this API. Do not reuse a service-wide key.
- Compare tokens in constant time. A request without a valid Bearer returns
401as RFC 7807application/problem+json. Do not tell the caller whether the token was missing or wrong. - Accept
User-Agent: Emperor-AppProvider/1.0andX-Request-Id(UUID). Echo that id in problem documents when you return errors. - On
429, sendRetry-After. - Rotate: keep the previous token valid during a rotation window so sync does not fail, then revoke the old token.
Connecting an application in Emperor
In a Development project open Applications (quick action or Project Settings). Attach an existing publication that already has an App Activity API connection, or create a new publication (name, UID, domain, server, App Activity API URL, token). The token is write-only: the UI later shows only a fingerprint.
On the publication edit and view screens, App activity is the named block for that URL and token. The view shows the URL in full (not “Configured”) and the token fingerprint.
Verify calls GET {baseUrl}/v1/manifest. Sync on a row, or Sync all, pulls users then activity in the same HTTP request (at most five minutes). Detach clears the project link and sync cursors; the publication remains.
Emperor stores the connection, sync cursors, and run history. Sync matches identities in the same run: a Person with no match is created; any existing match is queued for review.
On a Person card, Applications lists linked identities, registration and last-seen dates, and activity for a chosen period and metric. The CRM contacts list can filter to people who have a linked identity from a chosen application.
Limits
- A sync request that runs longer than five minutes is stopped.
- If a sync stays in progress and returns HTTP
409, Clear lock on the Applications row finishes that run asFAILEDwithout moving cursors. Then run Sync again. - Activity for a user rejected because they had no email or phone does not come back after the profile is fixed until that activity row's
updatedAtchanges. A full re-export requires detaching and attaching the application again.
Security and application audit log
v1 is HTTPS plus a Bearer token on the project–application pair.
Recommended: log each App Activity API request with time, source IP, X-Request-Id, User-Agent, resource (manifest / users / activity), updatedSince, HTTP status, and the number of records returned. Do not log the token, the Authorization header, email, phone, or response bodies.
Also recommended: a token used only for this API; a rate limit; alerts on a burst of 401, a foreign User-Agent, an unexpected IP, or a sudden volume jump. Do not require an IP allowlist: Emperor's outbound addresses are not a single fixed IP.