Skip to content

Authentication

Every request to the Google Ads API must carry three HTTP headers. They are checked in the order shown below, and the first failing check determines the error you receive.

# Header Value Required Checked
1 Authorization Bearer <OAuth 2.0 access token> Yes Scheme must be Bearer; token must be a known, non-expired access token
2 developer-token Your developer token Yes Must be on the developer-token allowlist
3 login-customer-id 9990001111 (the Nova Play manager account), dashes optional Recommended If present, must equal the manager account id

After the headers, the request is counted against your rate limit (60 requests per minute per access token, see Errors & quotas), and then the {customerId} in the path is checked against the accounts you may access (see Accounts).

Header details

Authorization: Bearer

The OAuth 2.0 access token identifies you. The sandbox issues long-lived tokens, so there is no refresh-token flow; you receive the token from your instructor and send it as-is. The scheme is case-insensitive (Bearer or bearer), the token is not.

developer-token

The developer token identifies the application making the calls. It is a separate secret from the access token and must be sent on every request, including customers:listAccessibleCustomers.

login-customer-id

When you act through a manager account, this header names the manager whose access rights apply to the request. For Nova Play the value is always 9990001111 (999-000-1111 is also accepted). The sandbox accepts requests without this header, but you should always send it: a request with a wrong value is rejected.

Example request

curl -s "https://googleads.huydata.com/v18/customers:listAccessibleCustomers" \
  -H "Authorization: Bearer $GOOGLE_ACCESS_TOKEN" \
  -H "developer-token: $GOOGLE_DEVELOPER_TOKEN" \
  -H "login-customer-id: 9990001111"
{
  "resourceNames": [
    "customers/5423891542",
    "customers/8340496580",
    "customers/2375371730",
    "..."
  ]
}

A minimal Python helper that attaches the headers to every call:

import os
import requests

BASE = "https://googleads.huydata.com/v18"
HEADERS = {
    "Authorization": f"Bearer {os.environ['GOOGLE_ACCESS_TOKEN']}",
    "developer-token": os.environ["GOOGLE_DEVELOPER_TOKEN"],
    "login-customer-id": "9990001111",
}

resp = requests.get(f"{BASE}/customers:listAccessibleCustomers", headers=HEADERS, timeout=30)
resp.raise_for_status()
customer_ids = [name.split("/")[1] for name in resp.json()["resourceNames"]]

Authentication and authorization errors

All errors use the google.rpc.Status envelope described in Errors & quotas. The table lists the exact code you receive for each failure; the JSON examples below are real responses.

Situation HTTP status details[0].errors[0].errorCode
Authorization header missing, or not Bearer … 401 UNAUTHENTICATED {"authenticationError": "AUTHENTICATION_ERROR"}
Bearer token unknown or expired 401 UNAUTHENTICATED {"authenticationError": "OAUTH_TOKEN_INVALID"}
developer-token header missing 401 UNAUTHENTICATED {"authenticationError": "DEVELOPER_TOKEN_PARAMETER_MISSING"}
developer-token not on the allowlist 403 PERMISSION_DENIED {"authorizationError": "DEVELOPER_TOKEN_NOT_ON_ALLOWLIST"}
login-customer-id present but not 9990001111 403 PERMISSION_DENIED {"authorizationError": "INVALID_LOGIN_CUSTOMER_ID_SERVING_CUSTOMER_ID_COMBINATION"}
{customerId} in the path is not one of your accessible customers 403 PERMISSION_DENIED {"authorizationError": "USER_PERMISSION_DENIED"}

The message inside errors[0] tells you which header is at fault.

Missing Authorization header

{
  "error": {
    "code": 401,
    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential.",
    "status": "UNAUTHENTICATED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authenticationError": "AUTHENTICATION_ERROR" },
            "message": "Authorization: Bearer <access token> header is missing."
          }
        ],
        "requestId": "2ba1d838d9ad45a3a74c"
      }
    ]
  }
}

Invalid or expired access token

{
  "error": {
    "code": 401,
    "message": "Request had invalid authentication credentials.",
    "status": "UNAUTHENTICATED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authenticationError": "OAUTH_TOKEN_INVALID" },
            "message": "The access token is invalid or expired."
          }
        ],
        "requestId": "75170ed3ac3a4452b0a7"
      }
    ]
  }
}

Missing developer-token header

{
  "error": {
    "code": 401,
    "message": "Request is missing required authentication credential.",
    "status": "UNAUTHENTICATED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authenticationError": "DEVELOPER_TOKEN_PARAMETER_MISSING" },
            "message": "The developer-token header is missing."
          }
        ],
        "requestId": "59d53a8ebfad4350a16e"
      }
    ]
  }
}

Developer token not on the allowlist

{
  "error": {
    "code": 403,
    "message": "The caller does not have permission",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authorizationError": "DEVELOPER_TOKEN_NOT_ON_ALLOWLIST" },
            "message": "The developer token is not allowed."
          }
        ],
        "requestId": "842d51f21c1149159e20"
      }
    ]
  }
}

Wrong login-customer-id

{
  "error": {
    "code": 403,
    "message": "The caller does not have permission",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authorizationError": "INVALID_LOGIN_CUSTOMER_ID_SERVING_CUSTOMER_ID_COMBINATION" },
            "message": "login-customer-id 1234567890 is not a manager of this account."
          }
        ],
        "requestId": "26641e18711f4123b15e"
      }
    ]
  }
}

Customer you cannot access

{
  "error": {
    "code": 403,
    "message": "The caller does not have permission",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.ads.googleads.v18.errors.GoogleAdsFailure",
        "errors": [
          {
            "errorCode": { "authorizationError": "USER_PERMISSION_DENIED" },
            "message": "User doesn't have permission to access customer 1234567890. Call customers:listAccessibleCustomers to see the customers you can query."
          }
        ],
        "requestId": "aa7e765e6f7c42f68923"
      }
    ]
  }
}

Security notes

  • Never commit tokens to source control or paste them into documentation. Read them from environment variables or a secrets manager.
  • Both tokens are personal. If a token leaks, ask your instructor to rotate it.
  • Each access token has its own 60 requests/minute quota; sharing a token between processes shares the quota too.