Skip to documentation
Open dashboard
Docs/Server integrations

Hosted account and dashboard API

The session-based API used by the dashboard and CherrySub CLI.

Download this chapter
On this page

The hosted API serves the dashboard and CherrySub CLI. It uses the opaque JSESSIONID cookie, verified account identity, selected project, app ownership, and role checks. It is separate from the publishable/secret-key runtime API and from root /admin/v1 administration.

This chapter covers the reviewed request shapes and the complete route inventory. Its JSON field names are preserved exactly: hosted responses commonly use camelCase, many write bodies use snake_case, and saved-chart bodies use camelCase. Do not apply a global casing conversion.

Authentication and session context

POST /auth/login accepts email and password, sets JSESSIONID, and returns email-verification state. POST /auth/register also takes name, surname, and an optional safe local callbackUrl. Registering/signing in is not equivalent to verified dashboard access.

Verification confirmation and password reset use their bounded-lifetime account tokens. The verification-request flow can include a local callbackUrl. Logout revokes the current session; logout-all revokes the account's sessions. Account/session management follows the account guide.

For normal automation, use CherrySub CLI so it manages private session storage and project selection. If you integrate the HTTP interface directly, keep cookies private, refuse credential-bearing redirects, and serialize selected-project changes for a shared session.

Authorization rules

Dashboard operations require a verified session and membership in the selected project. Resource IDs are checked against project/app ownership, not trusted merely because a caller supplies a UUID.

App/catalog/key/store/webhook configuration and support-note writes require owner/admin authority. Team changes have additional owner/admin target restrictions. Invitation-management reads also require manager authority. Saved views/charts and exports bind to the requesting account and project membership rather than being shared configuration writes.

POST /dashboard/projects/{project_id}/members intentionally rejects direct member addition. Use invitation creation and acceptance instead. A viewer cannot bypass a disabled dashboard button by calling the write route directly.

Project and app setup example

The project-creation body is:

json
{ "name": "My project" }

After creation or GET /dashboard/projects, select the intended project with POST /dashboard/projects/{project_id}/select. It changes session context and returns no content.

To create an app in that hosted project, use POST /dashboard/apps:

json
{
  "name": "My app",
  "apple_bundle_id": "com.example.app",
  "apple_app_apple_id": 1234567890,
  "google_package_name": "com.example.app"
}

Use only the identities your app supports. The hosted response contains app, secretKey, and publishableKey; the keys are one-time plaintext. This differs from the root-admin app response. Store and manage those values through the correct server/client boundaries.

Managed store credential bodies

PUT /dashboard/apps/{app_id}/store-connections/apple accepts issuer_id, key_id, and private_key_pem. Google accepts service_account_json as an embedded JSON object, not a string containing escaped JSON. The platform must be apple or google.

The list/PUT response exposes safe metadata: platform, source, account hint, and timestamps. DELETE disconnects the app's server credential use. managed and environment are credential sources; neither is proof that a live purchase passed.

The hosted JSON body limit is 32 KiB, except the two catalog-import endpoints, which allow a 1 MiB JSON envelope containing a source file up to 512 KiB. The Google credential validator's larger 64 KiB allowance is therefore not the effective maximum HTTP upload size; escaping/envelope overhead also counts. Store setup · Limits.

Dataset and chart queries

Customer, transaction, and event list requests support their resource-specific search/filter/sort fields through the hosted data-input parser. Common fields include query, platform, environment, status, type, page, and page_size; time/product/store filters depend on the resource. Default page size is 50, maximum 100. Unsupported filters or invalid pagination are rejected rather than applied to an unrelated dataset.

GET /dashboard/charts requires an app_id and a valid metric/range configuration:

Query fieldPurpose
app_idApp UUID belonging to the selected project
metricOne metric ID from the metric dictionary
daysSupported relative range when custom dates are absent
from, toInclusive custom UTC dates
resolutionday, week, or month within range limits
offset_daysSupported comparison offset
platform, environment, product_idApplicable data filters
retention_periodWeekly/monthly/yearly duration selection for retention
conversion_timeframeCustomer-conversion observation window
customer_lifetimeRealized LTV window
cancellation_windowTrial opt-out window
refund_request_reasonRefund review reason filter
cohort_definition, cohort_measureCohort matrix anchor/measure
currencyMonetary cohort selection where applicable

Do not send unrelated metric-specific settings as if every combination were supported. Charts are bounded interactive aggregates; they are not a full export API. Product detail requests may need app_id to disambiguate a product reused by multiple apps.

Export and saved-analysis examples

Create an export with POST /dashboard/exports:

json
{
  "app_id": "11111111-1111-4111-8111-111111111111",
  "resource": "transactions",
  "from_date": "2026-08-01",
  "to_date": "2026-08-31",
  "platform": "apple",
  "environment": "sandbox",
  "product_id": "pro.monthly"
}

The service returns 202 and a job record. List your jobs with GET /dashboard/exports?app_id=.... The Location header identifies the job; the current router does not expose a general GET-by-job-ID endpoint there. Use the list/status and emailed download flow.

Saved views take name, resource (customers, transactions, or events), and a JSON filters object. Saved charts take the complete camelCase chart configuration shown in their body contract below. Both are requester-scoped; the API does not make another person's saved analysis writable merely because you know its ID.

Errors and HTTP behavior

Hosted errors use message; account-link failures may also have code, such as invalid_or_expired_token, email_verification_required, or invitation_email_mismatch. Common statuses are 400 invalid input, 401 invalid session/credentials, 403 verification/role failure, 404 scoped resource missing, 409 lifecycle conflict, 429 throttling, and 503 delivery/dependency unavailable.

The generic invalid-input message can say “Invalid account details” even for a dashboard write. Interpret it with the operation's body contract, not as proof that your account email is wrong.

Send Content-Type: application/json for JSON bodies. Strict decoding rejects unsupported fields/trailing JSON. The cookie API uses its own auth/account throttles; runtime receipt-key throttling is a different boundary. The control plane has a process-local /healthz; the public staging proxy's health path can route to the core instead.

Catalog import and store product reads

Verified owner/admin accounts can call the app-scoped catalog routes. POST /dashboard/apps/{app_id}/catalog-import/preview accepts format (csv or json), string content, and optional activate_current. It returns a read-only plan with normalized catalog, counts, changes, issues, notes, canApply, and digest. Apply sends the same fields to /catalog-import/apply plus expected_digest from the preview. A changed source, scope, or target returns HTTP 409; refresh the preview before retrying. Application is atomic and repeated successful requests return their saved result. GET /dashboard/apps/{app_id}/catalog-imports returns recent applied summaries.

POST /dashboard/apps/{app_id}/store-products/{platform} accepts an optional opaque cursor and returns products, optional nextCursor, and notes. Read every page before applying a selected catalog. Platforms are apple and google. These requests do not create products in the store or write the destination catalog.

The separate Apple catalog connection lives at /dashboard/apps/{app_id}/catalog-connection/apple: GET returns safe metadata, PUT takes issuer_id, key_id, and private_key_pem, and DELETE removes it. The .p8 is an App Store Connect API key, not the IAP key. Both store listing and credential routes require manager authority and destination project/app ownership. Secret material is encrypted and never returned. Google catalog listing uses its existing managed service account. Migration flow.

Complete route index

This index is extracted from the hosted router at the reviewed backend revision. Body contracts are listed in the following section. Read the authorization notes above: not every write is a manager operation, and not every read exposes manager-only metadata to a viewer.

MethodRouteBody contract
GET/healthzSee operation notes / no named JSON body
POST/auth/registerregisterRequest
POST/auth/loginloginRequest
POST/auth/logoutSee operation notes / no named JSON body
POST/auth/logout-allSee operation notes / no named JSON body
GET/auth/accountSee operation notes / no named JSON body
POST/auth/verify-email/requestSee operation notes / no named JSON body
POST/auth/verify-email/confirmaccountTokenRequest
POST/auth/password-reset/requestpasswordResetRequest
POST/auth/password-reset/confirmresetPasswordRequest
POST/auth/password/changechangePasswordRequest
GET/auth/sessionsSee operation notes / no named JSON body
DELETE/auth/sessions/{session_id}See operation notes / no named JSON body
GET/auth/project-invitations/previewSee operation notes / no named JSON body
POST/auth/project-invitations/acceptaccountTokenRequest
GET/dashboardSee operation notes / no named JSON body
GET/dashboard/chartsSee operation notes / no named JSON body
GET/dashboard/exportsSee operation notes / no named JSON body
POST/dashboard/exportsexportJobRequest
GET/dashboard/projectsSee operation notes / no named JSON body
POST/dashboard/projectsprojectRequest
PATCH/dashboard/projects/{project_id}projectRequest
POST/dashboard/projects/{project_id}/selectSee operation notes / no named JSON body
GET/dashboard/projects/{project_id}/membersSee operation notes / no named JSON body
POST/dashboard/projects/{project_id}/membersSee operation notes / no named JSON body
PATCH/dashboard/projects/{project_id}/members/{user_id}projectMemberRequest
DELETE/dashboard/projects/{project_id}/members/{user_id}See operation notes / no named JSON body
GET/dashboard/projects/{project_id}/invitationsSee operation notes / no named JSON body
POST/dashboard/projects/{project_id}/invitationsprojectMemberRequest
DELETE/dashboard/projects/{project_id}/invitations/{invitation_id}See operation notes / no named JSON body
GET/dashboard/projects/{project_id}/activitySee operation notes / no named JSON body
GET/dashboard/customersSee operation notes / no named JSON body
GET/dashboard/customers/{customer_id}See operation notes / no named JSON body
POST/dashboard/customers/{customer_id}/notescustomerSupportNoteRequest
PATCH/dashboard/customers/{customer_id}/notes/{note_id}customerSupportNoteRequest
DELETE/dashboard/customers/{customer_id}/notes/{note_id}See operation notes / no named JSON body
GET/dashboard/eventsSee operation notes / no named JSON body
GET/dashboard/transactionsSee operation notes / no named JSON body
GET/dashboard/transactions/{event_id}See operation notes / no named JSON body
GET/dashboard/products/{platform}/{product_id}See operation notes / no named JSON body
GET/dashboard/access-levels/{access_level_id}See operation notes / no named JSON body
POST/dashboard/apps/{app_id}/access-levelsaccessLevelRequest
GET/dashboard/apps/{app_id}/product-access-mappingsSee operation notes / no named JSON body
POST/dashboard/apps/{app_id}/product-access-mappingsproductAccessMappingRequest
DELETE/dashboard/apps/{app_id}/product-access-mappings/{mapping_id}See operation notes / no named JSON body
GET/dashboard/integrations/{kind}See operation notes / no named JSON body
GET/dashboard/appsSee operation notes / no named JSON body
POST/dashboard/appscreateAppRequest
GET/dashboard/apps/artworkSee operation notes / no named JSON body
GET/dashboard/apps/{app_id}See operation notes / no named JSON body
PATCH/dashboard/apps/{app_id}createAppRequest
GET/dashboard/apps/{app_id}/store-connectionsSee operation notes / no named JSON body
PUT/dashboard/apps/{app_id}/store-connections/{platform}appleConnectionRequest, googleConnectionRequest
DELETE/dashboard/apps/{app_id}/store-connections/{platform}See operation notes / no named JSON body
GET/dashboard/apps/{app_id}/api-keysSee operation notes / no named JSON body
POST/dashboard/apps/{app_id}/api-keysapiKeyRequest
DELETE/dashboard/apps/{app_id}/api-keys/{key_id}See operation notes / no named JSON body
GET/dashboard/apps/{app_id}/product-setsSee operation notes / no named JSON body
POST/dashboard/apps/{app_id}/product-setsproductSetRequest
PATCH/dashboard/apps/{app_id}/product-sets/{product_set_id}updateProductSetRequest
DELETE/dashboard/apps/{app_id}/product-sets/{product_set_id}See operation notes / no named JSON body
POST/dashboard/apps/{app_id}/product-sets/{product_set_id}/packagesproductSetPackageRequest
DELETE/dashboard/apps/{app_id}/product-sets/{product_set_id}/packages/{package_id}See operation notes / no named JSON body
GET/dashboard/apps/{app_id}/webhooksSee operation notes / no named JSON body
POST/dashboard/apps/{app_id}/webhookswebhookEndpointRequest
PATCH/dashboard/apps/{app_id}/webhooks/{endpoint_id}updateWebhookEndpointRequest
DELETE/dashboard/apps/{app_id}/webhooks/{endpoint_id}See operation notes / no named JSON body
POST/dashboard/apps/{app_id}/webhooks/{endpoint_id}/rotate-secretSee operation notes / no named JSON body
GET/dashboard/apps/{app_id}/webhook-deliveriesSee operation notes / no named JSON body
GET/dashboard/apps/{app_id}/webhook-deliveries/{message_id}See operation notes / no named JSON body
POST/dashboard/apps/{app_id}/webhook-deliveries/{message_id}/retrySee operation notes / no named JSON body
GET/dashboard/saved-viewsSee operation notes / no named JSON body
POST/dashboard/saved-viewscreateSavedViewRequest
PATCH/dashboard/saved-views/{view_id}createSavedViewRequest
DELETE/dashboard/saved-views/{view_id}See operation notes / no named JSON body
GET/dashboard/saved-views/{view_id}/resultsSee operation notes / no named JSON body
GET/dashboard/saved-chartsSee operation notes / no named JSON body
POST/dashboard/saved-chartssavedChartRequest
PATCH/dashboard/saved-charts/{chart_id}savedChartRequest
DELETE/dashboard/saved-charts/{chart_id}See operation notes / no named JSON body
POST/dashboard/apps/{app_id}/store-products/{platform}See operation notes / no named JSON body
GET/dashboard/apps/{app_id}/catalog-connection/appleSee operation notes / no named JSON body
PUT/dashboard/apps/{app_id}/catalog-connection/appleappleConnectionRequest
DELETE/dashboard/apps/{app_id}/catalog-connection/appleSee operation notes / no named JSON body
POST/dashboard/apps/{app_id}/catalog-import/previewCatalogImportInput
POST/dashboard/apps/{app_id}/catalog-import/applyCatalogImportInput
GET/dashboard/apps/{app_id}/catalog-importsSee operation notes / no named JSON body

Hosted request body fields

These field names and transport types are extracted from the handler DTOs. Required/allowed combinations and lifecycle permissions are validated by the service; a zero-value Go field is not a declaration that the field is optional. This table deliberately does not invent a formal OpenAPI contract for the hosted interface. JSON value means an embedded object/value, not a stringified JSON blob.

registerRequest

JSON fieldTransport type
callbackUrlstring
namestring
surnamestring
emailstring
passwordstring

loginRequest

JSON fieldTransport type
emailstring
passwordstring

accountTokenRequest

JSON fieldTransport type
tokenstring

passwordResetRequest

JSON fieldTransport type
emailstring

resetPasswordRequest

JSON fieldTransport type
tokenstring
newPasswordstring

changePasswordRequest

JSON fieldTransport type
currentPasswordstring
newPasswordstring

exportJobRequest

JSON fieldTransport type
app_idUUID string
resourcestring
from_datestring
to_datestring
platformstring
environmentstring
product_idstring

customerSupportNoteRequest

JSON fieldTransport type
bodystring

accessLevelRequest

JSON fieldTransport type
lookup_keystring
display_namestring

productAccessMappingRequest

JSON fieldTransport type
platformstring
product_idstring
access_level_idUUID string
grant_modestring

projectRequest

JSON fieldTransport type
namestring

projectMemberRequest

JSON fieldTransport type
emailstring
rolestring

createAppRequest

JSON fieldTransport type
namestring
apple_bundle_idstring; pointer field supports omission
apple_app_apple_idinteger (int64); pointer field supports omission
google_package_namestring; pointer field supports omission

appleConnectionRequest

JSON fieldTransport type
issuer_idstring
key_idstring
private_key_pemstring

googleConnectionRequest

JSON fieldTransport type
service_account_jsonJSON value

apiKeyRequest

JSON fieldTransport type
kindstring
labelstring

productSetRequest

JSON fieldTransport type
identifierstring
display_namestring
is_currentboolean

updateProductSetRequest

JSON fieldTransport type
is_currentboolean

productSetPackageRequest

JSON fieldTransport type
identifierstring
package_typestring
platformstring
product_idstring

webhookEndpointRequest

JSON fieldTransport type
urlstring
descriptionstring
event_typesarray of strings

updateWebhookEndpointRequest

JSON fieldTransport type
urlstring; pointer field supports omission
descriptionstring; pointer field supports omission
event_typesarray of strings; pointer field supports omission
enabledboolean; pointer field supports omission

createSavedViewRequest

JSON fieldTransport type
namestring
resourcestring
filtersJSON value

savedChartRequest

JSON fieldTransport type
appIdUUID string
namestring
metricstring
daysinteger
fromDatestring
toDatestring
resolutionstring
compareModestring
platformstring
environmentstring
productIdstring
chartTypestring
segmentstring
currencystring
retentionPeriodstring
retentionModestring
conversionTimeframestring
customerLifetimestring
cancellationWindowstring
refundRequestReasonstring
cohortDefinitionstring
cohortMeasurestring

CatalogImportInput

JSON fieldTransport type
formatstring
contentstring
activate_currentbool
expected_digeststring