Integration GuidesREST API Patterns
Integration Guides

REST API Patterns

Common patterns for authentication, pagination, error handling, and file uploads across Neostra APIs.

REST API Patterns

All Neostra services follow consistent REST API patterns for authentication, request/response formatting, pagination, error handling, and file uploads. This guide covers the conventions shared across all services.

Base URL

All API endpoints use the /api/v1 prefix:

https://api.neostra.io/v1

Authentication

All authenticated endpoints require a Bearer JWT token in the Authorization header.

curl -X GET https://api.neostra.io/v1/assessments \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

See the Authentication guide for details on obtaining and managing JWT tokens.

Response Format

Every API response follows the ServiceResponse envelope:

{
  "success": true,
  "message": "Operation completed successfully",
  "data": {
    // Response payload
  }
}
FieldTypeDescription
successBooleanWhether the request was successful
messageStringHuman-readable status message
dataObjectThe response payload. Structure varies by endpoint.

Success Response Example

{
  "success": true,
  "message": "Assessment created successfully",
  "data": {
    "id": "assessment-abc-123",
    "name": "Website Privacy Assessment",
    "status": "draft",
    "createdAt": "2026-03-15T10:30:00Z"
  }
}

Error Response Example

{
  "success": false,
  "message": "Validation failed",
  "data": {
    "errors": [
      {
        "field": "name",
        "message": "Name is required"
      },
      {
        "field": "brandId",
        "message": "Invalid brand ID"
      }
    ]
  }
}

Pagination

List endpoints support pagination through query parameters. Paginated responses wrap results in a PaginatedResult structure.

Query Parameters

ParameterTypeDefaultDescription
skipInteger0Number of records to skip
limitInteger20Maximum number of records to return
sortFieldStringcreatedAtField to sort by
sortOrderStringdescSort direction: asc or desc
searchTermString--Full-text search filter

Paginated Response

{
  "success": true,
  "message": "Records retrieved",
  "data": {
    "totalCount": 156,
    "skip": 0,
    "limit": 20,
    "results": [
      { "id": "item-001", "name": "First Item" },
      { "id": "item-002", "name": "Second Item" }
    ]
  }
}

Pagination Examples

# First page, 20 items, sorted by name ascending
curl -X GET "https://api.neostra.io/v1/assessments?skip=0&limit=20&sortField=name&sortOrder=asc" \
  -H "Authorization: Bearer <token>"

# Second page
curl -X GET "https://api.neostra.io/v1/assessments?skip=20&limit=20&sortField=name&sortOrder=asc" \
  -H "Authorization: Bearer <token>"

# Search with pagination
curl -X GET "https://api.neostra.io/v1/assessments?searchTerm=privacy&skip=0&limit=10" \
  -H "Authorization: Bearer <token>"

Error Handling

HTTP Status Codes

StatusMeaningWhen It Occurs
200OKSuccessful read or update
201CreatedSuccessful resource creation
400Bad RequestValidation errors, malformed input
401UnauthorizedMissing or expired JWT token
403ForbiddenValid token but insufficient permissions
404Not FoundResource does not exist
500Internal Server ErrorUnexpected server-side failure

Field-Level Validation Errors

When a 400 response is returned for validation failures, the data.errors array contains field-level details:

{
  "success": false,
  "message": "Validation failed",
  "data": {
    "errors": [
      {
        "field": "email",
        "message": "Must be a valid email address"
      },
      {
        "field": "password",
        "message": "Must be at least 8 characters"
      }
    ]
  }
}

Error Handling Example

async function apiRequest(method, endpoint, body = null) {
  const options = {
    method,
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
    },
  };
  if (body) options.body = JSON.stringify(body);

  const response = await fetch(`https://api.neostra.io/v1/${endpoint}`, options);
  const result = await response.json();

  if (!response.ok) {
    switch (response.status) {
      case 400:
        // Handle validation errors
        const fieldErrors = result.data?.errors || [];
        console.error("Validation errors:", fieldErrors);
        break;
      case 401:
        // Token expired, redirect to login
        redirectToLogin();
        break;
      case 403:
        // Insufficient permissions
        console.error("Access denied:", result.message);
        break;
      default:
        console.error("API error:", result.message);
    }
    throw new Error(result.message);
  }

  return result.data;
}

Permissions

API endpoints enforce fine-grained permissions using @PreAuthorize annotations. Permissions follow the resource:action format.

A 403 Forbidden response means the authenticated user does not have the required permission. Check the user's role and permission assignments in the platform settings.

Common Permissions

Endpoint PatternRequired Permission
GET /api/v1/assessmentsassessments:view
POST /api/v1/assessmentsassessments:create
PUT /api/v1/assessments/:idassessments:edit
DELETE /api/v1/assessments/:idassessments:delete
GET /api/v1/subject-requestssubject-requests:view
POST /api/v1/subject-requestssubject-requests:create
GET /api/v1/usersusers:view
POST /api/v1/users/inviteusers:create
GET /api/v1/consentconsent:view
PUT /api/v1/consent/configconsent:manage

File Upload

File uploads use multipart/form-data with the following constraints:

ConstraintValue
Max file size10 MB
Max request size50 MB
EndpointPOST /api/v1/storage/file-upload
curl -X POST https://api.neostra.io/v1/storage/file-upload \
  -H "Authorization: Bearer <token>" \
  -F "file=@/path/to/document.pdf" \
  -F "context=assessment" \
  -F "referenceId=assessment-abc-123"

Upload Response

{
  "success": true,
  "message": "File uploaded successfully",
  "data": {
    "fileId": "file-xyz-789",
    "fileName": "document.pdf",
    "fileUrl": "https://storage.neostra.io/uploads/file-xyz-789/document.pdf",
    "mimeType": "application/pdf",
    "size": 245760
  }
}

Correlation IDs

Every API request is assigned a correlation ID by the CustomRequestInterceptor. This ID is returned in the response headers and is used for distributed tracing across services.

X-Correlation-Id: 7f3a8b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c
# The correlation ID is in the response headers
curl -v -X GET https://api.neostra.io/v1/assessments \
  -H "Authorization: Bearer <token>" 2>&1 | grep X-Correlation-Id
# < X-Correlation-Id: 7f3a8b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c

When reporting issues or debugging failures, include the X-Correlation-Id from the response headers. It allows tracing the request across all backend services.

Rate Limiting

API rate limits are determined by the tenant's subscription plan. When limits are exceeded, the API returns a 429 Too Many Requests response with a Retry-After header.

{
  "success": false,
  "message": "Rate limit exceeded. Please retry after 60 seconds.",
  "data": null
}
HeaderDescription
X-RateLimit-LimitMaximum requests per minute
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the rate limit resets
Retry-AfterSeconds to wait before retrying (on 429 responses)