API Endpoints Reference
June 10, 2026 · View on GitHub
Complete specification of all HTTP endpoints generated by DataSurface.
Base URL
All endpoints are prefixed with the configured ApiPrefix (default: /api).
Resource Endpoints
List Resources
GET /api/{route}
Returns a paginated list of resources with filtering, sorting, and search.
Query Parameters:
| Parameter | Example | Description |
|---|---|---|
page | ?page=2 | Page number (1-based, default: 1) |
pageSize | ?pageSize=50 | Items per page (default: 20, max: contract-defined) |
sort | ?sort=title,-createdAt | Comma-separated fields, - prefix for descending |
filter[field] | ?filter[price]=gt:100 | Field filter with operator |
q | ?q=search+term | Full-text search across searchable fields |
expand | ?expand=author,tags | Include related resources |
fields | ?fields=id,title,email | Return only specified fields |
Results are always deterministically ordered: the requested sort wins, falling back to the
resource's DefaultSort (see [CrudResource]), with the key appended as a final tie-breaker.
Response: 200 OK
{
"items": [...],
"page": 1,
"pageSize": 20,
"total": 142
}
Response Headers:
| Header | Example | Description |
|---|---|---|
X-Total-Count | 142 | Total matching records |
X-Page | 1 | Current page number |
X-Page-Size | 20 | Items per page |
ETag | W/"..." | Entity tag (if enabled) |
Cache-Control | private, max-age=60 | Emitted when CacheControlMaxAgeSeconds > 0 |
Count Resources (HEAD)
HEAD /api/{route}
Returns only count information — no response body.
Query Parameters: Same as List (filters, search apply).
Response: 200 OK (empty body)
Response Headers: Same as List (X-Total-Count, X-Page, X-Page-Size). X-Page-Size
reports the effective page size an equivalent GET would use (the requested size clamped to the
resource's MaxPageSize).
Get Single Resource
GET /api/{route}/{id}
Returns a single resource by its primary key.
Query Parameters:
| Parameter | Example | Description |
|---|---|---|
expand | ?expand=author | Include related resources |
fields | ?fields=id,title | Return only specified fields |
Response: 200 OK
{
"id": 1,
"title": "My Post",
"email": "alice@example.com"
}
Response Headers:
| Header | Example | Description |
|---|---|---|
ETag | W/"AAAAAAB=" | Entity tag for concurrency |
Errors: 404 Not Found if resource does not exist.
Create Resource
POST /api/{route}
Content-Type: application/json
Creates a new resource. Body must contain fields with CrudDto.Create flag.
Request Body:
{
"email": "alice@example.com",
"name": "Alice"
}
Response: 201 Created
{
"id": 1,
"email": "alice@example.com",
"name": "Alice",
"createdAt": "2024-12-28T14:30:00Z"
}
The Location header contains a relative, URL-encoded URI to the created resource
(e.g., /api/users/1).
Errors:
400 Bad Request— Validation failure (missing required fields, invalid values, unknown fields)401 Unauthorized— Authentication required403 Forbidden— Authorization denied
Partial Update
PATCH /api/{route}/{id}
Content-Type: application/json
If-Match: W/"AAAAAAB="
Updates only the provided fields. Omitted fields remain unchanged.
Request Body:
{
"name": "Alice Johnson"
}
Response: 200 OK with updated resource.
Errors:
400 Bad Request— Validation failure (immutable fields, invalid values)404 Not Found— Resource does not exist409 Conflict— Concurrency conflict (ETag mismatch)
Full Replacement
PUT /api/{route}/{id}
Content-Type: application/json
If-Match: W/"AAAAAAB="
Replaces all updatable fields. All updatable fields must be provided.
Requires: EnablePutForFullUpdate = true in DataSurfaceHttpOptions.
Response: 200 OK with updated resource.
Errors: Same as PATCH, plus 400 if required fields are missing.
Delete Resource
DELETE /api/{route}/{id}
Deletes a resource. For ISoftDelete entities, sets IsDeleted = true.
Response: 204 No Content
Errors:
404 Not Found— Resource does not exist401 Unauthorized/403 Forbidden— Access denied
Bulk Endpoint
POST /api/{route}/bulk
Content-Type: application/json
Requires: EnableBulkOperations = true in DataSurfaceHttpOptions (the
IDataSurfaceBulkService is registered by AddDataSurfaceEfCore).
Batch create, update, and delete operations. Each non-empty section of the request (create /
update / delete) is authorized against that operation's policy — a caller with only the Create
policy cannot mass-update or mass-delete through /bulk. See
Bulk Operations.
Streaming Endpoint
GET /api/{route}/stream
Requires: EnableStreaming = true in DataSurfaceHttpOptions (the
IDataSurfaceStreamingService is registered by AddDataSurfaceEfCore).
Returns NDJSON (newline-delimited JSON). See Streaming.
Content-Type: application/x-ndjson
Import/Export Endpoints
Requires: EnableImportExport = true in DataSurfaceHttpOptions.
Export
GET /api/{route}/export?format=json
GET /api/{route}/export?format=csv
Export materializes the whole result set in memory, capped at MaxExportRows
(default 100000); when the cap is reached the export is truncated and the
X-Export-Truncated: true response header is added.
Import
POST /api/{route}/import
Content-Type: application/json
See Import/Export.
Schema Endpoint
GET /api/$schema/{resourceKey}
Requires: MapSchemaEndpoint = true (the default; set to false to disable).
Returns JSON Schema for a resource. The segment matches the resource key or the route (case-insensitive). See OpenAPI Integration.
Resource Discovery
GET /api/$resources
Requires: MapResourceDiscoveryEndpoint = true (opt-in; default false).
Lists all registered resources (static and dynamic) with metadata.
Both the schema and discovery endpoints participate in the same default authorization, API key authentication, and rate limiting as the CRUD endpoints.
Dynamic Resource Endpoints
When MapDynamicCatchAll = true, dynamic resources are served under a separate prefix:
GET /api/d/{route}
HEAD /api/d/{route}
GET /api/d/{route}/{id}
POST /api/d/{route}
PATCH /api/d/{route}/{id}
DELETE /api/d/{route}/{id}
The dynamic prefix is configurable via DynamicPrefix (default: "/d").
Admin Endpoints
Requires: DataSurface.Admin package and MapDataSurfaceAdmin().
| Method | Path | Description |
|---|---|---|
GET | {prefix}/entities | List all entity definitions |
GET | {prefix}/entities/{key} | Get single entity definition |
PUT | {prefix}/entities/{key} | Create or update entity definition |
DELETE | {prefix}/entities/{key} | Delete entity definition |
GET | {prefix}/export | Export all definitions as JSON |
POST | {prefix}/import | Import definitions from JSON |
POST | {prefix}/entities/{key}/reindex | Rebuild search indexes |
Default prefix: /admin/ds.
Filter Operators
| Operator | Example | Description |
|---|---|---|
eq | filter[status]=eq:active | Equals (default if omitted) |
neq | filter[status]=neq:deleted | Not equals |
gt | filter[price]=gt:100 | Greater than |
gte | filter[price]=gte:100 | Greater than or equal |
lt | filter[price]=lt:50 | Less than |
lte | filter[price]=lte:50 | Less than or equal |
contains | filter[name]=contains:john | String contains |
starts | filter[name]=starts:john | String starts with |
ends | filter[name]=ends:son | String ends with |
in | filter[status]=in:a|b|c | In list (pipe-separated) |
isnull | filter[email]=isnull:true | Is null / is not null |
Only these operator prefixes are recognized — a value containing : after an unknown prefix
(e.g., an ISO timestamp filter[createdAt]=2024-12-28T14:30:00Z) is treated as a plain
equality value.