Skip to content

All articles

Provider 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 Provider API spec. Generate client types from that path on the same origin as this site.

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 baseUrl stored on the Facility connection:

  • /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 Contact rows (types EMAIL and TELEPHONE). The application identity lives on a separate link, so one person can come from several apps.

  • Email is normalized. Phone is stored in E.164.
  • An identity already linked to a Person is updated.
  • A new identity with no matching email or phone creates a Person.
  • Any match against an existing Person, including an email/phone conflict, goes to a manual review queue. Emperor does not auto-merge people.

Connection checklist

  1. Publish /v1/manifest, /v1/users, and /v1/activity over HTTPS in production.
  2. Issue a high-entropy Bearer token per project–application pair, with read-only rights to this API. Do not reuse a service-wide key.
  3. Compare tokens in constant time. A request without a valid Bearer returns 401 as RFC 7807 application/problem+json. Do not tell the caller whether the token was missing or wrong.
  4. Accept User-Agent: Emperor-AppProvider/1.0 and X-Request-Id (UUID). Echo that id in problem documents when you return errors.
  5. On 429, send Retry-After.
  6. Rotate: keep the previous token valid during a rotation window so sync does not fail, then revoke the old token.
  7. In local development, Emperor allows plain HTTP baseUrl only when APP_PROVIDER_ALLOW_PLAIN_HTTP=true (set by the frontend dev script). Production accepts HTTPS only.

Security and application audit log

v1 is HTTPS plus a Bearer token on the project–application pair. HMAC, mTLS, and OAuth2 are not part of this version.

Recommended (not a schema requirement): log each Provider 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 egress in Kubernetes is not a fixed address.

Articles