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.