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:
| field | description |
|---|---|
| event_message | the log's message |
| timestamp | time event was recorded |
| request metadata | metadata about the REST request |
| response metadata | metadata 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
1select2 -- the event message does not require unnesting3 event_message,4 -- unnested status_code column from metadata.response field5 status_code6from7 edge_logs8 -- Unpack data stored in the 'metadata' field9 cross join unnest(metadata) as metadata10 -- After unpacking the 'metadata' field, extract the 'response' field from it11 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
| Column | Description | Sample value |
|---|---|---|
| request.cf.city | Requester's city | Munich |
| request.cf.country | Requester's country | DE |
| request.cf.continent | Requester's continent | EU |
| request.cf.region | Requester's region | Bavaria |
| request.cf.latitudex | Requester's latitude | 48.10840 |
| request.cf.longitude | Requester's longitude | 11.61020 |
| request.cf.timezone | Requester's timezone | Europe/Berlin |
Unnesting example:
1select2 city3from4 edge_logs5-- Unpack 'metadata' field6cross join unnest(metadata) AS metadata7-- unpack 'request' from 'metadata'8cross join unnest(request) AS request;9-- unpack 'cf' from 'request'10cross 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
| Column | Description | Sample value |
|---|---|---|
| request.headers.cf_connecting_ip | Requester's IP | 80.81.18.138 |
| request.headers.user_agent | Requester's browser or app environment | Mozilla/5.0 (Linux; Android 11; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36 |
Unnesting example:
1select2 cf_connecting_ip3from4 edge_logs5-- Unpack 'metadata' field6cross join unnest(metadata) AS metadata7-- unpack 'request' from 'metadata'8cross join unnest(request) AS request;9-- unpack 'headers' from 'request'10cross join unnest(headers) AS headers;Query type and formatting data:
Suggested use cases:
- identify problematic queries
- identify unusual behavior by authenticated users
| Column | Description | Sample value |
|---|---|---|
| request.method | Request Method (PATCH, GET, PUT...) | GET |
| request.url | Request URL, which contains the PostgREST formatted query | https://yuhplfrsdxxxtldakizi.supabase.co/rest/v1/users?select=username&id=eq.63b6190e-214f-4b8a-b72d-3af6e1921411&limit=1 |
| request.sb.auth_users | authenticated user's ID | 63b6190e-214f-4b8a-b72d-3af6e1921411 |
Unnesting example:
1select2 method,3 url,4 auth_users5from6 edge_logs7-- Unpack 'metadata' field8cross join unnest(metadata) AS metadata9-- unpack 'request' from 'metadata'10cross join unnest(request) AS request;11-- unpack 'sb' from 'request'12cross join unnest(sb) AS sb;Response object
Status code:
Suggested use cases:
- detect success/errors
| Column | Description | Sample value |
|---|---|---|
| response.status_code | Response status code (200, 404, 500...) | 404 |
Unnesting example:
1select2 status_code3from4 edge_logs5 -- Unpack 'metadata' field6 cross join unnest(metadata) as metadata7 -- 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:
1let { data: countries, error } = await supabase.from('countries').select('name')translates to calling the following endpoint:
1https://<project ref>.supabase.co/rest/v1/countries?select=nameYou 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:
1select2 cast(timestamp as datetime) as timestamp,3 status_code,4 url,5 event_message6from edge_logs7cross join unnest(metadata) as metadata8cross join unnest(response) AS request;9cross join unnest(response) AS response;10where11 -- find all errors12 status_code >= 40013 and14 -- find queries featuring the a specific <table_name> and <column_name>15 (16 regexp_contains(url, '<table_name>')17 and18 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.
1select2 cast(postgres_logs.timestamp as datetime) as timestamp,3 error_severity,4 user_name,5 query,6 detail,7 sql_state_code,8 event_message9from postgres_logs10 cross join unnest(metadata) as metadata11 cross join unnest(metadata.parsed) as parsed12where13 -- filter only for error events14 regexp_contains(parsed.error_severity, 'ERROR|FATAL|PANIC')15 and16 -- All DB API requests are registered as the authenticator role17 parsed.user_name = 'authenticator'18 and19 -- find failed queries featuring the function <function_name>20 regexp_contains(parsed.query, '<function_name>')21 and22 -- limit the time of the search to be around the time of the failed API request23postgres_logs.timestamp between '2024-04-15 10:50:00' AND '2024-04-15 10:50:27'24order by25 timestamp desc26limit 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:
1select2 cast(timestamp as datetime) as timestamp,3 status_code,4 event_message,5 path6from7 edge_logs8 cross join unnest(metadata) as metadata9 cross join unnest(response) as response10 cross join unnest(request) as request11where12 -- find all errors13 status_code >= 40014 and regexp_contains(path, '^/rest/v1/');15-- only look at DB APIGroup errors by path and code:
1select2 status_code,3 path,4 count(path) as reoccurrence_per_path5from6 edge_logs7 cross join unnest(metadata) as metadata8 cross join unnest(response) as response9 cross join unnest(request) as request10where11 -- find all errors12 status_code >= 40013 and regexp_contains(path, '^/rest/v1/') -- only look at DB API14group by path, status_code15order by reoccurrence_per_path;Find requests by region:
1select2 path,3 region,4 count(region) as region_count5from6 edge_logs7 cross join unnest(metadata) as metadata8 cross join unnest(request) as request9 cross join unnest(cf) as cf10where11 -- only look at DB API12 regexp_contains(path, '^/rest/v1/')13group by region, path14order by requester_region_count;Find total requests by IP:
1select2 cf_connecting_ip as ip,3 count(cf_connecting_ip) as ip_count4from5 edge_logs6 cross join unnest(metadata) as metadata7 cross join unnest(request) as request8 cross join unnest(headers) as headers9 cross join unnest(cf) as cf10 cross join unnest(response) as response11where regexp_contains(path, '^/auth/v1/')12group by ip13order by ip_count;Search frequented query paths by authenticated user:
1select2 -- only available for front-end clients3 auth_users,4 path,5 count(auth_users) as ip_count6from7 edge_logs8 cross join unnest(metadata) as metadata9 cross join unnest(request) as request10 cross join unnest(sb) as sb11where12 -- only look at DB API13 regexp_contains(path, '^/rest/v1/')14group by auth_users, path;