API Endpoints
Reference for the REST API of the platform DevOur generates for your project.
Authentication
When your project has Firebase configured, requests need the signed-in user's Firebase ID token:
Authorization: Bearer <firebase-id-token>
These endpoints don't need a token:
GET /api/users/registeredPOST /api/mail/send-password-resetPOST /api/mail/send-email-verificationPOST /api/mail/send-sign-in-linkPOST /webhook/stripeGET /api/theme/bundleandGET /api/theme/css
A missing token returns 401 with EMPTY_BEARER_TOKEN. An invalid or revoked
token returns 401 with UNABLE_VERIFY_TOKEN.
Errors
Most errors use this shape:
{
"status": false,
"error_key": "RESOURCE_NOT_FOUND",
"message": "...",
"code": 404,
"details": {}
}
Endpoints that answer differently say so below.
Each client can send up to 100 requests per minute, and 5 per minute to
GET /api/users/registered. Past that, the API returns 429 with
details.error set to RATE_LIMIT_EXCEEDED.
Which Endpoints Your Platform Has
- Users, Mail, Uploads, and Theme endpoints are always included
- Products and the Stripe webhook are included when your project has Stripe keys and at least one product
- Collections endpoints are included when your project has collections
- Forms endpoints are included when your project has forms
Users
A user has these fields: id, createdAt, updatedAt, uid, email,
isVerified, customerID, requirePlan, and role.
GET /api/users
Get the signed-in user, with their subscription status.
Returns 200 with {"user": {...}}. The user also includes hasSubscription,
subscriptionExpired, planID, and usageLimits: for each usage limit, its
name, the max of the customer's plan, and how many records they have used.
Without a plan, usageLimits is empty and hasSubscription is false. When
the signed-in person has no user record yet, returns 200 with
{"error": "NO_USER_FOUND"}.
POST /api/users/create
Create the user record for the signed-in Firebase user, or return the existing one. No body.
Returns 200 with {"user": {...}}.
POST /api/users/validate
Check that the token is valid. No body. Returns 200 with {}.
GET /api/users/registered
Check whether an email address has an account. No token needed.
| Query parameter | Description |
|---|---|
email | Email address to check |
Returns 200 with {"message": "REGISTERED"}, or 200 with
{"error": "NO_USER"} when there's no account.
DELETE /api/users
Delete the signed-in user's account and their Firebase identity. Returns 200
with {}.
GET /api/users/all
List all users. Admins only.
Returns 200 with {"users": [...]}, or 401 with PERMISSION_DENIED for
other users.
POST /api/users/{id}
Save a user. Admins only. Send the user object in the body.
Returns 200 with {"user": {...}}, or 403 with
{"error": "PERMISSION_DENIED"} for other users.
Products
A product has these fields: ID, productID, maxUsage, limits, name,
description, descriptor, features, priceID, currency, interval,
intervalCount, amount, and active.
GET /api/products/all
List your project's products with their Stripe prices.
Returns 200 with {"products": [...], "stripeProducts": [...]}.
GET /api/products/{id}
Get one product. id is the Stripe product ID.
Returns 200 with {"product": {...}}, or 404 with RESOURCE_NOT_FOUND.
POST /api/products/payment-link
Create a Stripe payment link for a price. After checkout, Stripe sends the user
back to returnURL.
| Body field | Type | Description |
|---|---|---|
priceID | string | Stripe price ID |
returnURL | string | Where to send the user afterwards |
Returns 200 with {"paymentLink": "https://..."}.
Collections
An entity has these fields: id, createdAt, updatedAt, name,
collectionId, ownerId, and properties. ownerId is the user who created
it.
In a collection whose records are private to each customer, a signed-in user
without the admin role only lists, reads, updates, and deletes the entities they
created. Anyone else's entity returns 404 with RESOURCE_NOT_FOUND. Admins
reach every entity. In a shared collection, every signed-in user reaches every
entity. Deleting a user's account also deletes the entities they own in private
collections.
In a collection linked to a usage limit, a signed-in user without the admin role
needs a plan to create, update, or delete entities. Without one, those return
403 with PLAN_REQUIRED. Creating also returns 403 with
USAGE_LIMIT_REACHED once the entities they own across every collection linked
to that limit reach their plan's number. Reading is never limited. See
Usage Limits and What They Cap.
GET /api/collections/{collectionId}/entities
List a collection's entities, a page at a time.
| Query parameter | Default | Description |
|---|---|---|
page | 1 | Page number |
perPage | 12 | Entities per page |
Returns 200 with
{"entities": [...], "page": 1, "perPage": 12, "totalItems": 0, "totalPages": 0}.
POST /api/collections/{collectionId}/entities/new
Create an entity.
| Body field | Type | Description |
|---|---|---|
name | string | Entity name, 1 to 256 characters |
properties | object | Property values |
The entity goes into the collection in the path and is owned by the signed-in
user. Returns 200 with {"entity": {...}}.
GET /api/collections/{collectionId}/entities/{entityId}
Get one entity. Returns 200 with {"entity": {...}}, or 404 with
RESOURCE_NOT_FOUND.
POST /api/collections/{collectionId}/entities/{entityId}
Update an entity's name and properties. Returns 200 with
{"entity": {...}}, or 404 with RESOURCE_NOT_FOUND.
DELETE /api/collections/{collectionId}/entities/{entityId}
Delete an entity. Returns 200 with {}, or 404 with RESOURCE_NOT_FOUND.
Forms
POST /api/forms/{form-slug}
Submit a form. Each form has its own endpoint, named after the form: its words
in lowercase, joined with -. A form called "Contact Us" is at
POST /api/forms/contact-us.
The body has one key per field, named after the field's label in camelCase, so
"Email Address" becomes emailAddress. Values depend on the field type:
| Field type | Value |
|---|---|
| Number | number |
| Toggle, checkbox | boolean |
| Address, address with country | {"street", "city", "postalCode", "province", "country"} |
| Phone number | {"number", "extension"} |
| Any other type | string |
Returns 201 with {"submission": {...}}. Returns 400 when a required text
field is empty or doesn't match its pattern.
These endpoints send emails through Resend in the requested language. They don't
need a token. Each needs locale, or it returns 400.
POST /api/mail/send-password-reset
| Body field | Description |
|---|---|
email | Address to send the reset email to |
resetLink | Page of your platform that handles the reset |
locale | Language code of the email, such as en |
Returns 200 with {"message": "Password reset email sent!"}.
POST /api/mail/send-email-verification
| Body field | Description |
|---|---|
email | Address to verify |
verifyLink | Page of your platform that handles verification |
locale | Language code of the email |
Returns 200 with {"message": "Verification email sent!"}.
POST /api/mail/send-sign-in-link
Send a passwordless sign-in link.
| Body field | Description |
|---|---|
email | Address of an existing account |
signInLink | Page of your platform that completes sign-in |
locale | Language code of the email |
Returns 200 with {"message": "Sign-in link sent!"}, or 400 with
EMAIL_NOT_FOUND when the address has no account.
Uploads
POST /api/upload-image
Upload an image as multipart/form-data in the image field. Returns 200
with {"url": "/uploads/..."}.
POST /api/upload-file
Upload a file as multipart/form-data in the file field. Returns 200 with
{"url": "/uploads/..."}.
Theme
GET /api/theme/bundle and GET /api/theme/css
Serve the files of your project's theme when it uses a theme from the
marketplace. Your platform loads them itself. They return 404 when the project
uses the default theme.
Webhooks
POST /webhook/stripe
Receives Stripe events. See Webhooks.
Health
GET /health
In the production build, returns 200 with ok.