{"openapi":"3.1.0","info":{"title":"Carillon API","version":"1.0.0"},"components":{"schemas":{"HealthResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"surface":{"type":"string","enum":["public","internal"]},"revision":{"type":"string","nullable":true,"description":"Build commit SHA. Null if not configured."}},"required":["status","surface","revision"]},"SavedAudienceList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SavedAudience"}}},"required":["data"]},"SavedAudience":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"definition":{"$ref":"#/components/schemas/SavedAudienceDefinition"},"created_at":{"type":"string","format":"date-time"}},"required":["id","name","definition","created_at"]},"SavedAudienceDefinition":{"type":"object","properties":{"filters":{"type":"array","items":{"$ref":"#/components/schemas/SavedAudienceFilter"},"maxItems":20,"description":"All filters must match. An empty list matches all eligible devices in the app."}},"required":["filters"]},"SavedAudienceFilter":{"anyOf":[{"type":"object","properties":{"field":{"type":"string","enum":["platform"]},"value":{"type":"string","enum":["ios","android"]}},"required":["field","value"]},{"type":"object","properties":{"field":{"type":"string","enum":["number_tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"value":{"type":"number"},"gt":{"type":"number"},"gte":{"type":"number"},"lt":{"type":"number"},"lte":{"type":"number"},"between":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["user_number_tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"value":{"type":"number"},"gt":{"type":"number"},"gte":{"type":"number"},"lt":{"type":"number"},"lte":{"type":"number"},"between":{"type":"array","items":{"type":"number"},"minItems":2,"maxItems":2},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["date_tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"before":{"type":"string","format":"date-time"},"after":{"type":"string","format":"date-time"},"between":{"type":"array","items":{"type":"string","format":"date-time"},"minItems":2,"maxItems":2},"within_last_days":{"type":"integer","minimum":1,"maximum":3650},"within_next_days":{"type":"integer","minimum":1,"maximum":3650},"older_than_days":{"type":"integer","minimum":1,"maximum":3650},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["user_date_tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"before":{"type":"string","format":"date-time"},"after":{"type":"string","format":"date-time"},"between":{"type":"array","items":{"type":"string","format":"date-time"},"minItems":2,"maxItems":2},"within_last_days":{"type":"integer","minimum":1,"maximum":3650},"within_next_days":{"type":"integer","minimum":1,"maximum":3650},"older_than_days":{"type":"integer","minimum":1,"maximum":3650},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["boolean_tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"value":{"type":"boolean"},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["user_boolean_tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"value":{"type":"boolean"},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["push_permission"]},"value":{"type":"string","enum":["allowed","denied","provisional","undetermined"]}},"required":["field","value"]},{"type":"object","properties":{"field":{"type":"string","enum":["source"]},"value":{"type":"string","enum":["sdk","import"]}},"required":["field","value"]},{"type":"object","properties":{"field":{"type":"string","enum":["locale"]},"value":{"type":"string","minLength":1,"maxLength":256},"in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"not_in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50}},"required":["field"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["timezone_id"]},"value":{"type":"string","minLength":1,"maxLength":256},"in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"not_in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50}},"required":["field"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["app_version"]},"value":{"type":"string","minLength":1,"maxLength":256},"in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"not_in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50}},"required":["field"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["app_build"]},"value":{"type":"string","minLength":1,"maxLength":256},"in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"not_in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50}},"required":["field"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["os_version"]},"value":{"type":"string","minLength":1,"maxLength":256},"in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"not_in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50}},"required":["field"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["last_active"]},"within_days":{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":365}},"required":["field","within_days"]},{"type":"object","properties":{"field":{"type":"string","enum":["tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"value":{"type":"string","minLength":1,"maxLength":256},"in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"not_in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false},{"type":"object","properties":{"field":{"type":"string","enum":["user_tag"]},"key":{"type":"string","minLength":1,"maxLength":64},"value":{"type":"string","minLength":1,"maxLength":256},"in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"not_in":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":50},"exists":{"type":"boolean"}},"required":["field","key"],"additionalProperties":false}],"description":"All filters must match. Each typed tag filter reads only its own type and scope."},"Problem":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"number"},"code":{"type":"string"},"docs":{"type":"string"},"detail":{"type":"string"}},"required":["type","title","status","code","docs"],"additionalProperties":{"nullable":true}},"SavedAudienceCreation":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"Audience name, unique within the app.","example":"Lapsed iOS"},"definition":{"$ref":"#/components/schemas/SavedAudienceDefinition"}},"required":["name","definition"]},"SavedAudiencePreview":{"type":"object","properties":{"reachable":{"type":"integer","description":"Devices this definition would reach right now: matching every filter, opted in, and not invalidated."}},"required":["reachable"]},"SavedAudiencePreviewRequest":{"type":"object","properties":{"definition":{"$ref":"#/components/schemas/SavedAudienceDefinition"}},"required":["definition"]},"App":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"organization_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string","description":"Derived from the name, and made unique within the organization."},"created_at":{"type":"string","format":"date-time"}},"required":["id","organization_id","name","slug","created_at"]},"AppCreation":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"example":"Acme Mobile"}},"required":["name"]},"ApiKey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["secret","mobile"]},"mode":{"type":"string","enum":["live"]},"prefix":{"type":"string","description":"Key prefix for identification."},"label":{"type":"string","nullable":true},"secret":{"type":"string","nullable":true,"description":"Full key value. Mobile keys are returned on every read; secret keys only at creation."},"revoked_at":{"type":"string","nullable":true,"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","type","mode","prefix","label","secret","revoked_at","created_at"]},"ApiKeyCreation":{"type":"object","properties":{"type":{"type":"string","enum":["secret","mobile"]},"mode":{"type":"string","enum":["live"]},"label":{"type":"string","nullable":true,"minLength":1,"maxLength":120,"description":"Optional key label."}},"required":["type","mode"]},"Credential":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"provider":{"type":"string","enum":["apns","fcm"]},"key_id":{"type":"string"},"team_id":{"type":"string","nullable":true},"bundle_id":{"type":"string","nullable":true},"client_email":{"type":"string","nullable":true},"private_key_id":{"type":"string","nullable":true},"fingerprint":{"type":"string","description":"Truncated SHA-256 fingerprint of the credential material."},"status":{"type":"string","enum":["valid","invalid","revoked"]},"validated_at":{"type":"string","nullable":true,"format":"date-time","description":"Last provider validation time. Null if never validated."},"last_error":{"type":"string","nullable":true,"description":"Last provider rejection code, or null if none was recorded."},"validation":{"$ref":"#/components/schemas/CredentialValidation"},"created_at":{"type":"string","format":"date-time"}},"required":["id","provider","key_id","team_id","bundle_id","client_email","private_key_id","fingerprint","status","validated_at","last_error","created_at"]},"CredentialValidation":{"type":"object","properties":{"outcome":{"type":"string","enum":["valid","invalid","unreachable","not_checked"]},"stage":{"type":"string","nullable":true,"enum":["input","oauth","provider","validation",null]},"code":{"type":"string","nullable":true}},"required":["outcome","stage","code"],"description":"Result of this upload validation only, not retained by credential listing. An unreachable result is not a credential rejection. The stage locates the observed failure, not its root cause. No raw provider response or key material is returned."},"CredentialUpload":{"type":"object","properties":{"provider":{"type":"string","enum":["apns","fcm"]},"key_id":{"type":"string","minLength":1,"maxLength":128,"description":"The APNs Key ID, or the Firebase project id.","example":"ABC123DEFG"},"team_id":{"type":"string","nullable":true,"minLength":1,"maxLength":128,"description":"Apple team identifier. Not used by FCM."},"bundle_id":{"type":"string","nullable":true,"minLength":1,"maxLength":256,"description":"APNs bundle identifier (topic). Not used for FCM.","example":"dev.carillon.example"},"material":{"type":"string","minLength":1,"maxLength":16384,"description":"Contents of the APNs .p8 file or FCM service account JSON. Encrypted on upload and never returned."}},"required":["provider","key_id","material"]},"Device":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"token":{"type":"string"},"platform":{"type":"string","enum":["ios","android"]},"environment":{"type":"string","enum":["production","sandbox"]},"external_id":{"type":"string","nullable":true},"tags":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}},"date_tags":{"type":"object","additionalProperties":{"type":"string","format":"date-time"}},"number_tags":{"type":"object","additionalProperties":{"type":"number"}},"boolean_tags":{"type":"object","additionalProperties":{"type":"boolean"}},"timezone_id":{"type":"string","nullable":true},"locale":{"type":"string","nullable":true},"app_version":{"type":"string","nullable":true},"app_build":{"type":"string","nullable":true},"bundle_id":{"type":"string","nullable":true},"os_version":{"type":"string","nullable":true},"push_permission":{"type":"string","nullable":true,"enum":["allowed","denied","provisional","undetermined",null]},"sdk_version":{"type":"string","nullable":true},"opted_in":{"type":"boolean"},"invalidated_at":{"type":"string","nullable":true,"format":"date-time"},"invalidation_reason":{"type":"string","nullable":true,"description":"Why the device was invalidated: Superseded when a newer registration took its token, otherwise the provider feedback code such as Unregistered. Null while the device is valid."},"last_active_at":{"type":"string","nullable":true,"format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"required":["id","token","platform","environment","external_id","tags","date_tags","number_tags","boolean_tags","timezone_id","locale","app_version","app_build","bundle_id","os_version","push_permission","sdk_version","opted_in","invalidated_at","invalidation_reason","last_active_at","created_at"]},"DevicesDeleted":{"type":"object","properties":{"deleted":{"type":"integer","description":"Device rows removed by this call."}},"required":["deleted"]},"DeviceTags":{"type":"object","additionalProperties":{"anyOf":[{"type":"string","maxLength":256},{"type":"number"},{"type":"boolean"},{"nullable":true}]},"description":"Merge these keys with existing tags. A null value removes that key; omitted keys are unchanged.","example":{"plan":"pro","beta":null}},"DeviceRegistration":{"type":"object","properties":{"device_id":{"type":"string","format":"uuid","description":"Previous registration ID. Token rotation requires the matching installation_secret."},"installation_secret":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$","description":"32 random bytes encoded as unpadded base64url, persisted by this installation. Never expose it in logs or analytics."},"token":{"type":"string","minLength":1,"maxLength":512,"description":"The provider token, exactly as the device received it."},"platform":{"type":"string","enum":["ios","android"]},"environment":{"type":"string","enum":["production","sandbox"],"description":"APNs environment detected by the SDK from the app build. Selects the sandbox or production APNs endpoint."},"external_id":{"type":"string","nullable":true,"minLength":1,"maxLength":256,"description":"Your user identifier. Multiple devices can share the same external_id."},"tags":{"allOf":[{"$ref":"#/components/schemas/DeviceTags"},{"nullable":true}]},"date_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"string","nullable":true,"format":"date-time"}},"number_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"number","nullable":true}},"boolean_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"boolean","nullable":true}},"timezone_id":{"type":"string","nullable":true,"maxLength":64},"locale":{"type":"string","nullable":true,"maxLength":35},"app_version":{"type":"string","nullable":true,"maxLength":64},"app_build":{"type":"string","nullable":true,"maxLength":64,"description":"CFBundleVersion on iOS or versionCode on Android, encoded as a string."},"bundle_id":{"type":"string","nullable":true,"maxLength":256,"description":"Bundle identifier on iOS or package name on Android."},"os_version":{"type":"string","nullable":true,"maxLength":64,"description":"OS version reported by the device, stored without normalization."},"push_permission":{"type":"string","nullable":true,"enum":["allowed","denied","provisional","undetermined",null],"description":"OS notification permission. iOS supports all four values; Android reports allowed or denied."},"sdk_version":{"type":"string","nullable":true,"maxLength":64},"opted_in":{"type":"boolean"}},"required":["token","platform","environment"]},"UserTags":{"type":"object","properties":{"external_id":{"type":"string"},"tags":{"type":"object","additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}},"date_tags":{"type":"object","additionalProperties":{"type":"string","format":"date-time"}},"number_tags":{"type":"object","additionalProperties":{"type":"number"}},"boolean_tags":{"type":"object","additionalProperties":{"type":"boolean"}}},"required":["external_id","tags","date_tags","number_tags","boolean_tags"]},"EventReceipt":{"type":"object","properties":{"received":{"type":"integer","description":"Number of submitted events, including ignored events."}},"required":["received"]},"EventBatch":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/Event"},"minItems":1,"maxItems":100}},"required":["events"]},"Event":{"type":"object","properties":{"type":{"type":"string","enum":["opened","received"]},"delivery_id":{"type":"string","format":"uuid","description":"delivery_id from the reserved carillon object in the notification payload."},"at":{"type":"string","format":"date-time","description":"Event time in ISO 8601. Future timestamps and timestamps older than 30 days are replaced with the server receipt time."}},"required":["type","delivery_id","at"]},"DeviceImport":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","processing","done","failed"],"description":"failed means the file could not be parsed. done can include rejected rows; valid rows are still imported."},"counts":{"$ref":"#/components/schemas/DeviceImportCounts"},"errors":{"type":"array","items":{"$ref":"#/components/schemas/DeviceImportError"},"description":"Up to 1000 rejected rows. Empty until processing finishes."},"errors_truncated":{"type":"boolean","description":"True if rejected rows exceed 1000. See counts for the total."},"ignored_columns":{"type":"array","items":{"type":"string"},"description":"Unrecognized header columns. These columns are ignored."},"failure":{"type":"string","nullable":true,"description":"Why the file could not be read. Null unless status is failed."},"created_at":{"type":"string","format":"date-time"},"finished_at":{"type":"string","nullable":true,"format":"date-time"}},"required":["id","status","counts","errors","errors_truncated","ignored_columns","failure","created_at","finished_at"]},"DeviceImportCounts":{"type":"object","properties":{"created":{"type":"integer","description":"Device rows written by this import."},"already_present":{"type":"integer","description":"Rows skipped because their token already exists in this app. Existing device data is unchanged."},"invalid":{"type":"integer","description":"Total rejected rows, including those omitted from the error sample."}},"required":["created","already_present","invalid"]},"DeviceImportError":{"type":"object","properties":{"line":{"type":"integer","description":"CSV line number, including the header as line 1."},"field":{"type":"string"},"message":{"type":"string"}},"required":["line","field","message"]},"MessageTagPage":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"next_cursor":{"type":"string","nullable":true}},"required":["tags","next_cursor"]},"AppMessageOpens":{"type":"object","properties":{"hours":{"type":"array","items":{"type":"object","properties":{"hour":{"type":"string","format":"date-time","description":"The UTC hour, as an ISO instant."},"opened":{"type":"integer"}},"required":["hour","opened"]},"description":"Hourly open counts for the first 48 hours after campaign creation, through the current hour. Empty if no opens were recorded."}},"required":["hours"]},"MessageTrace":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"maxItems":10,"description":"Campaign labels for filtering history. Up to 10 case-sensitive strings of 1-64 characters. Duplicates are removed. Not included in the push payload. Prefer stable categories such as like or new_comment.","example":["new_comment","social"]},"status":{"type":"string","enum":["scheduled","sending","sent","canceled","paused"],"description":"Campaign processing state. sent means all deliveries have terminal outcomes and no scheduled wave remains; it does not guarantee provider acceptance or display. Inspect outcomes and delivery error codes."},"mode":{"type":"string","enum":["live"]},"created_at":{"type":"string","format":"date-time"},"schedule_type":{"type":"string","enum":["immediate","absolute","local_time"]},"send_at":{"type":"string","nullable":true,"format":"date-time","description":"Scheduled send time. Null unless scheduled with send_at."},"local_time":{"type":"string","nullable":true,"description":"Recipient-local send time. Null unless scheduled with local_time."},"timezone_fallback":{"type":"string","nullable":true,"description":"Fallback IANA timezone. Null unless scheduled with local_time."},"canceled_at":{"type":"string","nullable":true,"format":"date-time","description":"Cancellation request time. Deliveries already in flight may still be sent."},"waves":{"type":"object","properties":{"count":{"type":"integer"},"next_fire_at":{"type":"string","nullable":true,"format":"date-time","description":"Next pending timezone wave. Null if none remain."}},"required":["count","next_fire_at"],"description":"Timezone waves. Empty unless scheduled with local_time."},"delivery_rate":{"type":"integer","description":"Effective campaign-wide notifications per second."},"remaining":{"type":"integer","description":"Number of queued deliveries. Excludes deliveries already in flight."},"paused_reason":{"type":"string","nullable":true,"description":"Provider error that paused the campaign after repeated configuration failures. Null unless paused."},"outcomes":{"type":"object","properties":{"opened":{"type":"integer","description":"Deliveries with an SDK-reported open."},"sent":{"type":"integer"},"failed":{"type":"integer"},"pending":{"type":"integer","description":"Queued and in-flight deliveries whose final outcome is not yet recorded."},"total":{"type":"integer"}},"required":["opened","sent","failed","pending","total"],"description":"Counts across all deliveries. The deliveries array contains only a sample."},"platforms":{"type":"object","properties":{"ios":{"type":"object","properties":{"opened":{"type":"integer","description":"Deliveries with an SDK-reported open."},"sent":{"type":"integer"},"failed":{"type":"integer"},"pending":{"type":"integer","description":"Queued and in-flight deliveries whose final outcome is not yet recorded."},"total":{"type":"integer"}},"required":["opened","sent","failed","pending","total"],"description":"Counts across all deliveries. The deliveries array contains only a sample."},"android":{"type":"object","properties":{"opened":{"type":"integer","description":"Deliveries with an SDK-reported open."},"sent":{"type":"integer"},"failed":{"type":"integer"},"pending":{"type":"integer","description":"Queued and in-flight deliveries whose final outcome is not yet recorded."},"total":{"type":"integer"}},"required":["opened","sent","failed","pending","total"],"description":"Counts across all deliveries. The deliveries array contains only a sample."}},"required":["ios","android"],"description":"Outcomes per platform. Rows written before platform tracking are counted in outcomes only."},"deliveries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"device_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","in_flight","sent","failed"]},"error_code":{"type":"string","nullable":true,"description":"Provider or Carillon failure code."},"received_at":{"type":"string","nullable":true,"format":"date-time","description":"Receipt time reported by the SDK, subject to device clock validation. Null without a report; not proof of display or reading."},"received_reported_at":{"type":"string","nullable":true,"format":"date-time","description":"Server time of the first receipt report, which can arrive after an offline period."},"opened_at":{"type":"string","nullable":true,"format":"date-time","description":"Time reported by the device when the notification was opened. Null if no open was recorded."}},"required":["id","device_id","status","error_code","received_at","received_reported_at","opened_at"]},"description":"First 50 deliveries in creation order. Use counts for totals across the campaign."}},"required":["id","tags","status","mode","created_at","schedule_type","send_at","local_time","timezone_fallback","canceled_at","waves","delivery_rate","remaining","paused_reason","outcomes","platforms","deliveries"]},"Cancellation":{"type":"object","properties":{"canceled":{"type":"boolean","enum":[true]},"already_sent":{"type":"integer","description":"Deliveries marked sent or in_flight when cancellation was requested. In-flight deliveries may still be sent."},"canceled_count":{"type":"integer","description":"Queued deliveries cancelled. Zero if the audience had not yet been resolved."}},"required":["canceled","already_sent","canceled_count"]},"Message":{"type":"object","properties":{"audience":{"type":"object","properties":{"kind":{"type":"string","enum":["device_ids","external_ids","all","saved"]},"size":{"type":"integer","nullable":true,"minimum":0,"description":"Number of submitted identifiers, not resolved devices. Null for all or saved audiences."}},"required":["kind","size"]},"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["sending","scheduled"]},"mode":{"type":"string","enum":["live"]},"created_at":{"type":"string","format":"date-time"},"deliveries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"device_id":{"type":"string","format":"uuid"}},"required":["id","device_id"]},"description":"Delivery ids for an immediate device_ids send, including failed deliveries. Empty for scheduled sends and worker-resolved audiences; follow the campaign trace for results."},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/SendWarning"},"description":"Platforms blocked by missing or rejected credentials. Empty if none are blocked. Returns 422 if all targeted platforms are blocked."}},"required":["audience","id","status","mode","created_at","deliveries","warnings"]},"SendWarning":{"type":"object","properties":{"code":{"type":"string","enum":["no_valid_credential"]},"platform":{"type":"string","enum":["ios","android"]},"detail":{"type":"string","description":"Description of the blocked platform and reason."},"devices":{"type":"integer","nullable":true,"description":"Number of affected devices. Null when the audience has not been resolved."},"reason":{"type":"string","description":"Delivery error code for the affected platform. NoCredential means no credential is configured."},"credential":{"type":"object","nullable":true,"properties":{"key_id":{"type":"string"},"rejected_at":{"type":"string","nullable":true,"format":"date-time","description":"Last provider rejection time, or null if none was recorded."}},"required":["key_id","rejected_at"],"description":"Null if no credential exists for this provider."}},"required":["code","platform","detail","devices","reason","credential"]},"MessageCreation":{"type":"object","properties":{"audience":{"$ref":"#/components/schemas/Audience"},"tags":{"type":"array","items":{"type":"string","minLength":1,"maxLength":64},"maxItems":10,"description":"Campaign labels for filtering history. Up to 10 case-sensitive strings of 1-64 characters. Duplicates are removed. Not included in the push payload. Prefer stable categories such as like or new_comment.","example":["new_comment","social"]},"payload":{"$ref":"#/components/schemas/MessagePayload"},"dedup_key":{"type":"string","minLength":1,"maxLength":255,"description":"Business deduplication key. Reuse returns 409 for 30 days regardless of the body. Use Idempotency-Key for request retries.","example":"order-1234-shipped"},"send_at":{"type":"string","format":"date-time","description":"Scheduled send time, shared by all recipients. Must be in the future and within 30 days. Returns status scheduled.","example":"2026-12-24T18:00:00Z"},"local_time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","description":"Recipient-local send time in HH:MM format. Sends today if that time has not passed in the device timezone, otherwise tomorrow. Requires timezone_fallback.","example":"10:00"},"timezone_fallback":{"type":"string","minLength":1,"maxLength":64,"description":"IANA timezone for devices without a known timezone, such as Europe/Paris. Required with local_time.","example":"Europe/Paris"},"delivery_rate":{"type":"integer","minimum":1,"maximum":1000000,"description":"Campaign-wide notifications per second. Defaults to the app limit; values above that limit return 422. Applies across all timezone waves, which span about 26 hours.","example":500}},"required":["audience","payload"]},"Audience":{"anyOf":[{"type":"object","properties":{"device_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":100,"description":"Between 1 and 100 device ids, each at most once."}},"required":["device_ids"],"additionalProperties":false},{"type":"object","properties":{"external_ids":{"type":"array","items":{"type":"string","minLength":1,"maxLength":256},"minItems":1,"maxItems":2000,"description":"Between 1 and 2000 unique user identifiers. Every matching device is resolved by the worker; unknown identifiers are ignored."}},"required":["external_ids"],"additionalProperties":false},{"type":"object","properties":{"all":{"type":"boolean","enum":[true],"description":"All devices in this app and key mode. Opted-out or invalidated devices are recorded as failed deliveries."}},"required":["all"],"additionalProperties":false},{"type":"object","properties":{"audience_id":{"type":"string","format":"uuid","description":"Saved audience id in this app. Latest filters are frozen when recipient resolution begins; devices are resolved at send time. Unknown ids return 422 unknown_audience.","example":"01937b1e-0000-7000-8000-000000000000"}},"required":["audience_id"],"additionalProperties":false}]},"MessagePayload":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":256},"body":{"type":"string","minLength":1,"maxLength":2048},"subtitle":{"type":"string","minLength":1,"maxLength":256},"image":{"type":"string","description":"HTTPS image URL, up to 2048 characters."},"ttl":{"type":"integer","minimum":0,"maximum":2419200,"description":"Lifetime in seconds from the first attempt, preserved across retries. Defaults to 259200 (72 hours); 0 requests immediate delivery without provider storage."},"priority":{"type":"string","enum":["high","normal"]},"collapse_id":{"type":"string","minLength":1},"thread_id":{"type":"string","minLength":1,"maxLength":64},"sound":{"type":"string","minLength":1,"maxLength":64},"badge":{"type":"integer","minimum":0},"android":{"type":"object","additionalProperties":{"nullable":true},"description":"Raw FCM Android configuration merged over generated Android fields. Reserved tracking metadata remains protected."},"data":{"$ref":"#/components/schemas/MessageData"},"localizations":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/Localization"},"description":"Localized text by language tag. Matches the device locale, then its language, then the base text."},"apns":{"type":"object","additionalProperties":{"nullable":true},"description":"Raw APNs payload merged over the generated payload. Overrides conflicting fields."}}},"MessageData":{"type":"object","additionalProperties":{"anyOf":[{"type":"string","maxLength":1024},{"type":"number"},{"type":"boolean"},{"nullable":true},{"type":"array","items":{"nullable":true}},{"type":"object","additionalProperties":{"nullable":true}}]},"example":{"order_id":"42"},"description":"Custom JSON fields delivered with the notification. Objects and arrays are preserved by supported SDKs. The carillon key is reserved and must not be included."},"Localization":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":256},"body":{"type":"string","minLength":1,"maxLength":2048},"subtitle":{"type":"string","minLength":1,"maxLength":256},"image":{"type":"string","description":"HTTPS image URL, up to 2048 characters."}},"description":"Overrides the supplied fields. Omitted fields keep the base text."},"HelloResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}},"parameters":{},"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","description":"An API key, sent as `Authorization: Bearer carillon_sk_live_…`. A secret key sends, reads traces and manages devices; a mobile key may only register its own device and report that device’s events."},"SessionToken":{"type":"http","scheme":"bearer","description":"A session token, sent as `Authorization: Bearer …`. Sign in on the dashboard to obtain one, or run `carillon login`. Management endpoints require a session token."}}},"paths":{"/health":{"get":{"operationId":"health","summary":"Liveness probe","description":"Unauthenticated health check available on both API surfaces.","responses":{"200":{"description":"Process is running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}},"tags":["Service"],"security":[]}},"/v1/apps/{appId}/audiences":{"get":{"operationId":"listAudiences","summary":"List the saved audiences of an app","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"Every saved audience of the app, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedAudienceList"}}}}},"tags":["Audiences"],"security":[{"SessionToken":[]}]},"post":{"operationId":"createAudience","summary":"Save an audience","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedAudienceCreation"}}}},"responses":{"201":{"description":"Created audience. Use its id as audience_id when sending a message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedAudience"}}}},"409":{"description":"An audience of this app already carries that name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Audiences"],"security":[{"SessionToken":[]}]}},"/v1/apps/{appId}/audiences/{audienceId}":{"delete":{"operationId":"deleteAudience","summary":"Delete a saved audience","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"audienceId","in":"path"}],"responses":{"204":{"description":"Audience deleted. Existing campaigns retain their copied filters."},"404":{"description":"No such audience in this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Audiences"],"security":[{"SessionToken":[]}]}},"/v1/apps/{appId}/audiences/preview":{"post":{"operationId":"previewAudience","summary":"Preview an audience","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedAudiencePreviewRequest"}}}},"responses":{"200":{"description":"Current matching-device count. Does not save the definition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SavedAudiencePreview"}}}}},"tags":["Audiences"],"security":[{"SessionToken":[]}]}},"/v1/organizations/{organizationId}/apps":{"get":{"operationId":"listApps","summary":"List the apps of an organization","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"organizationId","in":"path"}],"responses":{"200":{"description":"Every app, oldest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/App"}}}}}},"tags":["Organizations and apps"],"security":[{"SessionToken":[]}]},"post":{"operationId":"createApp","summary":"Create an app","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"organizationId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppCreation"}}}},"responses":{"200":{"description":"Created app. A live mobile key is also created; retrieve it from the key list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}}},"tags":["Organizations and apps"],"security":[{"SessionToken":[]}]}},"/v1/apps/{appId}/keys":{"get":{"operationId":"listApiKeys","summary":"List the API keys of an app","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"Every key, revoked ones included, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}}},"tags":["API keys"],"security":[{"SessionToken":[]}]},"post":{"operationId":"createApiKey","summary":"Create an API key","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreation"}}}},"responses":{"200":{"description":"Created key. Store the secret key value now; it cannot be retrieved later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}}},"tags":["API keys"],"security":[{"SessionToken":[]}]}},"/v1/apps/{appId}/keys/{keyId}/revoke":{"post":{"operationId":"revokeApiKey","summary":"Revoke an API key","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"keyId","in":"path"}],"responses":{"200":{"description":"The key, now revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKey"}}}},"404":{"description":"No such key on this app, or it was already revoked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["API keys"],"security":[{"SessionToken":[]}]}},"/v1/apps/{appId}/credentials":{"get":{"operationId":"listCredentials","summary":"List the credentials of an app","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"responses":{"200":{"description":"Credential metadata. Secret material is excluded.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Credential"}}}}}},"tags":["Credentials"],"security":[{"SessionToken":[]}]},"post":{"operationId":"uploadCredential","summary":"Upload a provider credential","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialUpload"}}}},"responses":{"200":{"description":"Created credential metadata. Secret material is excluded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credential"}}}}},"tags":["Credentials"],"security":[{"SessionToken":[]}]}},"/v1/apps/{appId}/credentials/{credentialId}":{"delete":{"operationId":"deleteCredential","summary":"Delete a provider credential","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"appId","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":true,"name":"credentialId","in":"path"}],"responses":{"204":{"description":"Credential deleted."},"404":{"description":"No such credential in this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Credentials"],"security":[{"SessionToken":[]}]}},"/v1/devices":{"get":{"operationId":"exportDevices","summary":"List devices for export","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256},"required":false,"name":"external_id","in":"query"},{"schema":{"type":"string","enum":["ios","android"]},"required":false,"name":"platform","in":"query"},{"schema":{"type":"string","enum":["reachable","opted_out","invalidated"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":1000,"default":100},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Devices ordered by ID. Continue with next_cursor until null.","content":{"application/json":{"schema":{"type":"object","properties":{"devices":{"type":"array","items":{"$ref":"#/components/schemas/Device"}},"next_cursor":{"type":"string","nullable":true,"format":"uuid"}},"required":["devices","next_cursor"]}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]},"delete":{"operationId":"deleteDevicesByExternalId","summary":"Remove every device of a user","description":"Deletes every device row carrying this external_id. Delivery traces and usage already counted for the current month are kept.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256},"required":true,"name":"external_id","in":"query"}],"responses":{"200":{"description":"How many rows went. Zero when no device carried the external_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DevicesDeleted"}}}},"403":{"description":"The key does not carry devices:delete. A mobile key never does.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]},"post":{"operationId":"registerDevice","summary":"Register or refresh a device","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceRegistration"}}}},"responses":{"200":{"description":"Registered device.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Device"}}}},"400":{"description":"Invalid tags or merged tags exceed the limits.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is over 65536 bytes.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]}},"/v1/devices/{id}":{"get":{"operationId":"readDevice","summary":"Read a device","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The device in this app.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Device"}}}},"404":{"description":"No device with that ID in this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]},"patch":{"operationId":"patchDeviceTags","summary":"Merge device tags","description":"Requires a secret key with devices:write. Updates only supplied tag keys; null removes a key. Other device fields and activity timestamps are unchanged. Concurrent updates to different keys are preserved; the last applied update wins for the same key.","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"$ref":"#/components/schemas/DeviceTags"},"date_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"string","nullable":true,"format":"date-time"}},"number_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"number","nullable":true}},"boolean_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"boolean","nullable":true}}},"additionalProperties":false}}}},"responses":{"200":{"description":"Device with merged tags.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Device"}}}},"400":{"description":"Invalid tags or merged tags exceed the limits.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"A secret key with devices:write is required.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No device with this ID in the key’s app and mode.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]},"delete":{"operationId":"deleteDevice","summary":"Remove a device","description":"Deletes the device row. Delivery traces and usage already counted for the current month are kept. Removing an unknown or already removed id also answers 204.","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"204":{"description":"The device is gone, or never was in this app."},"403":{"description":"The key does not carry devices:delete. A mobile key never does.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]}},"/v1/users/{externalId}/tags":{"get":{"operationId":"readUserTags","summary":"Read shared user tags","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256},"required":true,"name":"externalId","in":"path"}],"responses":{"200":{"description":"The user tags in this app and key mode.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserTags"}}}},"403":{"description":"Requires users:read on a secret key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No user tags for this external ID.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Users"],"security":[{"ApiKey":[]}]},"patch":{"operationId":"patchUserTags","summary":"Merge shared user tags, creating the profile if needed","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256},"required":true,"name":"externalId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tags":{"$ref":"#/components/schemas/DeviceTags"},"date_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"string","nullable":true,"format":"date-time"}},"number_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"number","nullable":true}},"boolean_tags":{"type":"object","nullable":true,"additionalProperties":{"type":"boolean","nullable":true}}},"additionalProperties":false}}}},"responses":{"200":{"description":"The user tags in this app and key mode.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserTags"}}}},"400":{"description":"Invalid tags or merged tags exceed the limits.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Requires users:write on a secret key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Users"],"security":[{"ApiKey":[]}]},"delete":{"operationId":"deleteUserTags","summary":"Delete a user tag profile without changing its devices","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":256},"required":true,"name":"externalId","in":"path"}],"responses":{"204":{"description":"Profile deleted or already absent. Devices and their external IDs are unchanged."},"403":{"description":"Requires users:delete on a secret key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Users"],"security":[{"ApiKey":[]}]}},"/v1/events":{"post":{"operationId":"reportEvents","summary":"Report notification receipts and opens","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventBatch"}}}},"responses":{"202":{"description":"Batch accepted. Unknown, cross-app, and duplicate delivery ids are ignored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventReceipt"}}}}},"tags":["Events"],"security":[{"ApiKey":[]}]}},"/v1/imports/{id}":{"get":{"operationId":"getDeviceImport","summary":"Get an import","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Import status, counts, and rejected-row sample.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceImport"}}}},"404":{"description":"No such import in this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]}},"/v1/messages/tags":{"get":{"operationId":"listMessageTags","summary":"List notification tags used in this app","description":"Distinct tags from stored campaigns in this app and API key mode. Does not list device or user tags. No queue is required.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":64,"description":"The next_cursor returned by the previous page. Omit for the first page."},"required":false,"description":"The next_cursor returned by the previous page. Omit for the first page.","name":"cursor","in":"query"}],"responses":{"200":{"description":"Up to 100 distinct campaign tags, sorted by label. Follow next_cursor until null.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTagPage"}}}},"403":{"description":"Requires deliveries:read on a secret key, or membership of the app organization.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Messages"],"security":[{"ApiKey":[]}]}},"/v1/messages/{id}/opens":{"get":{"operationId":"getMessageOpens","summary":"Get hourly campaign opens","parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Hourly opens within the first 48 hours after campaign creation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppMessageOpens"}}}},"404":{"description":"No such campaign in this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Messages"],"security":[{"ApiKey":[]}]}},"/v1/messages/{id}":{"get":{"operationId":"getMessage","summary":"Read a campaign and its delivery trace","parameters":[{"schema":{"type":"string","example":"01937b1e-0000-7000-8000-000000000000"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Campaign details, delivery counts, and a bounded delivery sample.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageTrace"}}}},"403":{"description":"The key may not read a trace. A mobile key never can.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No such campaign in this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Messages"],"security":[{"ApiKey":[]}]}},"/v1/messages/{id}/cancel":{"post":{"operationId":"cancelMessage","summary":"Cancel a campaign","description":"Cancel queued deliveries and pending waves. Deliveries already in flight cannot be cancelled.","parameters":[{"schema":{"type":"string","example":"01937b1e-0000-7000-8000-000000000000"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Cancelled campaign and delivery counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cancellation"}}}},"403":{"description":"The key may not cancel. A mobile key never can.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No such campaign in this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Messages"],"security":[{"ApiKey":[]}]}},"/v1/messages":{"post":{"operationId":"sendMessage","summary":"Send or schedule a notification","description":"Create a campaign for one of four audiences: device_ids, external_ids, all devices, or a saved audience (audience_id). Sending runs asynchronously. Use Idempotency-Key for retries and dedup_key to reject duplicate business notifications for 30 days.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Retry key retained for 24 hours. Reuse the same key and identical request-body bytes to receive the original response. A different body returns 422; a concurrent request returns 409.","example":"018f2c8a-6f3f-7a2b-9f1e-2c9b0f4d5a11"},"required":false,"description":"Retry key retained for 24 hours. Reuse the same key and identical request-body bytes to receive the original response. A different body returns 422; a concurrent request returns 409.","name":"idempotency-key","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageCreation"}}}},"responses":{"202":{"description":"Campaign accepted for sending or scheduling.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"402":{"description":"The organization billing grace period has expired. Resolve billing before starting new sends.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key may not send. A mobile key never can.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"This `dedup_key` has already been used, or a request under this `Idempotency-Key` is still running.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is over 1048576 bytes.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The payload would show nothing, claims the reserved `carillon` data key, a `device_ids` entry or the `audience_id` is not in this app, `delivery_rate` is above the app ceiling, or this `Idempotency-Key` was used with a different body. `external_ids` and `all` never fail this way: identifiers matching no device are ignored.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Messages"],"security":[{"ApiKey":[]}]}},"/v1/devices/import":{"post":{"operationId":"importDevices","summary":"Import devices from a CSV","description":"Store a CSV for asynchronous processing. Tokens are not validated with APNs or FCM during import. Existing tokens in this app are skipped without updating their data.","requestBody":{"required":true,"content":{"text/csv":{"schema":{"type":"string","description":"CSV with a header and one device per row. Required: token, platform (ios or android). Optional: environment (iOS only; production or sandbox, defaults to production), external_id, timezone_id (IANA identifier), locale. Unknown columns are ignored and listed in the report.","example":"token,platform,external_id,timezone_id,locale\na1b2c3…,ios,user-42,Europe/Paris,fr-FR\n"}}}},"responses":{"202":{"description":"CSV stored and queued for processing. Poll the returned import id for status and errors.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceImport"}}}},"413":{"description":"The file is larger than one request may carry.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not a CSV.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The body is empty.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"tags":["Devices"],"security":[{"ApiKey":[]}]}}},"servers":[{"url":"https://api.carillon.dev","description":"Production"}],"tags":[{"name":"Messages","description":"Send a notification, follow it, cancel it."},{"name":"Devices","description":"Register a handset, or load a list of tokens you already hold."},{"name":"Users","description":"Manage shared user tags with a secret key."},{"name":"Events","description":"Report notification opens."},{"name":"Audiences","description":"Create audiences from device filters and preview matching devices."},{"name":"Credentials","description":"The APNs key and the Firebase service account."},{"name":"API keys","description":"Create, list, and revoke API keys."},{"name":"Organizations and apps","description":"Manage organizations and their apps."},{"name":"Service","description":"Check API health."}]}