- ID
- 9786b791-9b2f-4976-9404-96d09a81c44e
TDD: V1 Endpoint Deprecation Gate
Overview
Two-layer DynamicConfig gate enabling no-code, no-deploy disabling of V1
endpoints as clients migrate. Global kill-switch for final teardown. Default
off — zero behavior change until explicitly configured.
V1 endpoints live at =/api/<action>/= (40 total, defined in =app/urls/apiv1.py=).
All have V2/V3 equivalents. Technical Roadmap '26 lists "v1 deprecation" as Q2+.
Status
IMPLEMENTED. Both layers default off.
Problem
Clients (mobile apps, internal services) migrate at different rates. Need to:
- Disable individual V1 endpoints as each client confirms migration — no deploy
- Observe remaining V1 traffic per endpoint before and after disabling
- Roll back within 5 minutes if a client breaks
- Kill all V1 at once when migration is complete
People
| Role | Name |
|------------------+--------------|
| Technical Author | Colin Powell |
Design
Two-Layer Gate
| Layer | Config key | Type | Default | Purpose |
|----------------------------+-------------------------------------+-----------+---------+---------------------------------------|
| 1 — Global kill-switch | =api.v1_endpoints.disabled= | Boolean | ="0"= | 410 ALL V1 endpoints. Emergency/teardown |
| 2 — Per-endpoint gates | =api.v1_endpoints.endpoint_gates= | JSON dict | ={}= | 410 individually named endpoints |
Per-Endpoint Gate Format
#+BEGIN_SRC json
{
"api-filter-pairings": "enabled",
"api-get-tag-group": "enabled",
"api-get-promo-data": "disabled"
}
#+END_SRC
- ="enabled"= → endpoint returns HTTP 410 Gone
- ="disabled"= or absent → passes through normally
- Unknown URL names → logged as warnings, ignored (typo protection)
Alternatives Rejected
| Approach | Reason |
|-------------------------+-------------------------------------------------------------------------|
| Per-endpoint bool flags | 40 DB rows, 40 enum members, 40 admin entries — overkill |
| Middleware | Fragile path-prefix matching; can't log resolved URL name |
| URL routing gate | =path(...)= evaluated at startup, not per-request |
| 410 vs 404 | 410 = "permanently gone" (RFC 9110); redirect impossible (different contract) |
| Decorator on each view | 40+ edits; gate applied in =apiv1.py= URL conf instead |
Response Body
#+BEGIN_SRC json
{"detail": "This V1 API endpoint has been deprecated. Please migrate to the V2/V3 API."}
#+END_SRC
Uses =detail= key per DRF house convention.
Implementation
Files Changed
| File | Change |
|--------------------------------------------+-----------------------------------------------------------------|
| =app/datatypes/dynamic_config.py= | Added =V1EndpointGates(RootModel[dict[str, str]])= Pydantic model |
| =app/constants/dynamic_config.py= | Added =V1_ENDPOINTS_DISABLED= to =APIDoomsdayConfig=; new =V1DeprecationConfig= |
| =app/views/api.py= | Added =_V1_URL_NAMES=, =_v1_gone_response=, =v1_endpoint_gate= |
| =app/urls/apiv1.py= | All 40 views wrapped with =v1_endpoint_gate(...)= at URL registration |
| =app/tests/views/test_apiv1_gate.py= | New test file — 9 tests, all passing |
Dynamic Config Definitions
#+BEGIN_SRC python
# In APIDoomsdayConfig:
V1_ENDPOINTS_DISABLED = (
"api.v1_endpoints.disabled",
(
"When enabled, ALL V1 /api/<action>/ endpoints return HTTP 410 Gone."
" Use for final teardown or emergencies once all clients have migrated."
" For per-endpoint control, use api.v1_endpoints.endpoint_gates instead."
" Remember to disable this if you need to roll back!"
),
"0",
StringValidationTypeEnum.BOOLEAN,
)
# New class:
@_register_configs
class V1DeprecationConfig(dc_datatypes.BaseDynamicConfig, enum.Enum):
ENDPOINT_GATES = (
"api.v1_endpoints.endpoint_gates",
(
"JSON dict mapping V1 URL names to their gate status."
" Set a URL name to 'enabled' to return HTTP 410 Gone for that endpoint (no deploy needed)."
' Example: {"api-filter-pairings": "enabled", "api-get-tag-group": "enabled"}.'
" URL names are defined in app/urls/apiv1.py."
" Unknown URL names are logged as warnings and ignored."
),
"{}",
PydanticJSONValidator(V1EndpointGates),
)
#+END_SRC
Decorator (app/views/api.py)
#+BEGIN_SRC python
def v1_endpoint_gate(view_func):
"""Gates a V1 view behind DynamicConfig. Returns HTTP 410 Gone when either layer is active.
Layer 1 (global): APIDoomsdayConfig.V1_ENDPOINTS_DISABLED — kills all V1 endpoints at once.
Layer 2 (per-endpoint): V1DeprecationConfig.ENDPOINT_GATES — a JSON dict of {url_name: 'enabled'}.
"""
@functools.wraps(view_func)
def wrapper(request, *args, **kwargs):
if bool_config_is_active(APIDoomsdayConfig.V1_ENDPOINTS_DISABLED):
return _v1_gone_response(request, view_func.__name__)
gates = DynamicConfigValueFetcher.get_validated_value(V1DeprecationConfig.ENDPOINT_GATES)
url_name = request.resolver_match.url_name if request.resolver_match else None
if url_name:
unknown = set(gates.root.keys()) - _V1_URL_NAMES
if unknown:
logger.warning("Unknown V1 URL names in endpoint gates config", unknown_names=sorted(unknown))
if gates.root.get(url_name) == "enabled":
return _v1_gone_response(request, view_func.__name__)
return view_func(request, *args, **kwargs)
return wrapper
#+END_SRC
Applied in apiv1.py
#+BEGIN_SRC python
from app.views.api import v1_endpoint_gate
urlpatterns = [
path("get_cancel_reasons/", v1_endpoint_gate(api.get_cancel_reasons), name="api-get-cancel-reasons"),
# ... all 40 endpoints wrapped
]
#+END_SRC
Tests
=app/tests/views/test_apiv1_gate.py= — 9 tests, all passing:
1. Both layers off → =/api/get_cancel_reasons/= returns 200
2. Global flag on → returns 410 with correct =detail= body
3. Global flag on → applies to multiple endpoints
4. Per-endpoint gate → named endpoint returns 410
5. Per-endpoint gate → other endpoints unaffected (returns non-410)
6. Per-endpoint ="disabled"= → passes through
7. Unknown URL name in gates → no crash, passes through
8. 410 response uses =detail= key
9. Global flag takes precedence over empty gates dict
Rollout
1. [ ] Merge PR (both layers off — no behavior change)
2. [ ] Check Datadog traffic per endpoint (links below) — identify zero-traffic endpoints
3. [ ] For each migrated endpoint: add ="api-<name>": "enabled"= to =api.v1_endpoints.endpoint_gates= in Django admin
4. [ ] Monitor 410s via Datadog =V1 endpoint disabled= log event after each disable
5. [ ] When all endpoints disabled individually → flip =api.v1_endpoints.disabled= to ="1"= (global kill-switch)
6. [ ] After 2-4 weeks confirmed zero traffic → remove URL patterns, view functions, enum members
Endpoint Traffic (Datadog Links)
Click to see traffic per endpoint (grouped by client, excluding bots):
| Endpoint | URL name | Datadog |
|-----------------------------------+-------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| =/api/facebook_login/= | =api-facebook-login= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-facebook-login%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/apple_login/= | =api-apple-login= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-apple-login%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/google_login/= | =api-google-login= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-google-login%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_hold_time_period_options/= | =api-get-hold-time-period-options= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-hold-time-period-options%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_cancel_reasons/= | =api-get-cancel-reasons= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-cancel-reasons%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_cancel_subreasons/= | =api-get-cancel-subreasons= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-cancel-subreasons%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/cancel_customer_account/= | =api-process-cancel= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-process-cancel%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/set_periodicity/= | =api-set-periodicity= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-set-periodicity%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/set_pairing_rating/= | =api-set-pairing-rating= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-set-pairing-rating%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_order/= | =api-get-order= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-order%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/skip_order/= | =api-skip-order= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-skip-order%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/unskip_order/= | =api-unskip-order= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-unskip-order%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/reschedule_order/= | =api-reschedule-order= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-reschedule-order%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/record_order_view_date/= | =api-record-order-view-date= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-record-order-view-date%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/update_day_of_week/= | =api-update-day-of-week= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-update-day-of-week%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_product/= | =api-get-product= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-product%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_pairing/= | =api-get-pairing= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-pairing%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/filter_pairings/= | =api-filter-pairings= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-filter-pairings%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_product_categories/= | =api-get-product-categories= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-product-categories%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_pairing_dishtypes/= | =api-get-pairing-dishtypes= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-pairing-dishtypes%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_customer_dishtypes/= | =api-get-customer-dishtypes= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-customer-dishtypes%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/update_customer_dishtypes/= | =api-update-customer-dishtypes= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-update-customer-dishtypes%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_dishtypes/= | =api-get-dishtypes= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-dishtypes%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/auto_issue_credit/= | =api-auto-issue-credit= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-auto-issue-credit%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_discovery_survey_choices/= | =api-get-discovery-survey-choices= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-discovery-survey-choices%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/post_discovery_survey_choice/= | =api-post-discovery-survey-choice= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-post-discovery-survey-choice%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_possible_days/= | =api-get-possible-days= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-possible-days%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/save_shipping_day/= | =api-save-shipping-day= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-save-shipping-day%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/save_payment/= | =api-save-payment= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-save-payment%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/place_checkout_order/= | =api-place-checkout-order= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-place-checkout-order%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/submit_promo/= | =api-submit-promo= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-submit-promo%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_tag_group/= | =api-get-tag-group= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-tag-group%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_tag_groups/= | =api-get-tag-groups= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-tag-groups%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_customer_tags/= | =api-get-customer-tags= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-customer-tags%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/update_customer_tags/= | =api-update-customer-tags= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-update-customer-tags%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_customer_dashboard/= | =api-get-customer-dashboard= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-customer-dashboard%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/update_customer/= | =api-update-customer= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-update-customer%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_promo_data/= | =api-get-promo-data= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-promo-data%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/get_cyo_promos/= | =api-get-cyo-promos= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-get-cyo-promos%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
| =/api/update_customer_cyo/= | =api-update-customer-cyo= | [[https://app.datadoghq.com/logs?query=env%3Aprod%2A%20service%3Adjango%20%40route%3Aapi-update-customer-cyo%20-%40user_agent%3A%2Abot%2A&agg_m=count&agg_m_source=base&agg_q=%40route%2C%40client_name&agg_t=count&flat_group_bys=true&refresh_mode=sliding&sort_m=count&sort_t=count&storage=hot&top_n=1000&top_o=top&viz=query_table&x_missing=true&from_ts=1780704000000&to_ts=1782000000000&live=true][View traffic]] |
Risks
| Risk | Mitigation |
|---------------------------------------------+---------------------------------------------------------------------|
| Typo in JSON key → wrong endpoint | Unknown names logged as warnings; validated against =_V1_URL_NAMES= |
| High-traffic endpoints (get_tag_group, get_promo-data) | Check Datadog links above; only enable after confirmed zero |
| 5-min DC cache = up to 5-min rollback lag | Same as existing doomsday flags; acceptable SLA |
| Unauthenticated callers get 410 pre-auth | Intentional — endpoint is gone. Check client handling first |