App Package Management
Manage complete application packages including data tables, frontend workers, and all associated resources. Export and import applications between environments, create backups, or deploy template applications.
Overview
The App Package Management feature provides a complete application portability solution. Export your entire app as a single package and import it into any Taruvi environment—development, staging, or production.
What Gets Exported
When you export an app, the package includes:
- App Metadata: Name, slug, description, and configuration
- Data Tables: Complete schemas with all field definitions and constraints
- Policies: Authorization policies (resource, role, and derived role policies from Cerbos)
- Frontend Workers: Worker metadata (name, slug, UUID) and active build archives
- Build Archives: Compressed frontend deployment packages (one ZIP per build)
- Storage Buckets: Bucket configurations (visibility, quotas, allowed MIME types) and all stored files (one ZIP per bucket)
What Doesn't Get Exported
To ensure portability across environments:
- ❌ Environment-specific configurations: Domain names, subdomains, S3 paths
- ❌ Actual data records: Only schemas are exported (not the data in tables)
- ❌ Cross-app reference tables: Tables that reference tables in other apps (not portable)
- ❌ Principal policies: User-specific authorization policies (tied to specific user IDs)
- ❌ User associations: Created by, modified by references
- ❌ Secret values: Actual secret values (API keys, passwords, tokens) are never exported for security
Secret metadata (key names, types, and tags) IS exported to preserve your secret structure. However, actual secret values are replaced with a placeholder: "PLACEHOLDER - Update with actual value". After importing, you must manually update each secret with the correct value for your environment.
This design ensures packages are fully portable and environment-neutral.
Benefits
- 🚀 Quick Deployment: Deploy complete applications in seconds
- 🔄 Environment Parity: Ensure dev, staging, and production have identical configurations
- 📦 Application Templates: Create reusable application blueprints
- 💾 Disaster Recovery: Keep backup packages for quick restoration
- 🌍 Cross-Environment Migration: Move apps between any Taruvi instance
Export Application
Endpoint
POST /api/apps/{app_slug}/packages/
Authentication
Requires JWT authentication with appropriate permissions:
Authorization: Bearer YOUR_ACCESS_TOKEN
Request
Content-Type: application/json (optional)
Parameters (optional):
include_datatables(boolean): Whether to include data table schemas in the export (default:true)include_functions(boolean): Whether to include function definitions and code in the export (default:true)include_secrets(boolean): Whether to include secret metadata in the export (default:true). Note: Only metadata is exported; actual secret values are never included.include_policies(boolean): Whether to include authorization policies in the export (default:true). Exports resource, role, and derived role policies from Cerbos.include_analytics(boolean): Whether to include analytics queries in the export (default:true)include_storage(boolean): Whether to include storage buckets and files in the export (default:true)include_frontend_workers(boolean): Whether to include frontend workers and builds in the export (default:true)
Example Request Body:
{
"include_datatables": true,
"include_functions": true,
"include_secrets": true,
"include_policies": true,
"include_analytics": true,
"include_storage": true,
"include_frontend_workers": true
}
If no request body is provided, all modules will be included by default.
Auto-Dependency Resolution
The export system automatically includes dependencies between modules:
- Analytics → Secrets: When
include_analytics=trueandinclude_secrets=false, any secrets referenced by analytics queries (viasecret_keyfield) are automatically included in the export. This ensures the exported package is self-contained.
For example, if you have an analytics query that uses secret_key: "db-connection", that secret will be automatically included even if include_secrets=false.
This behavior is automatic and cannot be disabled. The manifest will show the actual count of exported secrets.
Response
Returns a ZIP file containing:
manifest.json: Package metadata and integrity checksumsapp/metadata.json: App configurationdatatables/metadata.json: Data table schemasfunctions/metadata.json: Function definitionssecrets/metadata.json: Secret configurations (values encrypted/excluded)policies/metadata.json: Authorization policies (resource, role, derived role)analytics/metadata.json: Analytics queriestags/metadata.json: Tag definitionsstorage/metadata.json: Storage buckets metadatastorage/buckets/*.zip: Bucket archives (one ZIP per bucket)frontend_workers/metadata.json: Frontend worker metadatafrontend_workers/builds/*.zip: Build archives (one ZIP per build)
Response Headers:
Content-Type: application/zip
Content-Disposition: attachment; filename="{app_slug}_export_{timestamp}.zip"
X-Export-Job-UUID: {uuid}
The X-Export-Job-UUID header contains the UUID of the export job that can be used to track the export operation.
Example
# Export an app with all modules (default)
curl -X POST https://your-tenant.taruvi.app/api/apps/blog-app/packages/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-o blog-app-export.zip
# Export without storage (only app structure)
curl -X POST https://your-tenant.taruvi.app/api/apps/blog-app/packages/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"include_storage": false}' \
-o blog-app-export.zip
# Verify the export
unzip -l blog-app-export.zip
Export Package Structure
blog-app_export_20241204_120000.zip
├── manifest.json # Main package manifest with checksums
├── app/
│ └── metadata.json # App configuration
├── datatables/
│ └── metadata.json # Data table schemas
├── functions/
│ └── metadata.json # Function definitions
├── secrets/
│ └── metadata.json # Secret configurations
├── policies/
│ └── metadata.json # Authorization policies
├── analytics/
│ └── metadata.json # Analytics queries
├── tags/
│ └── metadata.json # Tag definitions
├── storage/
│ ├── metadata.json # Storage metadata
│ └── buckets/
│ ├── avatars.zip # Bucket archives (one per bucket)
│ ├── documents.zip # Contains all files for this bucket
│ └── images.zip
└── frontend_workers/
├── metadata.json # Frontend workers metadata
└── builds/
├── 550e8400-e29b-41d4-a716.zip # Build archives (one per build)
└── 660e8400-e29b-41d4-a716.zip
Storage Bucket ZIP Structure
Each bucket ZIP file (storage/buckets/{bucket_slug}.zip) contains:
avatars.zip
├── bucket_metadata.json # Bucket configuration
├── user1/profile.jpg # All files in their original paths
├── user2/profile.png
└── user3/avatar.gif
Manifest Format
Main Manifest (manifest.json):
{
"format": "taruvi-app-package",
"version": "1.0.0",
"created_at": "2024-12-04T12:00:00Z",
"created_by": "admin@example.com",
"package": {
"app_slug": "blog-app",
"app_name": "Blog Application",
"description": "Content management system"
},
"requires": {
"taruvi_version": ">=1.0.0"
},
"modules": {
"app": {
"count": 1,
"files": {
"app/metadata.json": "sha256:abc123..."
}
},
"datatables": {
"count": 3,
"files": {
"datatables/metadata.json": "sha256:def456..."
}
},
"functions": {
"count": 5,
"files": {
"functions/metadata.json": "sha256:ghi789..."
}
},
"secrets": {
"count": 2,
"files": {
"secrets/metadata.json": "sha256:jkl012..."
}
},
"policies": {
"count": 15,
"by_type": {
"resource": 13,
"role": 1,
"derived_role": 1
},
"files": {
"policies/metadata.json": "sha256:pol345..."
}
},
"analytics": {
"count": 4,
"files": {
"analytics/metadata.json": "sha256:mno345..."
}
},
"tags": {
"count": 10,
"files": {
"tags/metadata.json": "sha256:pqr678..."
}
},
"storage": {
"count": 2,
"bucket_count": 2,
"total_files": 45,
"total_size_bytes": 12582912,
"files": {
"storage/metadata.json": "sha256:stu901...",
"storage/buckets/avatars.zip": "sha256:vwx234...",
"storage/buckets/documents.zip": "sha256:yz1567..."
}
},
"frontend_workers": {
"count": 2,
"build_count": 2,
"files": {
"frontend_workers/metadata.json": "sha256:abc890...",
"frontend_workers/builds/550e8400-e29b-41d4-a716.zip": "sha256:def123...",
"frontend_workers/builds/660e8400-e29b-41d4-a716.zip": "sha256:ghi456..."
}
}
},
"export_options": {
"include_datatables": true,
"include_functions": true,
"include_secrets": true,
"include_policies": true,
"include_analytics": true,
"include_storage": true,
"include_frontend_workers": true
},
"integrity": {
"package_checksum": "sha256:overall_checksum...",
"manifest_checksum": "sha256:manifest_checksum..."
}
}
Module Metadata Files:
Each module has its own {module}/metadata.json file containing the module-specific data:
app/metadata.json: App configuration (slug, name, description)datatables/metadata.json: Array of data table schemas with Frictionless Table Schema definitionsfunctions/metadata.json: Array of function definitions with code and configurationsecrets/metadata.json: Array of secret metadata (values are excluded for security)policies/metadata.json: Array of authorization policies (see Policies Format below)analytics/metadata.json: Array of analytics query definitionstags/metadata.json: Array of tag definitions with names and metadatastorage/metadata.json: Storage bucket configurations and file metadatafrontend_workers/metadata.json: Frontend worker definitions and build references
Policies Format
The policies/metadata.json file contains an array of policy definitions in a simplified, portable format:
Resource Policy Example:
{
"policy_type": "resource",
"entity_type": "datatable",
"name": "users",
"rules": [
{
"actions": ["read", "list"],
"effect": "EFFECT_ALLOW",
"roles": ["viewer", "editor", "admin"]
},
{
"actions": ["create", "update", "delete"],
"effect": "EFFECT_ALLOW",
"roles": ["editor", "admin"]
}
],
"import_derived_roles": ["owner"]
}
Role Policy Example:
{
"policy_type": "role",
"name": "editor",
"rules": [
{
"resource": "datatable:*",
"allow_actions": ["read", "create", "update"]
}
],
"parent_roles": ["viewer"]
}
Derived Role Example:
{
"policy_type": "derived_role",
"name": "owner",
"definitions": [
{
"name": "owner",
"parent_roles": ["user"],
"condition": {
"match": {
"expr": "request.principal.id == request.resource.attr.created_by"
}
}
}
]
}
Policy Fields:
| Field | Policy Types | Description |
|---|---|---|
policy_type | All | Type: resource, role, or derived_role |
name | All | Policy identifier (without scope prefix) |
entity_type | Resource only | Resource type (e.g., datatable, storage, function) |
rules | Resource, Role | Array of permission rules |
definitions | Derived Role only | Array of role definitions with conditions |
import_derived_roles | Resource only | Derived roles referenced by this policy |
parent_roles | Role only | Roles this role inherits from |
variables | All | Optional policy variables |
metadata | All | Optional annotations (excluding internal Cerbos fields) |
Download Stored Package
Download a previously generated package without regenerating it. Packages are automatically stored on S3 when created via the export endpoint.
Endpoint
GET /api/apps/{app_slug}/packages/{version}
Currently only version 1 is supported (latest package). Future versions will be supported when app versioning is implemented.
Authentication
Requires JWT authentication with appropriate permissions:
Authorization: Bearer YOUR_ACCESS_TOKEN
Behavior
- If stored package exists: Returns the stored ZIP file immediately
- If no stored package: Auto-generates a new package, stores it, and returns it
Response
Returns a ZIP file download.
Response Headers:
Content-Type: application/zip
Content-Disposition: attachment; filename="{app_slug}_package_v1.zip"
Example
# Download stored package (or auto-generate if not exists)
curl -X GET https://your-tenant.taruvi.app/api/apps/blog-app/packages/1 \
-H "Authorization: Bearer YOUR_TOKEN" \
-o blog-app-package.zip
Package Info in App Response
When retrieving app details, the response includes package information:
{
"slug": "blog-app",
"name": "Blog Application",
"package_info": {
"has_package": true,
"generated_at": "2024-12-04T12:00:45Z",
"download_url": "/api/apps/blog-app/packages/1"
}
}
If no package has been generated yet:
{
"package_info": {
"has_package": false,
"generated_at": null,
"download_url": null
}
}
Audit Trail (Export & Import Jobs)
Every export and import operation is recorded as a job for auditing, debugging, and compliance purposes.
Why Jobs Are Recorded
| Use Case | Description |
|---|---|
| Audit & Compliance | Track who exported/imported what app, when, for security and regulatory requirements |
| Debugging | If an import fails or causes issues, review the job history to see what changed |
| Historical Analysis | Understand patterns - how often apps are exported, which imports had warnings |
| Support | Help diagnose issues by examining job details, error messages, and module counts |
What Gets Recorded
ExportJob (per export operation):
- UUID, app, status, who triggered it, when
- Export options used (which modules were included)
- Artifact size and checksum
- Error message if failed
ImportJob (per import operation):
- UUID, target app slug, status, who triggered it, when
- Full manifest from the package
- Per-module results (created/updated counts)
- Warnings and errors
Jobs are recorded automatically - you don't need to do anything special. Use the list/retrieve endpoints below when you need to review history or debug issues.
List Export Jobs
Endpoint
GET /api/apps/{app_slug}/packages/
Authentication
Requires JWT authentication with appropriate permissions.
Query Parameters
status(optional): Filter by export job status (in_progress,completed,failed)ordering(optional): Order by field (default:-created_at)page(optional): Page number (default: 1)page_size(optional): Number of results per page (default: 10, max: 100)
Response
Success Response (200 OK):
{
"success": true,
"message": "Export jobs retrieved",
"status_code": 200,
"data": [
{
"id": 1,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"app_name": "Blog Application",
"app_slug": "blog-app",
"status": "completed",
"started_at": "2024-12-04T12:00:00Z",
"completed_at": "2024-12-04T12:00:45Z",
"created_at": "2024-12-04T12:00:00Z"
},
{
"id": 2,
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"app_name": "Blog Application",
"app_slug": "blog-app",
"status": "failed",
"started_at": "2024-12-03T10:00:00Z",
"completed_at": "2024-12-03T10:00:15Z",
"created_at": "2024-12-03T10:00:00Z"
}
],
"total": 2,
"page": 1,
"page_size": 10
}
Example
# List all export jobs for an app
curl -X GET https://your-tenant.taruvi.app/api/apps/blog-app/packages/ \
-H "Authorization: Bearer YOUR_TOKEN"
# List only completed exports
curl -X GET "https://your-tenant.taruvi.app/api/apps/blog-app/packages/?status=completed" \
-H "Authorization: Bearer YOUR_TOKEN"
# Paginated results
curl -X GET "https://your-tenant.taruvi.app/api/apps/blog-app/packages/?page=2&page_size=10" \
-H "Authorization: Bearer YOUR_TOKEN"
Get Export Job Details
Endpoint
GET /api/apps/{app_slug}/packages/{uuid}/
Authentication
Requires JWT authentication with appropriate permissions.
Response
Success Response (200 OK):
{
"success": true,
"message": "Export job retrieved",
"status_code": 200,
"data": {
"id": 1,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"app_name": "Blog Application",
"app_slug": "blog-app",
"status": "completed",
"options": {
"version": "1.0.0",
"modules": {
"app": {"count": 1},
"datatables": {"count": 3},
"functions": {"count": 5},
"secrets": {"count": 2},
"policies": {"count": 15, "by_type": {"resource": 13, "role": 1, "derived_role": 1}},
"analytics": {"count": 4},
"tags": {"count": 10},
"storage": {"count": 2, "bucket_count": 2, "total_files": 45},
"frontend_workers": {"count": 2, "build_count": 2}
},
"export_options": {
"include_datatables": true,
"include_functions": true,
"include_secrets": true,
"include_policies": true,
"include_analytics": true,
"include_storage": true,
"include_frontend_workers": true
}
},
"artifact_size_bytes": 12582912,
"artifact_checksum": "sha256:abc123...",
"error_message": null,
"started_at": "2024-12-04T12:00:00Z",
"completed_at": "2024-12-04T12:00:45Z",
"created_at": "2024-12-04T12:00:00Z",
"created_by_email": "admin@example.com"
}
}
Not Found Response (404):
{
"success": false,
"message": "Export job '550e8400-e29b-41d4-a716-446655440000' not found",
"status_code": 404,
"data": null
}
Example
# Get export job details
curl -X GET https://your-tenant.taruvi.app/api/apps/blog-app/packages/550e8400-e29b-41d4-a716-446655440000/ \
-H "Authorization: Bearer YOUR_TOKEN"
Import Application
Endpoint
POST /api/apps/imports/
Authentication
Requires JWT authentication with appropriate permissions:
Authorization: Bearer YOUR_ACCESS_TOKEN
Request
Content-Type: multipart/form-data
Parameters:
file(required): The export ZIP filedry_run(optional, boolean): Iftrue, validates the package without making any changes (default:false)
Behavior:
- App slug is determined from the ZIP file manifest
- Checksum validation is optional (set
validate_checksum=truefor production imports) - If the app already exists, it will be updated; otherwise, it will be created
Response
Success Response (200 OK):
{
"success": true,
"message": "Import completed successfully",
"status_code": 200,
"data": {
"status": "success",
"dry_run": false,
"app_slug": "blog-app",
"app_name": "Blog Application",
"version": "1.0.0",
"modules": ["app", "datatables", "functions", "secrets", "policies", "analytics", "tags", "storage", "frontend_workers"],
"results": {
"app": {
"created": false,
"updated": true
},
"datatables": {
"created": 2,
"updated": 1,
"skipped": 0
},
"functions": {
"created": 5,
"updated": 0
},
"secrets": {
"created": 2,
"updated": 0
},
"policies": {
"created": 13,
"updated": 2,
"skipped": 0
},
"analytics": {
"created": 4,
"updated": 0
},
"tags": {
"created": 8,
"updated": 2
},
"storage": {
"buckets_created": 1,
"buckets_updated": 1,
"files_imported": 45,
"files_failed": 0
},
"frontend_workers": {
"created": 2,
"updated": 0,
"builds_deployed": 2
}
},
"warnings": [
"[storage] File 'avatars/old-avatar.jpg' already exists, updated"
]
}
}
Dry Run Response (200 OK):
{
"success": true,
"message": "Dry run completed - no changes made",
"status_code": 200,
"data": {
"status": "dry_run",
"dry_run": true,
"valid": true,
"app_slug": "blog-app",
"app_name": "Blog Application",
"version": "1.0.0",
"modules": ["app", "datatables", "functions", "secrets", "policies", "analytics", "tags", "storage", "frontend_workers"],
"preview": {
"app": "would_update",
"datatables": {"would_create": 2, "would_update": 1},
"functions": {"would_create": 5},
"secrets": {"would_create": 2},
"policies": {"would_create": 13, "would_update": 2},
"analytics": {"would_create": 4},
"tags": {"would_create": 8, "would_update": 2},
"storage": {"would_create": 1, "would_update": 1},
"frontend_workers": {"would_create": 2}
},
"warnings": []
}
}
Validation Error Response (400 Bad Request):
{
"success": false,
"message": "Data validation failed",
"status_code": 400,
"data": null,
"error": {
"code": "PKG_VALIDATION_FAILED",
"message": "Data validation failed",
"errors": [
"datatables[0]: Invalid field type 'invalidtype' for field 'email'",
"functions[2]: Missing required field 'code'"
]
}
}
Checksum Error Response (400 Bad Request):
{
"success": false,
"message": "Checksum verification failed",
"status_code": 400,
"data": null,
"error": {
"code": "PKG_CHECKSUM_MISMATCH",
"message": "Checksum verification failed",
"details": {
"file": "storage/buckets/avatars.zip",
"expected": "sha256:abc123...",
"actual": "sha256:def456..."
}
}
}
Example
# Standard import (creates new app or updates existing)
curl -X POST https://your-tenant.taruvi.app/api/apps/imports/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@blog-app-export.zip"
# Dry run (validation only, no changes)
curl -X POST https://your-tenant.taruvi.app/api/apps/imports/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@blog-app-export.zip" \
-F "dry_run=true"
List Import Jobs
Track the history of all import operations.
Endpoint
GET /api/apps/imports/jobs/
Authentication
Requires JWT authentication with appropriate permissions.
Query Parameters
target_app_slug(optional): Filter by target app slugstatus(optional): Filter by import job status (pending,in_progress,completed,failed)is_dry_run(optional): Filter by dry run status (true/false)ordering(optional): Order by field (default:-created_at)page(optional): Page number (default: 1)page_size(optional): Number of results per page (default: 10, max: 100)
Response
Success Response (200 OK):
{
"success": true,
"message": "Import jobs retrieved",
"status_code": 200,
"data": [
{
"id": 1,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"is_dry_run": false,
"target_app_slug": "blog-app",
"created_at": "2024-12-04T12:00:00Z",
"updated_at": "2024-12-04T12:00:45Z"
},
{
"id": 2,
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"status": "failed",
"is_dry_run": false,
"target_app_slug": "api-gateway",
"created_at": "2024-12-03T10:00:00Z",
"updated_at": "2024-12-03T10:00:15Z"
}
],
"total": 2,
"page": 1,
"page_size": 10
}
Example
# List all import jobs
curl -X GET https://your-tenant.taruvi.app/api/apps/imports/jobs/ \
-H "Authorization: Bearer YOUR_TOKEN"
# List only completed imports
curl -X GET "https://your-tenant.taruvi.app/api/apps/imports/jobs/?status=completed" \
-H "Authorization: Bearer YOUR_TOKEN"
# Filter by app slug
curl -X GET "https://your-tenant.taruvi.app/api/apps/imports/jobs/?target_app_slug=blog-app" \
-H "Authorization: Bearer YOUR_TOKEN"
Get Import Job Details
Endpoint
GET /api/apps/imports/jobs/{uuid}/
Authentication
Requires JWT authentication with appropriate permissions.
Response
Success Response (200 OK):
{
"success": true,
"message": "Import job retrieved",
"status_code": 200,
"data": {
"id": 1,
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"is_dry_run": false,
"target_app_slug": "blog-app",
"upload_name": "blog-app-export.zip",
"artifact_size_bytes": 12582912,
"manifest": {
"format": "taruvi-app-package",
"version": "1.0.0",
"package": {
"app_slug": "blog-app",
"app_name": "Blog Application"
},
"modules": {
"app": {"count": 1},
"datatables": {"count": 3},
"functions": {"count": 5},
"secrets": {"count": 2},
"policies": {"count": 15},
"analytics": {"count": 4},
"tags": {"count": 10},
"storage": {"count": 2},
"frontend_workers": {"count": 2}
}
},
"results": {
"app": {"created": false, "updated": true},
"datatables": {"created": 2, "updated": 1},
"functions": {"created": 5, "updated": 0},
"secrets": {"created": 2, "updated": 0},
"policies": {"created": 13, "updated": 2},
"analytics": {"created": 4, "updated": 0},
"tags": {"created": 8, "updated": 2},
"storage": {"created": 1, "updated": 1, "files_imported": 45},
"frontend_workers": {"created": 2, "updated": 0}
},
"created_at": "2024-12-04T12:00:00Z",
"updated_at": "2024-12-04T12:00:45Z",
"created_by_email": "admin@example.com"
}
}
Not Found Response (404):
{
"success": false,
"message": "Import job '550e8400-e29b-41d4-a716-446655440000' not found",
"status_code": 404,
"data": null
}
Example
# Get import job details
curl -X GET https://your-tenant.taruvi.app/api/apps/imports/jobs/550e8400-e29b-41d4-a716-446655440000/ \
-H "Authorization: Bearer YOUR_TOKEN"
Import Behavior
App Creation/Update
- New app: Creates app with slug from the package manifest
- Existing app (same slug): Updates app metadata and associated resources
Data Tables
- New tables: Creates DataTable definitions
- Existing tables: Updates schemas (uses create-or-update pattern)
- Physical tables: NOT automatically materialized during import
- Use the Data Service API to materialize tables after import
Tables that are cross-app references (referencing tables in other apps) are skipped during export. After importing, you must manually recreate any cross-app references using the import-reference endpoint if needed.
Skipped references are listed in the manifest under modules.datatables.skipped_references.
Policies
Policies are exported from and imported to Cerbos (external authorization service), not Django database.
Supported Policy Types
| Type | Description | Example |
|---|---|---|
| Resource | Controls access to specific resources (datatables, storage, etc.) | Allow read on datatable:users for role viewer |
| Role | Defines role hierarchies and permissions | admin role inherits from editor |
| Derived Role | Dynamic roles based on conditions | owner if user_id == resource.created_by |
Principal policies (user-specific policies) are NOT exported as they are tied to specific users and not portable between environments.
Policy Import Behavior
- New policies: Created in Cerbos with target app scope
- Existing policies: Updated (uses add-or-update pattern)
- Disabled policies: Skipped during export (not included in package)
Scope Transformation
Policies are scoped by {tenant_id}_{app_slug} for multi-tenant isolation:
Source Environment (tenant: walmart, app: inventory)
Policy scope: walmart_inventory
Target Environment (tenant: jio, app: inventory)
Policy scope: jio_inventory (automatically transformed)
The export process strips the source scope, and import adds the target scope automatically.
Derived Roles: Special Handling
Unlike other policy types, derived roles don't support the Cerbos scope field. Instead, tenant isolation is achieved via naming convention:
┌─────────────────────────────────────────────────────────────────────┐
│ SCOPED POLICIES (Resource/Role) │ DERIVED ROLES │
├─────────────────────────────────────────────────────────────────────┤
│ │ │
│ Isolation via scope field: │ Isolation via name prefix: │
│ name: "users" │ name: "walmart_inv_owner" │
│ scope: "walmart_inventory" │ (no scope field) │
│ │ │
│ Export: strip scope │ Export: strip name prefix │
│ Import: add target scope │ Import: add target prefix │
│ │ │
└─────────────────────────────────────────────────────────────────────┘
Why This Matters:
- Derived role names like
ownerbecomewalmart_inventory_ownerin Cerbos - On export, the prefix is stripped:
owner - On import to a different app, the new prefix is added:
jio_inventory_owner
This is a Cerbos architectural limitation, not a Taruvi design choice. The Cerbos DerivedRoles protobuf doesn't have a scope field.
Frontend Workers
Worker Creation/Update
Uses create-or-update pattern based on (app, slug):
- New worker: Creates with original UUID and metadata
- Existing worker (same slug): Updates name and metadata
Build Deployment
- Creates new build record with original UUID
- Uploads build archive to S3
- Deploys build to temporary domain
- Sets as active build
Temporary Domains
Imported frontend workers are automatically assigned human-friendly temporary subdomains:
Format: {worker-slug}-{short-uuid}
Examples:
admin-portal + 550e8400-... → admin-portal-550e84
dashboard + a1b2c3d4-... → dashboard-a1b2c3
helpdesk-app + f7e8d9c0-... → helpdesk-app-f7e8d9
Full Domains (with environment prefix):
- Development:
dev-admin-portal-550e84.taruvi.app - Staging:
staging-dashboard-a1b2c3.taruvi.app - Production:
helpdesk-app-f7e8d9.taruvi.app
Why Temporary Domains?
- Export packages don't contain environment-specific domain configuration
- Temporary domains ensure workers are accessible immediately after import
- You can update to your preferred subdomain after import using the Frontend Workers API
Updating Subdomains After Import:
# Get the worker details (using slug from export)
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/frontend_workers/admin-portal/
# Update to your preferred subdomain
curl -X PATCH https://your-tenant.taruvi.app/api/frontend_workers/admin-portal/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subdomain_input": "my-admin"}'
# Result: Worker now accessible at dev-my-admin.taruvi.app
See Frontend Workers - Update Worker for details.
Storage Buckets
Bucket Creation/Update
Uses create-or-update pattern based on (app, slug):
- New bucket: Creates bucket with configuration from export
- Existing bucket (same slug): Updates configuration (visibility, quotas, allowed types)
File Restoration
- Recreates all files from export package
- Uploads files to S3 storage
- Preserves file metadata (mimetype, custom metadata)
- Uses upsert semantics (updates existing files)
Import Limits
To prevent memory issues, storage imports have the following limits:
- Maximum package size: 100MB total storage size
- Maximum file count: 1,000 files per import
- Packages exceeding these limits will be rejected during validation
Large Storage Workaround: If you need to migrate larger storage buckets:
- Export/import app structure first (without large storage)
- Use direct S3 sync or AWS CLI to transfer files
- Manually create Object records in database
Import Statistics
Storage import returns detailed statistics:
{
"created": 2, // Buckets created
"updated": 1, // Buckets updated
"files_imported": 45, // Files successfully uploaded
"files_failed": 2, // Files that failed
"warnings": [ // Detailed warnings
"Skipped file 'avatars/missing.jpg': file content missing from package"
]
}
Quota Handling
- Bucket quotas are restored from export
quota_exceededflag is NOT restored (recalculated)- Import respects bucket file size limits
- Failed uploads due to quota are logged in warnings
Common Use Cases
Use Case 1: Development to Production
Export from development and deploy to production:
# 1. Export from development environment
curl -X POST https://dev.yourapp.com/api/apps/blog-app/packages/ \
-H "Authorization: Bearer $DEV_TOKEN" \
-o blog-app.zip
# 2. Import to production environment
curl -X POST https://prod.yourapp.com/api/apps/imports/ \
-H "Authorization: Bearer $PROD_TOKEN" \
-F "file=@blog-app.zip"
# 3. Materialize data tables in production
curl -X POST https://prod.yourapp.com/api/apps/blog-app/datatables/posts/materialize/ \
-H "Authorization: Bearer $PROD_TOKEN"
# 4. Update frontend worker subdomains (if desired)
curl -X PATCH https://prod.yourapp.com/api/frontend_workers/admin-portal/ \
-H "Authorization: Bearer $PROD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subdomain_input": "admin"}'
Use Case 2: Application Templates
Create reusable application templates:
# 1. Create and configure template app
curl -X POST https://your-tenant.taruvi.app/api/apps/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "E-commerce Template",
"slug": "ecommerce-template"
}'
# 2. Add data tables, frontend workers, etc.
# ... (setup your template)
# 3. Export template
curl -X POST https://your-tenant.taruvi.app/api/apps/ecommerce-template/packages/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-o ecommerce-template.zip
# 4. Import template (creates or updates app with slug from package)
curl -X POST https://your-tenant.taruvi.app/api/apps/imports/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@ecommerce-template.zip"
# Note: The app slug is determined by the package manifest.
# To deploy to multiple tenants, import the same package into different tenant environments.
Use Case 3: Disaster Recovery
Create regular backups:
#!/bin/bash
# backup-apps.sh
APPS=("blog-app" "api-gateway" "admin-portal")
BACKUP_DIR="./backups/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
for app in "${APPS[@]}"; do
echo "Backing up $app..."
curl -X POST https://your-tenant.taruvi.app/api/apps/${app}/packages/ \
-H "Authorization: Bearer $BACKUP_TOKEN" \
-o "${BACKUP_DIR}/${app}-backup.zip"
done
echo "Backup complete: $BACKUP_DIR"
Use Case 4: Multi-Tenant Deployment
Deploy the same application to multiple tenant environments:
# Export from master template
curl -X POST https://master.taruvi.app/api/apps/saas-template/packages/ \
-H "Authorization: Bearer $MASTER_TOKEN" \
-o saas-template.zip
# Deploy to tenant environments
TENANTS=("tenant1" "tenant2" "tenant3")
for tenant in "${TENANTS[@]}"; do
echo "Deploying to ${tenant}..."
curl -X POST https://${tenant}.taruvi.app/api/apps/imports/ \
-H "Authorization: Bearer $TENANT_TOKEN" \
-F "file=@saas-template.zip"
echo "✓ Deployed to ${tenant}"
done
# Note: The app slug comes from the package manifest.
# Each tenant environment receives the same app structure.
Use Case 5: CI/CD Integration
Automate deployments in your CI/CD pipeline:
# .github/workflows/deploy.yml
name: Deploy Application
on:
push:
branches: [main]
jobs:
export-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Export from Staging
run: |
curl -X POST https://staging.yourapp.com/api/apps/main-app/packages/ \
-H "Authorization: Bearer ${{ secrets.STAGING_TOKEN }}" \
-o app-package.zip
- name: Import to Production
run: |
curl -X POST https://prod.yourapp.com/api/apps/imports/ \
-H "Authorization: Bearer ${{ secrets.PROD_TOKEN }}" \
-F "file=@app-package.zip"
- name: Materialize Tables
run: |
# Materialize critical tables
for table in users orders products; do
curl -X POST https://prod.yourapp.com/api/apps/main-app/datatables/${table}/materialize/ \
-H "Authorization: Bearer ${{ secrets.PROD_TOKEN }}"
done
Package Validation
Import packages are validated before processing:
Required Structure
✅ Valid Package:
app-export.zip
├── manifest.json # Required
├── storage/
│ ├── metadata.json # Required if storage exists
│ └── buckets/*.zip # Referenced bucket ZIPs must exist
├── frontend_workers/
│ ├── metadata.json # Required if workers exist
│ └── builds/*.zip # Referenced builds must exist
❌ Invalid Package:
app-export.zip
├── manifest.json # Missing app metadata
└── random-files/ # Unknown structure
Validation Checks
-
Package Integrity
- ZIP file is valid and extractable
- manifest.json exists and is valid JSON
-
Manifest Validation
- Contains required fields (version, app, export_timestamp)
- App metadata is complete (slug, name)
- Referenced files exist in package
-
Storage Buckets
- All referenced bucket ZIPs exist in
storage/buckets/ - Bucket slugs are valid format
- Package size within limits (100MB total)
- File count within limits (1,000 files max)
- Checksums match bucket ZIP contents
- All referenced bucket ZIPs exist in
-
Frontend Workers
- All referenced build archives exist
- Build UUIDs match archive filenames
- Worker slugs are valid format
- Checksums match archive contents
-
Data Tables
- Frictionless schemas are valid
- No circular foreign key dependencies
- Table names follow naming conventions
Validation Errors
Missing Manifest:
{
"success": false,
"message": "Invalid export package",
"status_code": 400,
"errors": {
"manifest": ["manifest.json not found in package"]
}
}
Invalid Worker Data:
{
"success": false,
"message": "Package validation failed",
"status_code": 400,
"errors": {
"frontend_workers": [
"Worker 'admin-portal': missing required field 'slug'",
"Build archive 'builds/550e8400.zip' not found in package"
]
}
}
Best Practices
1. Version Control Your Exports
Store export packages in version control for audit trail:
# Tag exports with version
export APP_VERSION="v1.2.3"
curl -X POST https://your-tenant.taruvi.app/api/apps/main-app/packages/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-o "releases/main-app-${APP_VERSION}.zip"
git add releases/
git commit -m "Release ${APP_VERSION}"
git tag "${APP_VERSION}"
2. Test Imports in Staging First
Always test imports in non-production environments:
# 1. Export from production
curl -X POST https://prod.yourapp.com/api/apps/main-app/packages/ \
-H "Authorization: Bearer $PROD_TOKEN" \
-o prod-snapshot.zip
# 2. Test import in staging (uses app slug from package manifest)
curl -X POST https://staging.yourapp.com/api/apps/imports/ \
-H "Authorization: Bearer $STAGING_TOKEN" \
-F "file=@prod-snapshot.zip"
# 3. Verify functionality
# ... run tests ...
# 4. If successful, proceed with actual deployment
3. Keep Export Packages Organized
Use consistent naming conventions:
# Format: {app-slug}-{environment}-{timestamp}.zip
blog-app-production-20241204.zip
api-gateway-staging-20241204.zip
admin-portal-dev-20241204.zip
4. Document Custom Configuration
After import, document environment-specific configuration needed:
# Post-Import Checklist
- [ ] Update frontend worker subdomains
- [ ] Materialize data tables
- [ ] Configure environment variables
- [ ] Set up custom domains
- [ ] Test API endpoints
- [ ] Verify authentication
5. Regular Backup Schedule
Automate regular exports for disaster recovery:
# Daily backup script
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_DIR="/backups/daily"
curl -X POST https://prod.yourapp.com/api/apps/main-app/packages/ \
-H "Authorization: Bearer $BACKUP_TOKEN" \
-o "${BACKUP_DIR}/main-app-${TIMESTAMP}.zip"
# Keep only last 7 days
find "$BACKUP_DIR" -name "*.zip" -mtime +7 -delete
Troubleshooting
Export Takes Too Long
Symptoms: Export request times out or takes many minutes
Possible Causes:
- Large build archives (frontend workers with many assets)
- Many frontend workers with builds
- Network bandwidth limitations
Solutions:
- Reduce build sizes: Optimize frontend builds before deployment
- Clean up old builds: Delete unused builds to reduce export size
- Increase timeout: Configure longer timeout in client
- Export during off-peak hours: Schedule exports when traffic is low
Import Fails with "Package Validation Failed"
Symptoms: Import rejected before processing
Possible Causes:
- Corrupted ZIP file
- Missing or invalid manifest.json
- Missing referenced build archives
Solutions:
-
Verify ZIP integrity:
unzip -t app-export.zip -
Check manifest:
unzip -p app-export.zip manifest.json | jq . -
Verify package structure:
unzip -l app-export.zip
Worker Deployed but Not Accessible
Symptoms: Import succeeds but frontend worker URL returns 404
Possible Causes:
- Temporary subdomain not yet propagated
- Build deployment failed silently
- DNS/CDN caching issues
Solutions:
-
Check worker status:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/ -
Verify build status: Check that
active_build_uuidis set -
Wait for propagation: CDN/DNS changes may take 1-5 minutes
-
Check build list:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/builds/
App Already Exists
Symptoms: You're importing an app that already exists in the environment
Behavior: The import uses create-or-update (upsert) semantics:
- If the app slug from the package already exists, the import updates the existing app
- If the app slug doesn't exist, a new app is created
This is usually the desired behavior - it allows you to re-import packages to update existing apps.
If you want a fresh install instead:
-
Delete existing app first (if intentional replacement):
curl -X DELETE https://your-tenant.taruvi.app/api/apps/{existing-slug}/ \
-H "Authorization: Bearer YOUR_TOKEN" -
Then import the package:
curl -X POST https://your-tenant.taruvi.app/api/apps/imports/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@app-export.zip"
The app slug is determined by the package manifest and cannot be overridden during import. To deploy the same package with a different slug, you would need to modify the package contents.
Data Tables Not Created
Symptoms: Import succeeds but data tables missing
Possible Causes:
- Data tables not materialized during import
- Schema validation errors
Solutions:
-
Check app's data tables:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/ -
Materialize manually:
curl -X POST https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/{table-name}/materialize/ \
-H "Authorization: Bearer YOUR_TOKEN"
Secrets Not Working After Import
Symptoms: Functions fail with authentication errors, API calls return 401/403
Cause:
Secret values are never exported for security reasons. Only secret metadata (key names, types, and tags) is included in the package. After import, all secrets have a placeholder value: "PLACEHOLDER - Update with actual value".
Solutions:
-
List imported secrets:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/secrets/?app={app-slug} -
Update each secret with the correct value:
curl -X PUT https://your-tenant.taruvi.app/api/secrets/{secret-key}/?app={app-slug} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": "your-actual-secret-value"}' -
Update function auth_config (if using authenticated webhooks): Function
auth_configvalues (API keys, tokens, etc.) are also redacted during export. Update them via:curl -X PATCH https://your-tenant.taruvi.app/api/apps/{app-slug}/functions/{function-slug}/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"auth_config": {"type": "bearer", "token": "your-actual-token"}}'
After importing an app package, always:
- Update all secret values
- Update function auth_config for authenticated webhooks
- Verify API integrations work correctly
Storage Import Fails or Incomplete
Symptoms: Storage files missing after import, or import shows warnings
Possible Causes:
- Package exceeds size limits (100MB)
- Too many files (>1,000)
- S3 upload failures
- Bucket quota exceeded
- Missing file content in package
Solutions:
-
Check import statistics: Look at the import response for
files_failedandwarningsfields:{
"files_imported": 45,
"files_failed": 2,
"warnings": [
"Skipped file 'avatars/missing.jpg': file content missing from package"
]
} -
Verify package size:
# Check total storage size in manifest
unzip -p app-export.zip storage/metadata.json | jq '.total_size_bytes'
# If over 100MB, you'll need to split the import -
For large storage buckets:
- Export/import app structure separately
- Use AWS CLI to sync large files:
# Direct S3 bucket sync (requires AWS credentials)
aws s3 sync ./local-backup/ s3://your-bucket/path/
-
Check bucket quotas: Verify imported buckets have sufficient quota:
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/storage/buckets/{bucket-slug}/ -
Retry failed files: If specific files failed, you can manually upload them:
curl -X PUT https://your-tenant.taruvi.app/api/storage/buckets/{bucket-slug}/objects/{path}/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@local-file.jpg"
Storage Package Too Large
Symptoms: Import validation fails with "Storage import too large"
Cause: Package exceeds 100MB limit
Solutions:
-
Split storage by bucket category: Export different bucket categories separately (if using filters in future)
-
Selective file migration:
- Import app structure without storage first
- Manually migrate critical files only
- Use S3 sync for bulk files
-
Increase limits (admin only): Platform administrators can adjust
MAX_IMPORT_SIZE_BYTESinStorageHandler
Security Considerations
Access Control
- Export requires appropriate app-level permissions
- Import requires create app permissions
- Build archives are validated for malicious content
- Only authorized users can export/import apps
Secrets Management
⚠️ Important: Export packages do NOT include:
- Environment variables
- API keys or tokens
- Database credentials
- OAuth client secrets
After import, configure:
- Environment-specific variables
- API credentials
- Third-party integrations
- Custom domains (if needed)
Package Integrity
- Validate package checksums before import
- Store export packages securely
- Use secure transfer methods (HTTPS, encrypted storage)
- Implement access controls on backup storage
Limits and Quotas
| Resource | Limit |
|---|---|
| Maximum package size | 100MB |
| Maximum workers per app | Unlimited |
| Maximum builds per worker | Unlimited (but only active builds exported) |
| Maximum data tables per app | Unlimited |
| Export timeout | 5 minutes |
| Import timeout | 10 minutes |
Note: Limits are configurable by platform administrators.
API Response Codes
| Code | Description |
|---|---|
| 200 | OK - Export/Import successful |
| 400 | Bad Request - Invalid package or parameters |
| 401 | Unauthorized - Authentication required |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - App not found |
| 413 | Payload Too Large - Package exceeds size limit |
| 500 | Internal Server Error - Server error occurred |
Next Steps
- Frontend Workers: Learn about frontend worker deployment
- Data Service: Work with data tables
- Apps Management: General app operations
- API Overview: Authentication and general API usage
Need help? Contact your Taruvi administrator or check the interactive API documentation at /api/docs/.