LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Digital Marketing / Amazon Ads Refresh Tokens: The 365-Day Limit...
Read this article in other languages:

Amazon Ads Refresh Tokens: The 365-Day Limit and Reliable Expiry Monitoring

Amazon Ads Refresh Tokens: The 365-Day Limit and Reliable Expiry Monitoring
Contents
  1. Amazon Ads Refresh Tokens: The 365-Day Limit and Reliable Expiry Monitoring
  2. 1. The scope of the 365-day rule
  3. 2. A ledger for authorizations, not raw tokens
  4. 3. A checker with explicit failure states
  5. 4. Scheduling and delivery
  6. 5. Renewal and error recovery
  7. Questions and answers
  8. Sources

Amazon Ads Refresh Tokens: The 365-Day Limit and Reliable Expiry Monitoring

Since 30 July 2026, newly issued refresh tokens for Amazon Ads advertising scopes have a fixed lifetime: 365 days from advertiser consent. Using a refresh token to obtain new access tokens does not restart that period. A local ledger can provide advance warnings, while error monitoring detects revoked or otherwise invalid credentials before the planned deadline.

An access token authenticates API requests and is normally valid for 60 minutes. A refresh token obtains new access tokens without repeating the consent dialog on every exchange.

1. The scope of the 365-day rule

Amazon applies the rule to tokens issued on or after 30 July 2026 for advertising::campaign_management and advertising::audiences. Earlier tokens have no fixed expiry under this change, but remain revocable and can become invalid for other reasons. The distinction depends on token issuance, not merely the date an advertiser account entered an integration.

The fixed period starts at consent. An exchange of a refresh token returns a new access token and the same refresh token; a new consent is a different operation. OAuth providers have different policies, so a sliding lifetime is not a general property of refresh tokens. Amazon Ads rules also should not be confused with Selling Partner API authorization rules.

The current Amazon Ads guide recommends proactive age monitoring and reauthorization. A blanket claim that Amazon never sends any email, console warning or other reminder is not established by that guide. Operational monitoring should work independently of an assumed provider notification.

Consent sets the fixed planning deadline. Repeated refresh calls do not extend it. Unknown metadata needs attention; legacy tokens have no fixed deadline under this rule.
Consent sets the fixed planning deadline. Repeated refresh calls do not extend it. Unknown metadata needs attention; legacy tokens have no fixed deadline under this rule.

2. A ledger for authorizations, not raw tokens

The PostgreSQL example stores one row per independently managed authorization. authorization_id is an internal stable identifier, and client_id identifies the application. Several applications or grants for the same advertiser need separate rows. An account name alone is not a unique authorization identifier. Actual refresh tokens and client secrets belong in protected secret storage, outside this table and alert output.

The policy field separates fixed_365, legacy and unknown. Legacy rows have no calculated expiry. Missing evidence belongs under unknown and triggers a warning, rather than becoming an invented consent date. For fixed_365, consent_date is the UTC calendar date of recorded consent and token_issued_at is the actual issuance timestamp. A trusted authorization log is needed to classify records around the cutoff.

CREATE TABLE amazon_ads_token_ledger (
    authorization_id text PRIMARY KEY,
    client_id        text NOT NULL,
    account_name     text NOT NULL,
    policy           text NOT NULL
                     CHECK (policy IN ('fixed_365', 'legacy', 'unknown')),
    consent_date     date,
    token_issued_at  timestamptz,
    expires_on       date GENERATED ALWAYS AS (
        CASE WHEN policy = 'fixed_365' THEN consent_date + 365 END
    ) STORED,
    last_refresh_ok  timestamptz,
    owner_email      text NOT NULL,
    status           text NOT NULL DEFAULT 'active'
                     CHECK (status IN ('active', 'revoked')),
    CHECK (policy <> 'fixed_365' OR
           (consent_date IS NOT NULL AND token_issued_at IS NOT NULL)),
    CHECK (policy <> 'fixed_365' OR
           (token_issued_at AT TIME ZONE 'UTC')::date >= DATE '2026-07-30'),
    CHECK (policy <> 'legacy' OR
           (token_issued_at IS NOT NULL AND
            (token_issued_at AT TIME ZONE 'UTC')::date < DATE '2026-07-30'))
);

CREATE INDEX ON amazon_ads_token_ledger (expires_on)
WHERE status = 'active';

expires_on is a conservative planning date: consent date plus 365 days. A date-only record cannot establish the exact server-side expiration instant. The checker treats the beginning of that UTC date as a critical planning boundary, so renewal belongs before that date. A generated column keeps arithmetic consistent with stored input; incorrect source dates still produce incorrect deadlines.

3. A checker with explicit failure states

This Python 3 example requires Psycopg 3. The database role only needs SELECT on the ledger. Connection details come from ADS_LEDGER_DSN; credentials can use PostgreSQL’s protected password file or a suitable service configuration. The example limits connection and query time and prints no DSN or token values on failure.

#!/usr/bin/env python3
import os
import sys
import psycopg

WARN_DAYS = 60
QUERY = """
SELECT authorization_id, account_name, policy, expires_on,
       expires_on - CURRENT_DATE AS days_left, owner_email
FROM amazon_ads_token_ledger
WHERE status = 'active'
  AND (policy = 'unknown' OR expires_on <= CURRENT_DATE + %s)
ORDER BY expires_on NULLS FIRST, authorization_id
"""

def render(rows):
    result = 0
    for aid, name, policy, expires, days, owner in rows:
        if policy == 'unknown':
            state = 'UNKNOWN: authorization metadata needs review'
            result = 2
        elif days <= 0:
            state = 'DUE: planning deadline reached; check authorization'
            result = max(result, 1)
        else:
            state = f'WARNING: {days} days to planning deadline'
        print(f'{aid} | {name} | {state} | deadline={expires} | owner={owner}')
    return result

def main():
    dsn = os.environ.get('ADS_LEDGER_DSN')
    if not dsn:
        print('CHECK FAILED: ADS_LEDGER_DSN is missing', file=sys.stderr)
        return 2
    try:
        with psycopg.connect(dsn, connect_timeout=5,
                options='-c timezone=UTC -c statement_timeout=5000') as conn:
            rows = conn.execute(QUERY, (WARN_DAYS,)).fetchall()
    except psycopg.Error as exc:
        # No DSN, credentials or token contents in diagnostic output.
        print(f'CHECK FAILED: {type(exc).__name__}', file=sys.stderr)
        return 2
    return render(rows)

if __name__ == '__main__':
    sys.exit(main())

Exit 0 means no critical condition was found; advance warnings still produce output. Exit 1 means a planning deadline has been reached. Exit 2 means unknown authorization metadata or a failed check. A monitoring wrapper must actually consume these codes. The 60-day threshold is a configurable organizational buffer, not an Amazon requirement or a measured universal approval duration.

4. Scheduling and delivery

# Service-user crontab; working local mail transport required.
MAILTO=ads-ops@example.com
ADS_LEDGER_DSN="dbname=ops user=ads_ledger_reader"
17 6 * * * /opt/ads/venv/bin/python /opt/ads/token_ledger_check.py

The paths and database role are deployment examples. The private Python environment must contain Psycopg, and local authentication must permit the read-only role. Cron runs at 06:17 in the scheduler’s configured time zone; the SQL date calculation is explicitly UTC. MAILTO requires working local mail transport and a tested recipient. The script itself does not send email, and owner_email only labels responsibility; it is not an automatic delivery address.

Output can produce a daily reminder while a condition persists. Escalation, acknowledgment and rate control belong in the surrounding monitoring. An independent heartbeat or missed-run check is necessary because a stopped scheduler cannot report its own failure. A last_refresh_ok field is useful only when the refresh worker updates it and a separate freshness check evaluates it.

5. Renewal and error recovery

An invalid refresh token produces HTTP 400 with invalid_grant according to Amazon. A reporting job must report that failure instead of replacing valid reports with empty data. Network failures may warrant bounded retries; repeated retries do not repair an invalid grant. Recovery requires a new authorization grant through the consent workflow. Successful renewal updates the secret and ledger coherently, followed by a real access-token exchange and a minimal authorized API check.

Questions and answers

What if the original consent date is unavailable?

An opaque refresh-token string is not a reliable consent-date record. Available authorization logs and onboarding evidence can establish the date; otherwise the row remains unknown. Planned reauthorization can establish a new recorded consent, but a speculative date should never silently turn an unknown authorization into a healthy one.

Do legacy tokens need annual renewal under this rule?

No fixed annual expiry applies to tokens issued before 30 July 2026 under the cited Ads rule. Revocation and other invalidation still need monitoring. A newly issued replacement token must be classified using the current issuance and consent evidence; ordinary access-token refresh is not replacement consent.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in touch

A question or an idea for a web project? Tell me a little about it. This form uses reCAPTCHA to protect against spam.

Write a comment

Practical experience and questions about the implementation are welcome here.

The email address is not published. Required fields are marked with an asterisk.