Apps
CRUD endpoints for managing apps — create, list, get, update, and delete apps within projects.
Create, list, get, update, and delete apps within projects.
Apps represent a specific platform build (e.g., iOS, Android, Web) within a project. Each app has its own client API key for SDK event ingestion.
GET /v1/apps
List all apps the caller has access to.
Auth required: Yes (apps:read permission or JWT)
| Parameter | Type | Description |
|---|---|---|
team_id | string | Optional. Filter to a specific team. |
// Response (200)
{
"apps": [
{
"id": "uuid",
"team_id": "uuid",
"project_id": "uuid",
"name": "iOS App",
"platform": "apple",
"bundle_id": "com.example.myapp",
"client_secret": "owl_client_abc123...",
"worldwide_rating_count": 1234,
"worldwide_rating_count_delta": 8,
"created_at": "2024-01-01T00:00:00.000Z"
}
]
}For Apple-platform apps, each row also carries the denormalized worldwide ratings rollup from the app_store_ratings_sync job: worldwide_average_rating, worldwide_rating_count, worldwide_current_version_rating, worldwide_current_version_rating_count, and ratings_synced_at. worldwide_rating_count_delta is the change in worldwide_rating_count since the previous daily snapshot — sum across countries of (latest − previous) rating_count, only counting countries with a prior snapshot. Tombstones contribute negatively; brand-new countries contribute 0. null when no app/country has prior data. Non-Apple platforms return null for all rating fields.
GET /v1/apps/:id
Get a single app by ID.
Auth required: Yes (apps:read permission or JWT)
// Response (200)
{
"id": "uuid",
"team_id": "uuid",
"project_id": "uuid",
"name": "iOS App",
"platform": "apple",
"bundle_id": "com.example.myapp",
"client_secret": "owl_client_abc123...",
"created_at": "2024-01-01T00:00:00.000Z"
}POST /v1/apps
Create a new app within a project. A client API key is automatically generated and returned in the client_secret field.
Auth required: Yes (apps:write permission or JWT, admin role required)
// Request
{
"name": "iOS App",
"platform": "apple",
"bundle_id": "com.example.myapp",
"project_id": "uuid"
}
// Response (201)
{
"id": "uuid",
"team_id": "uuid",
"project_id": "uuid",
"name": "iOS App",
"platform": "apple",
"bundle_id": "com.example.myapp",
"client_secret": "owl_client_full_key...",
"created_at": "2024-01-01T00:00:00.000Z"
}Field reference
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for the app. |
platform | string | Yes | One of: apple, android, web, backend. |
bundle_id | string | Conditional | Required for non-backend platforms. Immutable after creation. |
project_id | string | Yes | The project this app belongs to. Team is derived from the project. |
Platform notes
- Backend apps do not require a
bundle_id(it is set tonull). The ingest route skips bundle_id validation for these apps. - bundle_id is immutable. It cannot be changed after app creation. To change a bundle_id, delete and recreate the app.
PATCH /v1/apps/:id
Update an app. Only the name field can be changed.
Auth required: Yes (apps:write permission or JWT, admin role required)
// Request
{
"name": "Renamed App"
}
// Response (200)
{
"id": "uuid",
"team_id": "uuid",
"project_id": "uuid",
"name": "Renamed App",
"platform": "apple",
"bundle_id": "com.example.myapp",
"client_secret": "owl_client_abc123...",
"created_at": "2024-01-01T00:00:00.000Z"
}DELETE /v1/apps/:id
Soft-delete an app and its associated API keys.
Auth required: Yes (JWT only, admin role required. Agent keys receive 403.)
// Response (200)
{
"deleted": true
}GET /v1/apps/:id/users
List users seen by a specific app, ordered by most recently seen.
Auth required: Yes (apps:read permission or JWT)
| Parameter | Type | Description |
|---|---|---|
search | string | Filter by user_id substring (case-insensitive). |
is_anonymous | string | "true" for anonymous users only, "false" for identified users only. |
billing_status | string | Filter by billing tier (comma-separated). Values: free, trial, paid. Derived from RevenueCat integration properties. |
cursor | string | Cursor from previous response. |
limit | number | Items per page (1-200, default 50). |
// Response (200)
{
"users": [
{
"id": "uuid",
"project_id": "uuid",
"user_id": "owl_anon_abc123",
"is_anonymous": true,
"first_seen_at": "2024-01-01T00:00:00.000Z",
"last_seen_at": "2024-01-15T10:30:00.000Z",
"last_country_code": "US",
"last_app_version": "1.2.0",
"last_sdk_name": "owlmetry-swift",
"last_sdk_version": "0.1.0",
"claimed_from": null,
"properties": {},
"apps": [
{
"app_id": "uuid",
"app_name": "iOS App",
"first_seen_at": "2024-01-01T00:00:00.000Z",
"last_seen_at": "2024-01-15T10:30:00.000Z"
}
]
}
],
"cursor": "2024-01-14T08:00:00.000Z",
"has_more": false
}App users are project-scoped: the project_id identifies the project, and the apps array lists each app within that project that has seen this user. A user seen from multiple apps in the same project appears once, not once per app.
last_country_code is a 2-letter ISO country code derived server-side from the Cloudflare CF-IPCountry header on the most recent ingest request that carried one. It is never sent by SDKs, and is null until a request with a recognised country header arrives. Users seen only through backend apps stay null — the country header is dropped at ingest for backend apps because the request originates from a hosting datacenter rather than the end user. See Events › Country.
last_app_version is the app_version string from the most recent event ingested for this user. It mirrors the last_country_code pattern — updated on every batch from the incoming events — and is null until an event with a non-empty app_version arrives.
last_sdk_name and last_sdk_version are the SDK identifier and version from the most recent event ingested for this user (e.g. owlmetry-swift / 0.1.0). Auto-stamped by official SDKs and updated alongside last_app_version. Useful for spotting users on stale SDK versions.
GET /v1/app-users
List users across apps/projects/teams. Accepts the same filters as /v1/apps/:id/users plus team_id, project_id, app_id, since, and until for cross-app queries.
Auth required: Yes (apps:read permission or JWT)
Query parameters
| Parameter | Type | Description |
|---|---|---|
team_id | string | Filter to a specific team. |
project_id | string | Filter to a specific project. |
app_id | string | Filter to users who have been seen from a specific app. |
search | string | Filter by user_id substring (case-insensitive). |
is_anonymous | string | "true" for anonymous users only, "false" for identified users only. |
billing_status | string | Comma-separated billing tiers: free, trial, paid. |
since | string | Filter by last_seen_at >= since. Relative or ISO 8601. |
until | string | Filter by last_seen_at <= until. Relative or ISO 8601. |
cursor | string | Cursor from previous response. |
limit | number | Items per page (1-200, default 50). |
Response shape is identical to /v1/apps/:id/users.
