Skip to main content
GET
/
{collection}
/
{id}
Get Document
curl --request GET \
  --url https://api.example.com/{collection}/{id} \
  --header 'Authorization: <authorization>'
{
  "_id": "<string>",
  "created_at": "<string>",
  "updated_at": "<string>",
  "created_by": "<string>",
  "...": "<any>"
}

Overview

Fetch a specific document from a collection by its _id. The endpoint:
  • Validates document permissions before returning data
  • Applies field-level access controls
  • Masks sensitive fields based on user role
  • Calculates computed fields
  • Logs read access to audit trail (if enabled)

Request

Path Parameters

collection
string
required
The name of the collection containing the document
id
string
required
The unique identifier (_id) of the document. Can be MongoDB ObjectId or any string ID.

Headers

Authorization
string
required
Bearer token for authentication

Response

_id
string
The document’s unique identifier
created_at
string
ISO 8601 timestamp when the document was created
updated_at
string
ISO 8601 timestamp when the document was last updated
created_by
string
User ID of the user who created the document
...
any
All other fields from the document, filtered by field-level permissions and masking rules

Examples

Get by ObjectId

curl -X GET https://api.example.com/users/507f1f77bcf86cd799439011 \
  -H "Authorization: Bearer YOUR_TOKEN"

Get by String ID

curl -X GET https://api.example.com/products/SKU-12345 \
  -H "Authorization: Bearer YOUR_TOKEN"

Response

{
  "_id": "507f1f77bcf86cd799439011",
  "name": "John Doe",
  "email": "j***@example.com",
  "role": "admin",
  "status": "active",
  "department": "Engineering",
  "created_at": "2024-03-15T10:30:00Z",
  "updated_at": "2024-03-15T14:25:00Z",
  "created_by": "user_123",
  "company_id": "company_456"
}
Note: The email field is masked based on the user’s role permissions.

Implementation Details

MongoDB Query Pattern

The handler uses MongoDB’s findOne operation:
// Convert string ID to ObjectID if applicable
var filter bson.M
objectID, err := primitive.ObjectIDFromHex(id)
if err == nil {
    filter = bson.M{"_id": objectID}
} else {
    filter = bson.M{"_id": id} // String ID
}

var doc bson.M
err = collection.FindOne(ctx, filter).Decode(&doc)
if err == mongo.ErrNoDocuments {
    return ErrNotFound
}

Workflow

  1. Extract collection and ID from URL path (handlers_crud.go:195-196)
  2. Validate parameters - collection and ID required (handlers_crud.go:198-205)
  3. Authenticate request and get auth context (handlers_crud.go:208)
  4. Validate collection exists in schema (handlers_crud.go:214)
  5. Fetch document from MongoDB (handlers_crud.go:227)
  6. Handle not found - return 404 if document doesn’t exist (handlers_crud.go:229)
  7. Handle invalid ID - return 400 for malformed IDs (handlers_crud.go:236)
  8. Check RBAC permissions - verify user can read this specific document (handlers_crud.go:249)
  9. Log audit event if log_reads enabled (handlers_crud.go:258)
  10. Calculate computed fields - virtual fields not stored in DB (handlers_crud.go:261)
  11. Apply field policy - remove denied fields (handlers_crud.go:264)
  12. Apply field masking - mask sensitive data (handlers_crud.go:267)
  13. Return document with 200 OK (handlers_crud.go:270)

Field-Level Access Control

Fields are filtered based on role policies:
func applyFieldPolicy(authCtx *auth.AuthContext, collection string, doc map[string]interface{}) {
    // Get role policies
    var denyFields []string
    var allowFields []string
    
    for _, role := range authCtx.Roles {
        rolePolicy := h.policy.GetRolePolicy(collection, role)
        if rolePolicy != nil && rolePolicy.Fields != nil {
            denyFields = append(denyFields, rolePolicy.Fields.Deny...)
            allowFields = append(allowFields, rolePolicy.Fields.Allow...)
        }
    }
    
    // If allow list is specified, only keep those fields
    if len(allowFields) > 0 {
        for field := range doc {
            if !contains(allowFields, field) && field != "_id" {
                delete(doc, field)
            }
        }
    }
    
    // Remove denied fields
    for _, field := range denyFields {
        delete(doc, field)
    }
}

Field Masking

Sensitive fields are masked based on role permissions:
func applyFieldMasking(authCtx *auth.AuthContext, collection string, doc map[string]interface{}) {
    // Get mask configuration from role policies
    maskConfig := make(map[string]MaskType)
    for _, role := range authCtx.Roles {
        rolePolicy := h.policy.GetRolePolicy(collection, role)
        if rolePolicy != nil && rolePolicy.Fields != nil {
            for field, maskType := range rolePolicy.Fields.Mask {
                maskConfig[field] = maskType
            }
        }
    }
    
    // Apply masks
    for field, maskType := range maskConfig {
        if val, ok := doc[field].(string); ok {
            doc[field] = maskValue(val, maskType)
        }
    }
}
Mask Types:
  • email: Shows first char and domain (e.g., j***@example.com)
  • phone: Shows last 4 digits (e.g., ***-***-1234)
  • partial: Shows first and last char (e.g., j***n)
  • full: Replaces with ***

Computed Fields

Virtual computed fields are calculated on-the-fly:
# Example schema with computed field
fields:
  first_name:
    type: string
  last_name:
    type: string
  full_name:
    type: string
    computed:
      expression: "{{first_name}} {{last_name}}"
      store: false  # Not stored in DB, computed on read

RBAC Permission Check

The system checks if the user can read this specific document:
if !h.canPerformAction(authCtx, collection, config.ActionRead, doc) {
    return ErrForbidden
}
Example permission conditions:
  • Owner-only: resource.created_by == user.id
  • Tenant isolation: resource.company_id == user.tenant_id
  • Department access: resource.department in user.departments
  • Hierarchical: resource.owner_id in user.subordinates

Error Responses

400 Bad Request

Returned when ID format is invalid:
{
  "error": "Invalid document ID",
  "code": "bad_request",
  "details": {
    "id": "invalid-id-format"
  }
}

401 Unauthorized

{
  "error": "Authentication required"
}

403 Forbidden

Returned when user doesn’t have permission to read this document:
{
  "error": "You don't have permission to perform this action",
  "code": "forbidden",
  "details": {
    "action": "read",
    "collection": "users"
  }
}

404 Not Found

Returned when document or collection doesn’t exist:
{
  "error": "Document not found",
  "code": "document_not_found",
  "details": {
    "collection": "users",
    "id": "507f1f77bcf86cd799439011"
  }
}

500 Internal Server Error

{
  "error": "Failed to retrieve document",
  "code": "internal_error",
  "details": {
    "error": "database connection failed"
  }
}

Use Cases

Profile Page

# Get current user's profile
curl -X GET https://api.example.com/users/me \
  -H "Authorization: Bearer YOUR_TOKEN"

View Details

# View order details
curl -X GET https://api.example.com/orders/ORD-12345 \
  -H "Authorization: Bearer YOUR_TOKEN"

Check Status

# Check task status
curl -X GET https://api.example.com/tasks/task_789 \
  -H "Authorization: Bearer YOUR_TOKEN"

Build docs developers (and LLMs) love