Discovering and Interpreting API Errors in the Logs

Last edited: 9/9/2025

A complimentary guide was made for the Postgres logs

Navigating the API logs:

The Database API is powered by a PostgREST web-server, recording every request to the API Edge Network logs. To precisely navigate them, use the Log Explorer. These logs are managed through Logflare and can be queried with a subset of BigQuery SQL syntax.

The log table that contains API requests is edge_logs.

Notably, it contains:

fielddescription
event_messagethe log's message
timestamptime event was recorded
request metadatametadata about the REST request
response metadatametadata about the REST response

The request and response columns are arrays in the metadata field and must be unnested. This is done with a cross join.

Unnesting example

1
select
2
-- the event message does not require unnesting
3
event_message,
4
-- unnested status_code column from metadata.response field
5
status_code
6
from
7
edge_logs
8
-- Unpack data stored in the 'metadata' field
9
cross join unnest(metadata) as metadata
10
-- After unpacking the 'metadata' field, extract the 'response' field from it
11
cross join unnest(response) as response;

The most useful fields for debugging are:

NOTE: not every field is included below. For a full list, check the API Edge field reference in the Log Explorer

Request object

Cloudflare geographic data:

Suggested use cases:

  • Detecting abuse from a specific region
  • Detecting activity spikes from certain regions
ColumnDescriptionSample value
request.cf.cityRequester's cityMunich
request.cf.countryRequester's countryDE
request.cf.continentRequester's continentEU
request.cf.regionRequester's regionBavaria
request.cf.latitudexRequester's latitude48.10840
request.cf.longitudeRequester's longitude11.61020
request.cf.timezoneRequester's timezoneEurope/Berlin

Unnesting example:

1
select
2
city
3
from
4
edge_logs
5
-- Unpack 'metadata' field
6
cross join unnest(metadata) AS metadata
7
-- unpack 'request' from 'metadata'
8
cross join unnest(request) AS request;
9
-- unpack 'cf' from 'request'
10
cross join unnest(cf) AS cf;

IP and browser/environment data:

Suggested use cases:

  • Detecting request behavior from IP
  • Detecting abuse by IP
  • Detecting errors by user_agent
ColumnDescriptionSample value
request.headers.cf_connecting_ipRequester's IP80.81.18.138
request.headers.user_agentRequester's browser or app environmentMozilla/5.0 (Linux; Android 11; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36

Unnesting example:

1
select
2
cf_connecting_ip
3
from
4
edge_logs
5
-- Unpack 'metadata' field
6
cross join unnest(metadata) AS metadata
7
-- unpack 'request' from 'metadata'
8
cross join unnest(request) AS request;
9
-- unpack 'headers' from 'request'
10
cross join unnest(headers) AS headers;

Query type and formatting data:

Suggested use cases:

  • identify problematic queries
  • identify unusual behavior by authenticated users
ColumnDescriptionSample value
request.methodRequest Method (PATCH, GET, PUT...)GET
request.urlRequest URL, which contains the PostgREST formatted queryhttps://yuhplfrsdxxxtldakizi.supabase.co/rest/v1/users?select=username&id=eq.63b6190e-214f-4b8a-b72d-3af6e1921411&limit=1
request.sb.auth_usersauthenticated user's ID63b6190e-214f-4b8a-b72d-3af6e1921411

Unnesting example:

1
select
2
method,
3
url,
4
auth_users
5
from
6
edge_logs
7
-- Unpack 'metadata' field
8
cross join unnest(metadata) AS metadata
9
-- unpack 'request' from 'metadata'
10
cross join unnest(request) AS request;
11
-- unpack 'sb' from 'request'
12
cross join unnest(sb) AS sb;

Response object

Status code:

Suggested use cases:

  • detect success/errors
ColumnDescriptionSample value
response.status_codeResponse status code (200, 404, 500...)404

Unnesting example:

1
select
2
status_code
3
from
4
edge_logs
5
-- Unpack 'metadata' field
6
cross join unnest(metadata) as metadata
7
-- unpack 'response' from 'metadata'
8
cross join unnest(response) as response;

Finding errors

API level errors

The metadata.request.url contains PostgREST formatted queries.

For example, the following call to the JS client:

1
let { data: countries, error } = await supabase.from('countries').select('name')

translates to calling the following endpoint:

1
https://<project ref>.supabase.co/rest/v1/countries?select=name

You can use regex (Advanced Regex Guide) to find the objects related to your query. Try isolating by:

  • function names
  • column names
  • table names
  • query methods (select, insert, ...)

Example:

1
select
2
cast(timestamp as datetime) as timestamp,
3
status_code,
4
url,
5
event_message
6
from edge_logs
7
cross join unnest(metadata) as metadata
8
cross join unnest(response) AS request;
9
cross join unnest(response) AS response;
10
where
11
-- find all errors
12
status_code >= 400
13
and
14
-- find queries featuring the a specific <table_name> and <column_name>
15
(
16
regexp_contains(url, '<table_name>')
17
and
18
regexp_contains(event_message, '<column_name1>|<column_name2>')
19
)

PostgREST has an error reference table that you can use to interpret status codes.

Database-level errors

However, some errors that are reported through the Database API occur at the Postgres level. If it is not clear which error occurred you should reference the timestamp of the error and try to see if you can find it in the Postgres logs.

1
select
2
cast(postgres_logs.timestamp as datetime) as timestamp,
3
error_severity,
4
user_name,
5
query,
6
detail,
7
sql_state_code,
8
event_message
9
from postgres_logs
10
cross join unnest(metadata) as metadata
11
cross join unnest(metadata.parsed) as parsed
12
where
13
-- filter only for error events
14
regexp_contains(parsed.error_severity, 'ERROR|FATAL|PANIC')
15
and
16
-- All DB API requests are registered as the authenticator role
17
parsed.user_name = 'authenticator'
18
and
19
-- find failed queries featuring the function <function_name>
20
regexp_contains(parsed.query, '<function_name>')
21
and
22
-- limit the time of the search to be around the time of the failed API request
23
postgres_logs.timestamp between '2024-04-15 10:50:00' AND '2024-04-15 10:50:27'
24
order by
25
timestamp desc
26
limit 100;

Like PostgREST, Postgres has a reference table for interpreting error codes.

PostgREST server and Cloudflare errors

In some cases, errors may emerge because of Cloudflare or PostgREST server errors. For 500 and above errors, you may want to check your PostgREST logs and the Cloudflare docs.)

Practical examples:

Find All Errors:

1
select
2
cast(timestamp as datetime) as timestamp,
3
status_code,
4
event_message,
5
path
6
from
7
edge_logs
8
cross join unnest(metadata) as metadata
9
cross join unnest(response) as response
10
cross join unnest(request) as request
11
where
12
-- find all errors
13
status_code >= 400
14
and regexp_contains(path, '^/rest/v1/');
15
-- only look at DB API

Group errors by path and code:

1
select
2
status_code,
3
path,
4
count(path) as reoccurrence_per_path
5
from
6
edge_logs
7
cross join unnest(metadata) as metadata
8
cross join unnest(response) as response
9
cross join unnest(request) as request
10
where
11
-- find all errors
12
status_code >= 400
13
and regexp_contains(path, '^/rest/v1/') -- only look at DB API
14
group by path, status_code
15
order by reoccurrence_per_path;

Find requests by region:

1
select
2
path,
3
region,
4
count(region) as region_count
5
from
6
edge_logs
7
cross join unnest(metadata) as metadata
8
cross join unnest(request) as request
9
cross join unnest(cf) as cf
10
where
11
-- only look at DB API
12
regexp_contains(path, '^/rest/v1/')
13
group by region, path
14
order by requester_region_count;

Find total requests by IP:

1
select
2
cf_connecting_ip as ip,
3
count(cf_connecting_ip) as ip_count
4
from
5
edge_logs
6
cross join unnest(metadata) as metadata
7
cross join unnest(request) as request
8
cross join unnest(headers) as headers
9
cross join unnest(cf) as cf
10
cross join unnest(response) as response
11
where regexp_contains(path, '^/auth/v1/')
12
group by ip
13
order by ip_count;

Search frequented query paths by authenticated user:

1
select
2
-- only available for front-end clients
3
auth_users,
4
path,
5
count(auth_users) as ip_count
6
from
7
edge_logs
8
cross join unnest(metadata) as metadata
9
cross join unnest(request) as request
10
cross join unnest(sb) as sb
11
where
12
-- only look at DB API
13
regexp_contains(path, '^/rest/v1/')
14
group by auth_users, path;