Skip to content

Supabase REST API — Direct HTTP Access to Your Database

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Supabase REST API. We cover key concepts, practical examples, and best practices to help you master this topic.

The Supabase REST API is an auto-generated HTTP API powered by PostgREST that lets you query, filter, and mutate your PostgreSQL database using standard HTTP requests without any SDK.

What You'll Learn

By the end of this lesson you will construct REST API requests to query and mutate data, use query parameters for filtering, sorting, and pagination, handle authentication headers, and understand the response format.

Why It Matters

The REST API enables any language or tool to interact with Supabase. It is essential for server-to-server communication, curl-based testing, scripting, and working with languages without an official SDK.

Real-World Use

DodaZIP uses the REST API for automated testing and CI/CD pipelines. Python scripts and shell scripts interact with the database via curl and HTTP requests without loading the full SDK.

flowchart LR
    A[curl] -->|HTTP GET| API[PostgREST]
    B[Python requests] -->|HTTP POST| API
    C[CI/CD Pipeline] -->|HTTP PATCH| API
    API -->|SQL| DB[(PostgreSQL)]
    style API fill:#3ecf8e,color:#fff

Making Requests

Every Supabase REST API request includes the project URL, the table name, and authentication headers.

# Base URL format
# https://<project-ref>.supabase.co/rest/v1/<table>

# Query all rows
curl "https://example.supabase.co/rest/v1/files" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

# Query with filter
curl "https://example.supabase.co/rest/v1/files?user_id=eq.user_abc" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
# rest_queries.py
# Supabase REST API with Python requests

import requests
import os

def rest_api_examples():
    url = os.getenv("SUPABASE_URL", "https://example.supabase.co")
    anon_key = os.getenv("SUPABASE_ANON_KEY", "your-anon-key")
    
    headers = {
        "apikey": anon_key,
        "Authorization": f"Bearer {anon_key}",
        "Content-Type": "application/json",
    }
    
    # GET all files
    response = requests.get(
        f"{url}/rest/v1/files",
        headers=headers
    )
    print(f"GET /files: {response.status_code}")
    print(f"Results: {len(response.json())} rows")
    
    # GET with filter
    response = requests.get(
        f"{url}/rest/v1/files",
        headers=headers,
        params={"user_id": "eq.user_abc", "select": "id,name,size_bytes"}
    )
    print(f"Filtered: {response.json()}")

rest_api_examples()

Query Parameters

Filter, sort, and paginate using query parameters.

# Filter operators
# eq (equals), neq (not equal), gt (greater than)
# lt (less than), gte (greater or equal), lte (less or equal)
# like, ilike (case-insensitive like), in (in list)
# is (is null), not.is (is not null)

# Examples
curl ".../files?status=eq.completed"
curl ".../files?size_bytes=gt.1000000"
curl ".../files?name=like.%report%"

# Sorting
curl ".../files?order=created_at.desc"

# Pagination
curl ".../files?offset=0&limit=20"

# Range headers
curl ".../files" -H "Range: 0-19"

# Counting
curl ".../files?select=count"
# query_params.py
# Advanced query parameters

def query_examples():
    base = "https://example.supabase.co/rest/v1/files"
    headers = {
        "apikey": "your-anon-key",
        "Authorization": "Bearer your-jwt",
    }
    
    queries = [
        ("All files", {}),
        ("Completed files", {"status": "eq.completed"}),
        ("Large files", {"size_bytes": "gt.10000000"}),
        ("Recent files", {"order": "created_at.desc"}),
        ("First page", {"offset": "0", "limit": "20"}),
        ("Specific columns", {"select": "id,name,created_at"}),
        ("Name search", {"name": "like.%report%"}),
    ]
    
    print("REST API Query Examples:")
    for desc, params in queries:
        param_str = "&".join(f"{k}={v}" for k, v in params.items())
        full_url = f"{base}?{param_str}" if param_str else base
        print(f"  {desc:20s} {full_url}")

query_examples()

Mutating Data

Insert, update, and delete via HTTP methods.

# rest_mutations.py
# Create, update, and delete via REST API

def rest_mutations():
    url = "https://example.supabase.co/rest/v1/files"
    headers = {
        "apikey": "your-anon-key",
        "Authorization": "Bearer your-jwt",
        "Content-Type": "application/json",
        "Prefer": "return=representation",
    }
    
    # INSERT a new row
    new_file = {
        "user_id": "user_abc",
        "name": "new-report.pdf",
        "size_bytes": 512000,
        "mime_type": "application/pdf",
    }
    response = requests.post(url, headers=headers, json=new_file)
    print(f"INSERT: {response.status_code}")
    print(f"Created: {response.json()}")
    
    # UPDATE matching rows
    response = requests.patch(
        url,
        headers=headers,
        params={"id": "eq.file_123"},
        json={"status": "archived"}
    )
    print(f"UPDATE: {response.status_code}")
    print(f"Updated: {response.json()}")
    
    # DELETE matching rows
    response = requests.delete(
        url,
        headers=headers,
        params={"id": "eq.file_456"}
    )
    print(f"DELETE: {response.status_code}")

rest_mutations()

Authentication Headers

Understanding the different authentication modes.

# Using anon key (respects RLS)
curl -H "apikey: YOUR_ANON_KEY" \
     -H "Authorization: Bearer USER_JWT"

# Using service_role key (bypasses RLS)
curl -H "apikey: YOUR_SERVICE_ROLE_KEY" \
     -H "Authorization: Bearer YOUR_SERVICE_ROLE_KEY"

# Public access (no auth)
# Only works for tables where RLS allows public access
curl -H "apikey: YOUR_ANON_KEY"
# auth_headers.py
# Authentication modes

def auth_modes():
    modes = {
        "Anon Key + User JWT": "Client-side requests. Respects RLS policies. Users see only their data.",
        "Service Role Key": "Server-side requests. Bypasses RLS. Full database access.",
        "Anon Key Only": "Public requests. Only works if RLS allows SELECT for anonymous users.",
    }
    
    print("Authentication Modes:")
    for mode, desc in modes.items():
        print(f"  {mode:30s} | {desc}")

auth_modes()

Common Mistakes

  1. Not including the apikey header: The API key is required for every request, even anonymous ones.

  2. Using service_role key from the browser: The service_role key bypasses RLS. Exposing it in client-side code is a critical security vulnerability.

  3. Incorrect filter syntax: Filters use the format column=operator.value. For example, status=eq.completed not status=completed.

  4. Forgetting the Prefer header for INSERT return: Without Prefer: return=representation, the response does not include the created row.

  5. Mixing HTTP methods: Use GET for reads, POST for inserts, PATCH for updates, DELETE for deletes. Using GET for mutations will fail.

Practice Questions

  1. What is the base URL format for the Supabase REST API? https://<project-ref>.supabase.co/rest/v1/<table_name>.

  2. How do you filter rows where status equals completed? Add query parameter ?status=eq.completed.

  3. What HTTP method do you use to insert a row? POST.

  4. What is the purpose of the Prefer header? It controls response behavior, such as return=representation to return the inserted/updated row.

  5. Challenge: Write a bash script using curl that connects to a Supabase project, queries a table with filters and pagination, and formats the output as JSON.

FAQ

Can I use the REST API without an SDK?

Yes. The REST API uses standard HTTP. Any HTTP client (curl, fetch, requests) can interact with it.

What HTTP methods does the REST API support?

GET (read), POST (insert), PATCH (update), DELETE (delete).

How do I handle complex queries with OR conditions?

Use the or query parameter: ?or=(status.eq.active,status.eq.pending).

Does the REST API support joins?

Yes. Use the select parameter with embedded relationships: ?select=id,name,user:user_id(email).

What is the rate limit for REST API requests?

Free tier has rate limits. Check the Supabase Dashboard for your project's specific limits.

Mini Project

Create a Python script that uses the Supabase REST API to perform a complete CRUD workflow: query files, insert a new file, update its status, and delete it, with proper error handling at each step.

import requests
import json

def rest_crud_workflow():
    base = "https://example.supabase.co/rest/v1/files"
    headers = {"apikey": "your-key", "Authorization": "Bearer your-token", "Content-Type": "application/json"}
    
    # Create
    resp = requests.post(base, headers=headers, json={"name": "test.txt", "user_id": "user_abc", "size_bytes": 1000})
    new_id = resp.json()[0]["id"]
    print(f"Created: {new_id}")
    
    # Read
    resp = requests.get(f"{base}?id=eq.{new_id}", headers=headers)
    print(f"Read: {resp.json()}")
    
    # Update
    requests.patch(f"{base}?id=eq.{new_id}", headers=headers, json={"name": "renamed.txt"})
    print(f"Updated: {new_id}")
    
    # Delete
    requests.delete(f"{base}?id=eq.{new_id}", headers=headers)
    print(f"Deleted: {new_id}")

rest_crud_workflow()

What's Next

Next: Supabase GraphQL for Graphql queries.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro