---
title: "Como corrigir JSONDecodeError: Expecting Value no Python"
description: "JSONDecodeError: Expecting Value significa que o parser não achou JSON no primeiro caractere: o corpo está vazio, é HTML ou texto puro. Veja como achar a causa."
url: https://proxynet.io/pt-br/blog/jsondecodeerror-expecting-value
date: 2026-09-24
author: "Acar Diveroli"
category: "Tutoriais, Web scraping"
lang: pt-BR
---

# Como corrigir JSONDecodeError: Expecting Value no Python

Você coleta a lista de produtos de uma loja em `/api/products?page=1`, um endereço que encontrou no painel Network do navegador. O script percorre as primeiras 300 páginas e então para em `data = r.json()` com `requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)`. Mesmo endereço, mesmo código. Você acrescenta uma linha, `print(r.status_code, r.headers.get("Content-Type"), r.text[:200])`, e o quadro muda: `403`, `text/html` e uma página que começa com `<!DOCTYPE html>`. O servidor mandou uma página web em vez de JSON, e o parser desistiu no primeiro caractere, `<`.

Este guia explica o que significam a mensagem e a posição que ela aponta, qual exceção cada biblioteca Python lança e uma verificação de três linhas que encontra a causa. Depois passa por respostas vazias, páginas HTML, a resposta `407` de um proxy e corpos que só parecem JSON, e termina com uma função auxiliar `parse_json()` testada.

> **Nota: Resposta rápida**
>
> `JSONDecodeError: Expecting value: line 1 column 1 (char 0)` significa que o parser de JSON do Python não encontrou nenhum valor JSON no primeiro caractere do texto. A conexão funcionou; o problema está no corpo. Ou ele está vazio (`204`, uma requisição `HEAD`, um `200` vazio), ou não é JSON: uma página de bloqueio, um redirecionamento para o login, uma página de erro do servidor, a página `407` de um proxy, um limite de taxa em texto puro ou JSONP. Antes de `r.json()`, confira `r.status_code`, `r.headers.get("Content-Type")` e `r.text[:200]`. Se o status não for `2xx` ou o tipo não for `application/json`, não leia o corpo como JSON; descubra primeiro por que o servidor mandou outra coisa.

## O que significa JSONDecodeError: Expecting value?

Significa que o parser esperava o início de um valor JSON e encontrou outra coisa. Um texto JSON é um único valor, como define a [RFC 8259](https://www.rfc-editor.org/rfc/rfc8259): um objeto, um array, uma string entre aspas duplas, um número, `true`, `false` ou `null`. Por isso, um texto válido só pode começar com `{`, `[`, `"`, um dígito, `-`, `t`, `f` ou `n`, depois de espaços em branco opcionais. Quando o parser encontra o `<` de uma página HTML, o `T` de `Too Many Requests` ou o fim de uma string vazia, ele para com "Expecting value".

Isso não é um erro de conexão. Uma requisição que nunca chegou ao servidor falha antes, dentro de `requests.get()`, com erros como `ConnectionError` ou `ProxyError` ([Max Retries Exceeded With URL](/pt-br/blog/max-retries-exceeded-with-url)). Quando você vê `JSONDecodeError`, uma resposta chegou; só o conteúdo dela não pôde ser lido como JSON.

## O que "line 1 column 1 (char 0)" indica?

Os números mostram onde a leitura falhou. `json.JSONDecodeError` os guarda como atributos: `msg` (o motivo), `doc` (o texto inteiro), `pos` (o índice do caractere que falhou), `lineno` e `colno` ([documentação do json no Python](https://docs.python.org/3/library/json.html#json.JSONDecodeError)). `char 0` é o primeiro caractere do corpo, então nenhum JSON chegou a começar.

Outras posições dizem mais:

- `line 1 column 4 (char 3)`: o corpo tinha três espaços e mais nada. Os espaços em branco são ignorados, e então o texto acaba.
- `line 2 column 1 (char 1)`: o corpo começa com uma quebra de linha, seguida de algo que não é JSON, muitas vezes uma página HTML.
- Uma posição bem dentro do texto: o JSON começou, mas quebrou depois, por exemplo em um download cortado.

Dentro de um bloco `except`, `e.doc[:200]` mostra o início desse texto.

## Qual exceção requests, json, httpx e aiohttp lançam?

Verificamos cada biblioteca com Python 3.13, Requests 2.34.2, HTTPX 0.28.1 e AIOHTTP 3.14.3:

- **json:** `json.loads()` lança `json.JSONDecodeError`, uma subclasse de `ValueError`.
- **Requests:** desde a versão 2.27.0 (janeiro de 2022), `r.json()` lança `requests.exceptions.JSONDecodeError`. Segundo o [changelog do Requests](https://github.com/psf/requests/blob/main/HISTORY.md), ela herda das exceções lançadas antes e também é uma `RequestException`.
- **Requests com simplejson instalado:** a classe-mãe passa a ser `simplejson.errors.JSONDecodeError`. No nosso teste, `except json.JSONDecodeError` então não a capturou; `except ValueError` ainda capturou.
- **HTTPX:** `Response.json()` lança o `json.decoder.JSONDecodeError` padrão.
- **AIOHTTP:** `await resp.json()` confere o Content-Type primeiro e lança `ContentTypeError` (`Attempt to decode JSON with unexpected mimetype: text/html`) sem ler o corpo. Com `content_type=None`, lança `json.JSONDecodeError`.

Com o Requests, capture `requests.exceptions.JSONDecodeError`: funciona nos dois casos. Outras diferenças entre os clientes estão em [Comparativo entre HTTPX, Requests e AIOHTTP](/pt-br/blog/httpx-vs-requests-vs-aiohttp).

## Como encontrar a causa em três linhas?

Imprima o que chegou antes de ler como JSON:

```python
print(r.status_code, r.history, r.url)
print(r.headers.get("Content-Type"), len(r.content))
print(r.text[:200])
```

Depois leia a saída nesta ordem:

1. **Status e histórico.** O status é `2xx`? Houve um `301` ou `302` no caminho? Depois de um redirecionamento, `r.status_code` mostra o `200` final, e só `r.history` mostra `[<Response [302]>]`.
2. **URL final.** `r.url` é o endereço depois dos redirecionamentos. Se terminar em `/login` ou `/consent`, você não chegou à API.
3. **Content-Type.** Você quer `application/json` ou um tipo que termine em `+json`. Qualquer outro aponta para uma das causas abaixo.
4. **Tamanho e primeiros caracteres.** `0` significa vazio, `<` significa HTML, `{'` um dict do Python impresso como texto, `cb(` JSONP.
5. **Compare o resultado com a tabela abaixo.**

Registre no log só os primeiros 200 caracteres: um corpo inteiro pode conter tokens ou dados pessoais.

## O que os primeiros caracteres do corpo revelam?

Todas as mensagens abaixo vêm da nossa execução com Python 3.13.9 e Requests 2.34.2 contra um servidor de teste local; outras versões do Python podem redigi-las de outro jeito.

| Início do corpo | Status e Content-Type típicos | Mensagem de `r.json()` | Causa provável | O que fazer |
|---|---|---|---|---|
| (nada) | `204`, `304`, `HEAD` ou um `200` vazio | `Expecting value: line 1 column 1 (char 0)` | O endpoint não devolve conteúdo | Confira o status e `len(r.content)` antes de ler |
| `<!DOCTYPE html>` | `403`, `429`, `503`, ou `200` depois de um `302`; `text/html` | `Expecting value: line 1 column 1 (char 0)` | Página de bloqueio, redirecionamento para o login, página de erro | Resolva o status primeiro |
| `<html>...407...` ou nada | `407` e `Proxy-Authenticate`, destino `http://` | `Expecting value: line 1 column 1 (char 0)` | Credenciais ou IP do proxy errados | Confira `user:pass` e a whitelist de IP |
| `Too Many Requests` | `429`, `text/plain` | `Expecting value: line 1 column 1 (char 0)` | Um limite de taxa em texto puro | Diminua o ritmo; leia `Retry-After` |
| `cb({"items": ...});` | `200`, `application/javascript` | `Expecting value: line 1 column 1 (char 0)` | JSONP | Use o endpoint sem callback |
| `{"id":1}`, depois uma nova linha e `{"id":2}` | `200`, `application/x-ndjson` | `Extra data: line 2 column 1 (char 9)` | NDJSON | Leia linha por linha |
| `{'id': 1, ...}` | `200`, muitas vezes `text/plain` | `Expecting property name enclosed in double quotes: line 1 column 2 (char 1)` | Um dict escrito com `str()` | `json.dumps()` no código que gera os dados |
| Um BOM invisível, depois `{` | `200`, `application/json` | `Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)` | Uma marca de ordem de bytes | Decodifique com `utf-8-sig` |

As cinco primeiras linhas da tabela têm a mesma mensagem, então a mensagem sozinha nunca diz a causa; o status e o Content-Type dizem.

## Respostas vazias: 204, HEAD e 304

Algumas respostas não têm corpo por definição. A [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.3.5) estabelece que uma resposta `204 No Content` não pode ter conteúdo, que um `304 Not Modified` também não tem, e que a resposta a uma requisição `HEAD` traz só cabeçalhos. Muitas APIs respondem a `DELETE` e `PUT` com `204`: a ação deu certo e não há nada para ler, mas `r.json()` lança `Expecting value`.

Trate essas respostas como "sem dados", não como erros, e confira `r.status_code` antes de ler o corpo. Um `200` vazio é diferente: em geral indica uma falha do servidor ou um endpoint errado, e merece uma linha no log.

## HTML em vez de JSON: páginas de bloqueio, de login e de erro

Três tipos de página HTML chegam onde se esperava JSON, e o código de status os diferencia.

**Uma página de bloqueio ou de verificação.** Um `403`, `429` ou `503` com `text/html` costuma ser a resposta de uma proteção contra bots. O `<title>` dela, como "Just a moment..." ou "Access denied", basta para reconhecê-la. Não tente ler essa página como JSON nem repita a requisição em loop. O que cada código significa está em [Códigos de status HTTP no web scraping](/pt-br/blog/http-status-codes-web-scraping), as páginas da Cloudflare em [Cloudflare scraper](/pt-br/blog/cloudflare-scraper) e a pontuação de bots em [Como funciona a detecção de bots](/pt-br/blog/how-bot-detection-works). Os caminhos legítimos são uma API oficial, um ritmo menor que respeite o `robots.txt` ou a permissão do dono do site.

**Uma página de login depois de um redirecionamento.** O status é `200`, então esse caso passa despercebido com facilidade. `r.history` mostra `[<Response [302]>]` e `r.url` termina em `/login`: a sua sessão expirou. Na sua própria conta, a correção é cuidar da sessão ([Sessões e cookies em Python](/pt-br/blog/python-login-session-cookies)).

**Uma página de erro do servidor.** Um `500`, `502` ou `504` com HTML vem do servidor ou de um gateway na frente dele: o problema está na API, não no seu parser.

HTML também chega quando a URL aponta para a página, e não para a API. O JSON vem de uma requisição separada que a página faz, e você a encontra no painel Network ([primeiro encontre a requisição da API](/pt-br/blog/static-vs-dynamic-pages)).

## Por um proxy: a resposta 407

Envie uma requisição `http://` simples por um proxy com a senha errada, e o próprio proxy responde `407 Proxy Authentication Required`. O Requests entrega essa resposta ao seu código como uma resposta normal, com o corpo que o proxy mandar: uma página HTML, um texto curto ou nada. Com dois proxies de teste locais, um que mandava HTML e outro que mandava um corpo vazio, `r.json()` deu `Expecting value` nas duas vezes.

Para um destino `https://`, o `407` chega enquanto o túnel é montado, então `requests.get()` lança `ProxyError: Tunnel connection failed: 407` e o código nunca chega a `r.json()` ([Max Retries Exceeded With URL](/pt-br/blog/max-retries-exceeded-with-url)).

Um `407` com um cabeçalho `Proxy-Authenticate` aponta para o proxy, não para o destino. Confira o usuário e a senha, codifique os caracteres especiais na URL do proxy (`@` vira `%40`) ou confirme que o seu IP está na whitelist ([Autenticação de proxy: user:pass ou whitelist de IP](/pt-br/blog/proxy-authentication-methods)).

## Corpos que parecem JSON, mas não são: JSONP, NDJSON e aspas simples

Alguns corpos contêm JSON, mas não como um único valor limpo.

### JSONP

O JSONP envolve o JSON em uma chamada de função, `cb({"items": [1]});`, em geral enviada como `application/javascript`. O parser vê o `c` e informa `Expecting value` em `char 0`. Use o endpoint sem o parâmetro `callback` ou pegue o texto entre o primeiro `(` e o último `)`.

### NDJSON e JSON Lines

Endpoints de exportação e de streaming muitas vezes mandam um valor JSON por linha, um formato descrito em [jsonlines.org](https://jsonlines.org/). A primeira linha é lida, depois o parser encontra mais texto e para com `Extra data: line 2 column 1`. Leia esses corpos linha por linha com `r.iter_lines()`.

### Aspas simples e valores do Python

Um corpo como `{'id': 1}` foi escrito com o `str()` do Python em vez de `json.dumps()`, e o parser para em `char 1` com `Expecting property name enclosed in double quotes`. Um corpo `None` ou `True`, na grafia do Python, falha com `Expecting value`, porque o JSON escreve `null` e `true`. Corrija o código que escreve os dados: `text.replace("'", '"')` quebra qualquer valor com apóstrofo, e `ast.literal_eval()` só é seguro com dados que você mesmo produziu. Se é você quem envia esses dados a uma API com o Python Requests, passe o dict para `json=` em vez de convertê-lo em texto com `str()`; explicamos a diferença entre `json=` e `data=` no artigo [POST com JSON no Python Requests: equivalentes do cURL](/pt-br/blog/python-requests-post-json).

Uma marca de ordem de bytes (BOM) no início dá `Unexpected UTF-8 BOM`; o lado da codificação está em [Erros de codificação no Python](/pt-br/blog/python-unicode-encoding-errors).

## Exemplo completo: parse_json() que confere o corpo antes de ler o JSON

A função auxiliar transforma a verificação de três linhas em código. Ela devolve os dados lidos, `None` para respostas que não têm corpo por definição, ou lança um único erro `NotJSON` que informa o status, os redirecionamentos, o Content-Type, a URL final e o início do corpo. Instale o Requests com `pip install requests`.

```python
"""Lê uma resposta JSON ou diz em uma linha por que o corpo não é JSON."""
import json

import requests

PROXY = "http://user:pass@pr.proxynet.io:8000"
PROXIES = {"http": PROXY, "https": PROXY}

class NotJSON(ValueError):
    """O servidor respondeu, mas não com o JSON que pedimos."""

def describe(r):
    """Status, redirecionamentos, Content-Type, URL final e os primeiros 200 caracteres."""
    hops = "".join(f"{h.status_code} -> " for h in r.history)
    ctype = r.headers.get("Content-Type", "none")
    start = r.text[:200].replace("\n", " ")
    return f"HTTP {hops}{r.status_code}, {ctype}, {r.url}, body {start!r}"

def media_type(r):
    return r.headers.get("Content-Type", "").split(";")[0].strip().lower()

def is_json_type(mtype):
    return mtype == "application/json" or mtype.endswith("+json")

def parse_json(r):
    """Devolve o corpo lido, None para "sem conteúdo", ou lança NotJSON com o motivo."""
    if r.status_code in (204, 304) or r.request.method == "HEAD":
        return None  # essas respostas não têm corpo por definição
    mtype = media_type(r)
    if not r.ok and not is_json_type(mtype):
        raise NotJSON(f"error response, not JSON: {describe(r)}")
    if not r.content:
        raise NotJSON(f"empty body: {describe(r)}")
    if mtype == "application/x-ndjson":
        return [json.loads(line) for line in r.iter_lines() if line.strip()]
    if r.text.lstrip().startswith("<"):
        raise NotJSON(f"HTML instead of JSON: {describe(r)}")
    try:
        return r.json()
    except requests.exceptions.JSONDecodeError as e:
        raise NotJSON(f"{e.msg} at char {e.pos}: {describe(r)}") from e

if __name__ == "__main__":
    url = "https://example.com/api/products?page=1"
    r = requests.get(url, proxies=PROXIES, timeout=(5, 30))
    try:
        data = parse_json(r)
    except NotJSON as e:
        print("stop:", e)
    else:
        if not r.ok:
            print("API error:", r.status_code, data)
        elif data is None:
            print("no content")
        else:
            print("ok:", type(data).__name__, len(data))
```

Um status de erro com corpo que não é JSON para logo no início, então uma página `403` ou o `407` de um proxy nunca passa pelo parser. Um status de erro com corpo JSON, como `400` com `{"error": ...}`, é devolvido, porque muitas APIs explicam os erros assim; quem chama a função confere `r.ok`. `PROXIES` é opcional, e `timeout=(5, 30)` dá 5 segundos para a conexão e 30 para a resposta.

A função auxiliar não faz novas tentativas, não rotaciona IPs e não roda em paralelo, e isso é de propósito. Quais status merecem uma nova tentativa está em [Códigos de status HTTP no web scraping](/pt-br/blog/http-status-codes-web-scraping), a rotação em [Como rotacionar proxies em Python](/pt-br/blog/how-to-rotate-proxies-in-python) e as requisições paralelas em [Concorrência e paralelismo no web scraping](/pt-br/blog/concurrency-vs-parallelism).

### Como fica a saída

Rodamos `parse_json()` contra um servidor de teste local que responde a cada caminho com um corpo da tabela; a última linha passou por um proxy de teste que respondeu `407` com HTML.

```text
/api/products -> {'items': [1, 2, 3]}
/api/items/7 -> None
/api/empty -> NotJSON: empty body: HTTP 200, application/json, http://127.0.0.1:8111/api/empty, body ''
/api/blocked -> NotJSON: error response, not JSON: HTTP 403, text/html; charset=utf-8, http://127.0.0.1:8111/api/blocked, body '<!DOCTYPE html><html><head><title>Just a moment...</title></head></html>'
/api/private -> NotJSON: HTML instead of JSON: HTTP 302 -> 200, text/html; charset=utf-8, http://127.0.0.1:8111/login, body '<!DOCTYPE html> <html><head><title>Sign in</title></head></html>'
/api/slow -> NotJSON: error response, not JSON: HTTP 429, text/plain, http://127.0.0.1:8111/api/slow, body 'Too Many Requests'
/api/jsonp -> NotJSON: Expecting value at char 0: HTTP 200, application/javascript, http://127.0.0.1:8111/api/jsonp, body 'cb({"items": [1]});'
/api/export -> [{'id': 1}, {'id': 2}]
/api/dict -> NotJSON: Expecting property name enclosed in double quotes at char 1: HTTP 200, text/plain, http://127.0.0.1:8111/api/dict, body "{'id': 1, 'name': 'Lamp'}"
/api/bad-request -> {'error': 'page must be a number'} (status 400)
proxy, wrong password -> NotJSON: error response, not JSON: HTTP 407, text/html, http://example.com/api/products, body '<html><head><title>407 Proxy Authentication Required</title></head><body><h1>407</h1></body></html>'
```

`/api/items/7` respondeu `204` e `/api/export` mandou NDJSON, então nenhum dos dois é erro. A linha de `/api/private` mostra o redirecionamento que uma verificação de status não pega: `302 -> 200`, terminando em `/login`.

## Casos de uso: quais scripts que esperam JSON encontram esse erro?

- **Chamar a API do próprio site:** a requisição que você copiou do painel Network para de funcionar quando a sessão ou o token por trás dela expira ([páginas estáticas e dinâmicas](/pt-br/blog/static-vs-dynamic-pages)).
- **Monitoramento de preços:** uma tarefa diária que lê o JSON dos produtos recebe uma página de bloqueio no dia em que roda rápido demais ([monitoramento de preços da concorrência](/pt-br/blog/competitor-price-tracking)).
- **Paginação de API:** a página depois da última pode devolver `204` ou um corpo vazio em vez de uma lista vazia ([paginação no web scraping](/pt-br/blog/pagination-web-scraping)).
- **Ferramentas de automação:** um nó HTTP Request do n8n espera JSON e recebe uma página de erro em HTML ([proxy no n8n](/pt-br/blog/n8n-proxy)).
- **Pipelines de dados:** uma resposta HTML entre milhares de respostas JSON deve parar um lote, não acabar no banco de dados ([extração de dados](/pt-br/data-scraping)).
- **Crawlers:** um crawler que lê endpoints JSON em muitos hosts precisa de uma mensagem clara para cada host que falhou ([web crawler](/pt-br/web-crawler)).

## Erros comuns

- **Engolir o erro.** `except JSONDecodeError: pass` não guarda nada e esconde a causa.
- **Confiar no status 200.** Uma página de login depois de um `302` também chega como `200`. Confira `r.history` e `r.url`.
- **Trocar aspas simples por aspas duplas com `replace()`.** Isso quebra todo valor que contém um apóstrofo.
- **Usar `eval()` no corpo de uma resposta.** Ele executa qualquer código que o servidor tenha mandado.
- **Repetir uma página de bloqueio no mesmo ritmo.** As mesmas requisições no mesmo ritmo recebem o mesmo `429` ou `403`; diminua o ritmo primeiro ([429 Too Many Requests](/pt-br/blog/http-429-too-many-requests)).
- **Registrar o corpo inteiro no log.** Os primeiros 200 caracteres dizem a causa; o resto pode ter tokens e dados pessoais.
- **Um único `except RequestException` em volta de `get()` e `json()`.** Desde a 2.27.0 ele captura os dois, então um erro de rede e um erro de leitura do JSON ficam iguais.
- **Procurar o bug no módulo json.** O parser está certo; o corpo é que não é JSON.

## Guia de decisão

| O que você vê | O que fazer |
|---|---|
| Status `204`, ou o corpo está vazio | Não chame `r.json()`; trate como "sem dados" |
| `403`, `429` ou `503` com `text/html` | Não leia como JSON e resolva o status ([Códigos de status HTTP no web scraping](/pt-br/blog/http-status-codes-web-scraping)) |
| `200`, mas `r.history` mostra um `302` para uma página de login | Renove a sua sessão ([Sessões e cookies em Python](/pt-br/blog/python-login-session-cookies)) |
| `407` com corpo HTML ou vazio | Confira as credenciais do proxy e a whitelist de IP |
| `ProxyError: Tunnel connection failed: 407` | A mesma causa em destinos `https://` ([Max Retries Exceeded With URL](/pt-br/blog/max-retries-exceeded-with-url)) |
| `Extra data` | Leia linha por linha com `r.iter_lines()` |
| O corpo começa com `callback(` | Use o endpoint sem callback ou remova o invólucro |
| `Unexpected UTF-8 BOM` | Decodifique com `utf-8-sig` ([Erros de codificação no Python](/pt-br/blog/python-unicode-encoding-errors)) |

## Perguntas frequentes

### Por que r.json() falha quando o código de status é 200?

Um `200` não diz nada sobre o formato do corpo. Uma página de login depois de um redirecionamento, uma resposta JSONP ou um corpo vazio podem vir com `200`. Confira o Content-Type, `r.history` e o início de `r.text`.

### Qual é a diferença entre requests.exceptions.JSONDecodeError e json.JSONDecodeError?

`r.json()` lança `requests.exceptions.JSONDecodeError` desde o Requests 2.27.0. Ela é subclasse do `JSONDecodeError` da biblioteca de JSON e da `RequestException` do próprio Requests, então as duas a capturam. A exceção a essa regra é o simplejson: quando ele está instalado, `except json.JSONDecodeError` não pega o erro, então capture a classe do Requests.

### Por que recebo JSONDecodeError: Extra data?

O parser leu um valor JSON completo e depois encontrou mais texto: em geral NDJSON, ou dois objetos escritos um depois do outro. Leia linha por linha ou use `json.JSONDecoder().raw_decode()` para ler um valor de cada vez.

### O que significa "Expecting property name enclosed in double quotes"?

Uma chave dentro de um objeto não está entre aspas duplas. A causa mais comum é um dict do Python escrito com `str()`, que usa aspas simples. No Python 3.12 e em versões anteriores, uma vírgula sobrando antes de `}` também dá essa mensagem; o Python 3.13 a informa como `Illegal trailing comma before end of object`.

### Recebo esse erro no yfinance ou no spotdl. O que devo fazer?

A biblioteca pediu JSON a um serviço remoto e recebeu outra coisa. Atualize a biblioteca, chame-a com menos frequência e procure a mesma mensagem nas issues do projeto.

### Como fica o mesmo erro em JavaScript?

No Node.js 24, `JSON.parse()` sobre uma página HTML lança `SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON`, e sobre uma string vazia, `SyntaxError: Unexpected end of JSON input`. Lá também, confira o status e o Content-Type antes de `response.json()` ([cURL em JavaScript](/pt-br/blog/curl-in-javascript)). O aviso do editor do WordPress "The response is not a valid JSON response" é outro problema.

## Em resumo

`JSONDecodeError: Expecting value` é um sintoma, não a causa. A conexão funcionou e o servidor respondeu, mas o corpo estava vazio ou não era JSON. Três verificações encontram a causa: o código de status com `r.history`, o Content-Type e os primeiros 200 caracteres do corpo. Um `204` vazio é normal, uma página HTML indica bloqueio, login ou página de erro, um `407` aponta para o proxy, e `Extra data` ou aspas simples significam que o corpo só parece JSON. Os tipos de proxy que você pode colocar na frente de um trabalho assim estão na nossa página de [serviços de proxy](/pt-br/proxy).
