notamify-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. notamify_sdk-0.1.0/LICENSE +21 -0
  2. notamify_sdk-0.1.0/MANIFEST.in +1 -0
  3. notamify_sdk-0.1.0/PKG-INFO +203 -0
  4. notamify_sdk-0.1.0/README.md +180 -0
  5. notamify_sdk-0.1.0/examples/README.md +128 -0
  6. notamify_sdk-0.1.0/examples/local_service_run.py +201 -0
  7. notamify_sdk-0.1.0/examples/notams_fetch.py +105 -0
  8. notamify_sdk-0.1.0/examples/service_receiver.py +99 -0
  9. notamify_sdk-0.1.0/notamify_sdk/__init__.py +109 -0
  10. notamify_sdk-0.1.0/notamify_sdk/client.py +504 -0
  11. notamify_sdk-0.1.0/notamify_sdk/cloudflared.py +90 -0
  12. notamify_sdk-0.1.0/notamify_sdk/config.py +80 -0
  13. notamify_sdk-0.1.0/notamify_sdk/models.py +498 -0
  14. notamify_sdk-0.1.0/notamify_sdk/receiver.py +183 -0
  15. notamify_sdk-0.1.0/notamify_sdk/signature.py +61 -0
  16. notamify_sdk-0.1.0/notamify_sdk.egg-info/PKG-INFO +203 -0
  17. notamify_sdk-0.1.0/notamify_sdk.egg-info/SOURCES.txt +34 -0
  18. notamify_sdk-0.1.0/notamify_sdk.egg-info/dependency_links.txt +1 -0
  19. notamify_sdk-0.1.0/notamify_sdk.egg-info/requires.txt +1 -0
  20. notamify_sdk-0.1.0/notamify_sdk.egg-info/top_level.txt +2 -0
  21. notamify_sdk-0.1.0/notamify_watcher_sdk/__init__.py +25 -0
  22. notamify_sdk-0.1.0/notamify_watcher_sdk/client.py +78 -0
  23. notamify_sdk-0.1.0/notamify_watcher_sdk/cloudflared.py +8 -0
  24. notamify_sdk-0.1.0/notamify_watcher_sdk/config.py +59 -0
  25. notamify_sdk-0.1.0/notamify_watcher_sdk/models.py +1 -0
  26. notamify_sdk-0.1.0/notamify_watcher_sdk/receiver.py +7 -0
  27. notamify_sdk-0.1.0/notamify_watcher_sdk/signature.py +13 -0
  28. notamify_sdk-0.1.0/pyproject.toml +45 -0
  29. notamify_sdk-0.1.0/setup.cfg +4 -0
  30. notamify_sdk-0.1.0/tests/test_client.py +531 -0
  31. notamify_sdk-0.1.0/tests/test_cloudflared.py +22 -0
  32. notamify_sdk-0.1.0/tests/test_compat.py +120 -0
  33. notamify_sdk-0.1.0/tests/test_config.py +72 -0
  34. notamify_sdk-0.1.0/tests/test_models.py +175 -0
  35. notamify_sdk-0.1.0/tests/test_receiver.py +141 -0
  36. notamify_sdk-0.1.0/tests/test_signature.py +34 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Skymerse Inc. (Notamify)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ recursive-include examples *.md *.py
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: notamify-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Notamify APIs
5
+ Author: Damian Szumski
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/skymerse/notamify-sdk-python
8
+ Project-URL: Repository, https://github.com/skymerse/notamify-sdk-python
9
+ Project-URL: Issues, https://github.com/skymerse/notamify-sdk-python/issues
10
+ Keywords: notam,aviation,webhook,sdk
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: pydantic<3,>=2
22
+ Dynamic: license-file
23
+
24
+ # notamify-sdk (Python)
25
+
26
+ Python SDK for Notamify public APIs:
27
+
28
+ - Notamify API v2 (`https://api.notamify.com/api/v2`)
29
+ - Watcher API (`https://watcher.notamify.com`)
30
+
31
+ ## Features
32
+
33
+ - Typed API client with one auth token
34
+ - Pydantic response/request models
35
+ - Pager-style NOTAM listing via `client.notams.*` with item iteration and `pager.pages`
36
+ - Supported NOTAM endpoints:
37
+ - `GET /notams`
38
+ - `GET /notams/raw`
39
+ - `GET /notams/nearby`
40
+ - `GET /notams/archive`
41
+ - `POST /notams/briefing`
42
+ - `GET /notams/briefing/{uuid}`
43
+ - `POST /notams/prioritisation`
44
+ - Watcher listener management + webhook logs/secrets
45
+ - Listener `mode` support (`prod` default, `sandbox` for test-only listeners)
46
+ - Listener lifecycle support via `lifecycle.enabled` and `lifecycle.types`
47
+ - Sandbox test delivery endpoint (`POST /listeners/{id}/sandbox:send`)
48
+ - Webhook signature verification (`X-Notamify-Signature`)
49
+ - Typed webhook event models for `interpretation` and `lifecycle` payloads
50
+ - Embedded receiver + cloudflared helper for local webhook testing
51
+ - Strict receiver mode fails fast when webhook secret is missing
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ pip install notamify-sdk
57
+ ```
58
+
59
+ Or with `uv`:
60
+
61
+ ```bash
62
+ uv add notamify-sdk
63
+ ```
64
+
65
+ For contributors working from a source checkout:
66
+
67
+ ```bash
68
+ uv sync
69
+ ```
70
+
71
+ ## Configuration
72
+
73
+ Environment variables (highest priority):
74
+
75
+ - `NOTAMIFY_TOKEN`
76
+ - `NOTAMIFY_API_BASE_URL`
77
+ - `NOTAMIFY_WATCHER_BASE_URL`
78
+ - `NOTAMIFY_WEBHOOK_SECRET`
79
+ - `NOTAMIFY_CONFIG_FILE`
80
+
81
+ Default config file: `~/.config/notamify/config.json`
82
+
83
+ ## Usage
84
+
85
+ ```python
86
+ from notamify_sdk import NotamifyClient
87
+
88
+ client = NotamifyClient(token="YOUR_TOKEN")
89
+
90
+ # The API accepts at most 30 items per page.
91
+ active_notams = list(client.notams.active({
92
+ "location": ["KJFK", "KLAX"],
93
+ "per_page": 30,
94
+ }))
95
+ print(len(active_notams))
96
+
97
+ first_page = next(iter(client.notams.active({
98
+ "location": ["KJFK", "KLAX"],
99
+ "per_page": 30,
100
+ }).pages))
101
+ print(first_page.total_count)
102
+
103
+ job = client.create_async_briefing({
104
+ "locations": [{
105
+ "location": "KJFK",
106
+ "type": "origin",
107
+ "starts_at": "2026-02-25T10:00:00Z",
108
+ "ends_at": "2026-02-25T12:00:00Z",
109
+ }],
110
+ })
111
+ print(job.uuid)
112
+
113
+ # Watcher sandbox flow
114
+ listener = client.create_listener(
115
+ "https://example.trycloudflare.com/webhooks/notamify",
116
+ mode="sandbox",
117
+ lifecycle={"enabled": False},
118
+ )
119
+ print(listener.webhook_secret)
120
+ sandbox_result = client.send_sandbox_message(listener.id, "SANDBOX-NOTAM-1")
121
+ print(sandbox_result.notam_id)
122
+ ```
123
+
124
+ ## Pagination
125
+
126
+ The SDK exposes two NOTAM access styles:
127
+
128
+ - `client.get_active_notams(...)`, `client.get_raw_notams(...)`, `client.get_nearby_notams(...)`, and `client.get_historical_notams(...)` return a single `NotamListResult` page.
129
+ - `client.notams.active(...)`, `client.notams.raw(...)`, `client.notams.nearby(...)`, and `client.notams.historical(...)` return a pager that fetches all pages lazily as you iterate.
130
+
131
+ Use the pager when you want all NOTAMs across pages:
132
+
133
+ ```python
134
+ pager = client.notams.active(
135
+ {"location": ["KJFK", "KLAX"]},
136
+ per_page=30,
137
+ )
138
+
139
+ for notam in pager:
140
+ print(notam.id)
141
+ ```
142
+
143
+ If you want everything in memory at once, materialize the pager with `list(...)`:
144
+
145
+ ```python
146
+ all_notams = list(
147
+ client.notams.active(
148
+ {"location": ["KJFK", "KLAX"]},
149
+ per_page=30,
150
+ )
151
+ )
152
+ ```
153
+
154
+ If you need page metadata such as `page`, `per_page`, or `total_count`, iterate over `pager.pages`:
155
+
156
+ ```python
157
+ pager = client.notams.active(
158
+ {"location": ["KJFK", "KLAX"]},
159
+ per_page=30,
160
+ )
161
+
162
+ for page in pager.pages:
163
+ print(page.page, page.total_count, len(page.notams))
164
+ ```
165
+
166
+ The API allows at most `30` items per page. The SDK validates this with Pydantic and rejects larger `per_page` values.
167
+
168
+ ## Local Webhook Testing
169
+
170
+ The example scripts live in the repository and source distribution under `examples/`.
171
+ They are intended to be run from a source checkout or source distribution, not from an installed wheel alone.
172
+
173
+ - Production: implement the webhook endpoint in your own application code.
174
+ - Verify `X-Notamify-Signature` using `NOTAMIFY_WEBHOOK_SECRET`.
175
+ - Production watcher deliveries include `kind`, `event_id`, and `notam`.
176
+ - Lifecycle watcher deliveries also include `change.changed_notam_id` for the original NOTAM that was cancelled or replaced.
177
+ - Local development: expose your local app endpoint with `cloudflared`, then set watcher `webhook_url` to that tunnel URL.
178
+ - Sandbox test sends use the same webhook payload DTO as production sends, with mock NOTAM content.
179
+
180
+ Prerequisite: `cloudflared` must be installed and available in `PATH`.
181
+
182
+ ```bash
183
+ export NOTAMIFY_TOKEN="your_notamify_token"
184
+ export LOCAL_APP_URL="http://127.0.0.1:8080/webhooks/notamify"
185
+ uv run python ./examples/local_service_run.py
186
+ ```
187
+
188
+ ## Examples
189
+
190
+ - `examples/service_receiver.py`: minimal production-style webhook receiver
191
+ - `examples/local_service_run.py`: cloudflared tunnel + sandbox delivery flow
192
+ - `examples/notams_fetch.py`: straightforward NOTAM query examples
193
+ - `examples/README.md`: setup notes and sample payloads
194
+
195
+ ## Development
196
+
197
+ ```bash
198
+ uv run python -m unittest discover -s tests -p 'test_*.py'
199
+ ```
200
+
201
+ ## License
202
+
203
+ MIT
@@ -0,0 +1,180 @@
1
+ # notamify-sdk (Python)
2
+
3
+ Python SDK for Notamify public APIs:
4
+
5
+ - Notamify API v2 (`https://api.notamify.com/api/v2`)
6
+ - Watcher API (`https://watcher.notamify.com`)
7
+
8
+ ## Features
9
+
10
+ - Typed API client with one auth token
11
+ - Pydantic response/request models
12
+ - Pager-style NOTAM listing via `client.notams.*` with item iteration and `pager.pages`
13
+ - Supported NOTAM endpoints:
14
+ - `GET /notams`
15
+ - `GET /notams/raw`
16
+ - `GET /notams/nearby`
17
+ - `GET /notams/archive`
18
+ - `POST /notams/briefing`
19
+ - `GET /notams/briefing/{uuid}`
20
+ - `POST /notams/prioritisation`
21
+ - Watcher listener management + webhook logs/secrets
22
+ - Listener `mode` support (`prod` default, `sandbox` for test-only listeners)
23
+ - Listener lifecycle support via `lifecycle.enabled` and `lifecycle.types`
24
+ - Sandbox test delivery endpoint (`POST /listeners/{id}/sandbox:send`)
25
+ - Webhook signature verification (`X-Notamify-Signature`)
26
+ - Typed webhook event models for `interpretation` and `lifecycle` payloads
27
+ - Embedded receiver + cloudflared helper for local webhook testing
28
+ - Strict receiver mode fails fast when webhook secret is missing
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ pip install notamify-sdk
34
+ ```
35
+
36
+ Or with `uv`:
37
+
38
+ ```bash
39
+ uv add notamify-sdk
40
+ ```
41
+
42
+ For contributors working from a source checkout:
43
+
44
+ ```bash
45
+ uv sync
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ Environment variables (highest priority):
51
+
52
+ - `NOTAMIFY_TOKEN`
53
+ - `NOTAMIFY_API_BASE_URL`
54
+ - `NOTAMIFY_WATCHER_BASE_URL`
55
+ - `NOTAMIFY_WEBHOOK_SECRET`
56
+ - `NOTAMIFY_CONFIG_FILE`
57
+
58
+ Default config file: `~/.config/notamify/config.json`
59
+
60
+ ## Usage
61
+
62
+ ```python
63
+ from notamify_sdk import NotamifyClient
64
+
65
+ client = NotamifyClient(token="YOUR_TOKEN")
66
+
67
+ # The API accepts at most 30 items per page.
68
+ active_notams = list(client.notams.active({
69
+ "location": ["KJFK", "KLAX"],
70
+ "per_page": 30,
71
+ }))
72
+ print(len(active_notams))
73
+
74
+ first_page = next(iter(client.notams.active({
75
+ "location": ["KJFK", "KLAX"],
76
+ "per_page": 30,
77
+ }).pages))
78
+ print(first_page.total_count)
79
+
80
+ job = client.create_async_briefing({
81
+ "locations": [{
82
+ "location": "KJFK",
83
+ "type": "origin",
84
+ "starts_at": "2026-02-25T10:00:00Z",
85
+ "ends_at": "2026-02-25T12:00:00Z",
86
+ }],
87
+ })
88
+ print(job.uuid)
89
+
90
+ # Watcher sandbox flow
91
+ listener = client.create_listener(
92
+ "https://example.trycloudflare.com/webhooks/notamify",
93
+ mode="sandbox",
94
+ lifecycle={"enabled": False},
95
+ )
96
+ print(listener.webhook_secret)
97
+ sandbox_result = client.send_sandbox_message(listener.id, "SANDBOX-NOTAM-1")
98
+ print(sandbox_result.notam_id)
99
+ ```
100
+
101
+ ## Pagination
102
+
103
+ The SDK exposes two NOTAM access styles:
104
+
105
+ - `client.get_active_notams(...)`, `client.get_raw_notams(...)`, `client.get_nearby_notams(...)`, and `client.get_historical_notams(...)` return a single `NotamListResult` page.
106
+ - `client.notams.active(...)`, `client.notams.raw(...)`, `client.notams.nearby(...)`, and `client.notams.historical(...)` return a pager that fetches all pages lazily as you iterate.
107
+
108
+ Use the pager when you want all NOTAMs across pages:
109
+
110
+ ```python
111
+ pager = client.notams.active(
112
+ {"location": ["KJFK", "KLAX"]},
113
+ per_page=30,
114
+ )
115
+
116
+ for notam in pager:
117
+ print(notam.id)
118
+ ```
119
+
120
+ If you want everything in memory at once, materialize the pager with `list(...)`:
121
+
122
+ ```python
123
+ all_notams = list(
124
+ client.notams.active(
125
+ {"location": ["KJFK", "KLAX"]},
126
+ per_page=30,
127
+ )
128
+ )
129
+ ```
130
+
131
+ If you need page metadata such as `page`, `per_page`, or `total_count`, iterate over `pager.pages`:
132
+
133
+ ```python
134
+ pager = client.notams.active(
135
+ {"location": ["KJFK", "KLAX"]},
136
+ per_page=30,
137
+ )
138
+
139
+ for page in pager.pages:
140
+ print(page.page, page.total_count, len(page.notams))
141
+ ```
142
+
143
+ The API allows at most `30` items per page. The SDK validates this with Pydantic and rejects larger `per_page` values.
144
+
145
+ ## Local Webhook Testing
146
+
147
+ The example scripts live in the repository and source distribution under `examples/`.
148
+ They are intended to be run from a source checkout or source distribution, not from an installed wheel alone.
149
+
150
+ - Production: implement the webhook endpoint in your own application code.
151
+ - Verify `X-Notamify-Signature` using `NOTAMIFY_WEBHOOK_SECRET`.
152
+ - Production watcher deliveries include `kind`, `event_id`, and `notam`.
153
+ - Lifecycle watcher deliveries also include `change.changed_notam_id` for the original NOTAM that was cancelled or replaced.
154
+ - Local development: expose your local app endpoint with `cloudflared`, then set watcher `webhook_url` to that tunnel URL.
155
+ - Sandbox test sends use the same webhook payload DTO as production sends, with mock NOTAM content.
156
+
157
+ Prerequisite: `cloudflared` must be installed and available in `PATH`.
158
+
159
+ ```bash
160
+ export NOTAMIFY_TOKEN="your_notamify_token"
161
+ export LOCAL_APP_URL="http://127.0.0.1:8080/webhooks/notamify"
162
+ uv run python ./examples/local_service_run.py
163
+ ```
164
+
165
+ ## Examples
166
+
167
+ - `examples/service_receiver.py`: minimal production-style webhook receiver
168
+ - `examples/local_service_run.py`: cloudflared tunnel + sandbox delivery flow
169
+ - `examples/notams_fetch.py`: straightforward NOTAM query examples
170
+ - `examples/README.md`: setup notes and sample payloads
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ uv run python -m unittest discover -s tests -p 'test_*.py'
176
+ ```
177
+
178
+ ## License
179
+
180
+ MIT
@@ -0,0 +1,128 @@
1
+ # Notamify Examples (Python)
2
+
3
+ These examples cover watcher webhook integration and NOTAM data fetching.
4
+ Run them from a source checkout or source distribution after installing dependencies with `uv sync`.
5
+
6
+ ## 1) Copy-paste receiver into your service
7
+
8
+ Use this file as your base:
9
+
10
+ - `examples/service_receiver.py`
11
+
12
+ Run it locally:
13
+
14
+ ```bash
15
+ export NOTAMIFY_WEBHOOK_SECRET="your_webhook_secret"
16
+ export NOTAMIFY_WEBHOOK_PATH="/webhooks/notamify"
17
+ uv run python ./examples/service_receiver.py
18
+ ```
19
+
20
+ Replace `handle_notamify_event(event)` with your business logic.
21
+
22
+ ## 2) End-to-end local test against your service
23
+
24
+ This script:
25
+
26
+ - starts cloudflared tunnel to your local app origin
27
+ - derives watcher `webhook_url` from tunnel host + your local path
28
+ - creates/updates one listener in test mode (`mode=sandbox`)
29
+ - calls `POST /listeners/{id}/sandbox:send` to trigger a webhook
30
+ - retries transient DNS propagation errors
31
+
32
+ Prerequisite: `cloudflared` must be installed and available in `PATH`.
33
+
34
+ ```bash
35
+ export NOTAMIFY_TOKEN="your_notamify_token"
36
+ export LOCAL_APP_URL="http://127.0.0.1:8080/webhooks/notamify"
37
+ uv run python ./examples/local_service_run.py
38
+ ```
39
+
40
+ Optional:
41
+
42
+ - `NOTAMIFY_LISTENER_ID`
43
+ - `NOTAMIFY_LISTENER_NAME` (default `notamify-sdk-sandbox-example`)
44
+
45
+ Create a token in the [Notamify API Manager](https://notamify.com/api-manager).
46
+
47
+ ## 3) Fetch NOTAMs with the SDK pager
48
+
49
+ This script keeps the NOTAM examples intentionally simple. It makes a few direct SDK calls with the NOTAM pager resource:
50
+
51
+ - `client.notams.active(...)`
52
+ - `client.notams.raw(...)`
53
+ - `client.notams.nearby(...)`
54
+ - `client.notams.historical(...)`
55
+
56
+ It also shows how to access the first page metadata via `pager.pages`.
57
+
58
+ Use `per_page` values up to `30`; larger values are rejected by the SDK query models.
59
+
60
+ ```bash
61
+ export NOTAMIFY_TOKEN="your_notamify_token"
62
+ uv run python ./examples/notams_fetch.py
63
+ ```
64
+
65
+ Edit the ICAO codes, coordinates, or page sizes in `examples/notams_fetch.py` to match your own use case.
66
+
67
+ ## Webhook payload DTO
68
+
69
+ Sandbox test sends and production sends use the same webhook payload DTO. Watcher can send standard `interpretation` messages and optional `lifecycle` messages for later NOTAMC/NOTAMR changes when the listener has `lifecycle.enabled = true`.
70
+
71
+ ```json
72
+ {
73
+ "listener_id": "listener-123",
74
+ "kind": "interpretation",
75
+ "event_id": "event-123",
76
+ "notam": {
77
+ "id": "A1234/26",
78
+ "notam_number": "A1234/26",
79
+ "notam_type": "N",
80
+ "location": "EPWA",
81
+ "icao_code": "EPWA",
82
+ "qcode": "QMRLC",
83
+ "classification": "INTL",
84
+ "starts_at": "2026-02-25T12:00:00Z",
85
+ "ends_at": "2026-02-25T13:00:00Z",
86
+ "issued_at": "2026-02-25T11:55:00Z",
87
+ "is_estimated": false,
88
+ "is_permanent": false,
89
+ "message": "NOTAM text",
90
+ "icao_message": "NOTAM text",
91
+ "interpretation": {
92
+ "description": "Human summary",
93
+ "excerpt": "Short summary",
94
+ "category": "AERODROME",
95
+ "subcategory": "RUNWAY_OPERATIONS",
96
+ "map_elements": [],
97
+ "affected_elements": [],
98
+ "schedules": []
99
+ }
100
+ },
101
+ "context": {
102
+ "location": {
103
+ "ident": "EPWA",
104
+ "icao": "EPWA",
105
+ "name": "Warsaw Chopin Airport",
106
+ "iso_country": "PL",
107
+ "iso_country_name": "Poland",
108
+ "coordinates": {
109
+ "lat": 52.1657,
110
+ "lon": 20.9671
111
+ },
112
+ "fir_icaos": ["EPWW"]
113
+ }
114
+ },
115
+ "sent_at": "2026-02-25T13:09:51Z"
116
+ }
117
+ ```
118
+
119
+ Lifecycle payloads use the same top-level structure, but set `kind` to `"lifecycle"` and add:
120
+
121
+ ```json
122
+ {
123
+ "change": {
124
+ "changed_notam_id": "old-notam-uuid",
125
+ "notam_type": "R"
126
+ }
127
+ }
128
+ ```