Skip to main content

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
  • App Roles: Application-scoped roles with hierarchy (name, description, parent relationships)
  • 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
Secrets Handling

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_app_roles (boolean): Whether to include app roles in the export (default: true)
  • 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)
Frontend Workers Temporarily Disabled

Package export/import currently skips frontend workers, even if include_frontend_workers=true.

Example Request Body:

{
"include_app_roles": true,
"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=true and include_secrets=false, any secrets referenced by analytics queries (via secret_key field) 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.

Discover Export Options (for dynamic UIs)

If you're building a UI for exporting apps, you can fetch the available include_* options from the backend instead of hardcoding them. This prevents frontend changes whenever a new exportable module is added.

Endpoint:

GET /api/apps/{app_slug}/packages/export-config/

Example:

curl -X GET https://your-tenant.taruvi.app/api/apps/blog-app/packages/export-config/ \
-H "Authorization: Bearer YOUR_TOKEN"

The response includes:

  • order: ordered list of include_* keys
  • options: list of option descriptors (key, default, label, help_text)
  • always_included: modules that are always exported (e.g. app, tags)
  • notes: human-readable export behavior notes (e.g. auto-dependencies)

Response

Returns a ZIP file containing:

  • manifest.json: Package metadata and integrity checksums
  • app/metadata.json: App configuration
  • app_roles/metadata.json: App roles (portable by role name; hierarchy via parent_name)
  • datatables/metadata.json: Data table schemas
  • functions/metadata.json: Function definitions
  • secrets/metadata.json: Secret configurations (values encrypted/excluded)
  • policies/metadata.json: Authorization policies (resource, role, derived role)
  • analytics/metadata.json: Analytics queries
  • tags/metadata.json: Tag definitions
  • storage/metadata.json: Storage buckets metadata
  • storage/buckets/*.zip: Bucket archives (one ZIP per bucket)
  • frontend_workers/metadata.json: Frontend worker metadata
  • frontend_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
├── app_roles/
│ └── metadata.json # App roles (portable by role name)
├── 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": "[email protected]",
"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..."
}
},
"app_roles": {
"count": 3,
"files": {
"app_roles/metadata.json": "sha256:roles123..."
}
},
"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_app_roles": true,
"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)
  • app_roles/metadata.json: App roles (portable by role name; hierarchy via parent_name)
  • datatables/metadata.json: Array of data table schemas with Frictionless Table Schema definitions
  • functions/metadata.json: Array of function definitions with code and configuration
  • secrets/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 definitions
  • tags/metadata.json: Array of tag definitions with names and metadata
  • storage/metadata.json: Storage bucket configurations and file metadata
  • frontend_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:

FieldPolicy TypesDescription
policy_typeAllType: resource, role, or derived_role
nameAllPolicy identifier (without scope prefix)
entity_typeResource onlyResource type (e.g., datatable, storage, function)
rulesResource, RoleArray of permission rules
definitionsDerived Role onlyArray of role definitions with conditions
import_derived_rolesResource onlyDerived roles referenced by this policy
parent_rolesRole onlyRoles this role inherits from
variablesAllOptional policy variables
metadataAllOptional 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 CaseDescription
Audit & ComplianceTrack who exported/imported what app, when, for security and regulatory requirements
DebuggingIf an import fails or causes issues, review the job history to see what changed
Historical AnalysisUnderstand patterns - how often apps are exported, which imports had warnings
SupportHelp 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
tip

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"
}
],
"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.

Example

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 file
  • dry_run (optional, boolean): If true, 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=true for production imports)
  • If the app already exists, it will be updated; otherwise, it will be created

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 "[email protected]"

# Dry run (validation only, no changes)
curl -X POST https://your-tenant.taruvi.app/api/apps/imports/ \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "[email protected]" \
-F "dry_run=true"

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..."
}
}
}

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 slug
  • status (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)

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.

Example

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: Automatically materialized during import
    • Import currently materializes datatables (creates/updates physical tables) as part of the datatables module import
    • If you need schema-only imports without DDL, this may change in a future release via an import option
Cross-App References

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

TypeDescriptionExample
ResourceControls access to specific resources (datatables, storage, etc.)Allow read on datatable:users for role viewer
RoleDefines role hierarchies and permissionsadmin role inherits from editor
Derived RoleDynamic roles based on conditionsowner if user_id == resource.created_by
Principal Policies

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 owner become walmart_inventory_owner in 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:

  1. Export/import app structure first (without large storage)
  2. Use direct S3 sync or AWS CLI to transfer files
  3. Manually create Object records in database

Import Statistics

Storage import returns detailed statistics:

{
"created": 2,
"updated": 1,
"files_imported": 45,
"files_failed": 2,
"warnings": [
"Skipped file 'avatars/missing.jpg': file content missing from package"
]
}

Quota Handling

  • Bucket quotas are restored from export
  • quota_exceeded flag 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 "[email protected]"

# 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 "[email protected]"

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 "[email protected]"

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 "[email protected]"

- 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

  1. Package Integrity

    • ZIP file is valid and extractable
    • manifest.json exists and is valid JSON
  2. Manifest Validation

    • Contains required fields (version, app, export_timestamp)
    • App metadata is complete (slug, name)
    • Referenced files exist in package
  3. 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
  4. Frontend Workers

    • All referenced build archives exist
    • Build UUIDs match archive filenames
    • Worker slugs are valid format
    • Checksums match archive contents
  5. 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.

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

Solutions:

  1. Reduce build sizes: Optimize frontend builds before deployment
  2. Clean up old builds: Delete unused builds to reduce export size
  3. Increase timeout: Configure longer timeout in client
  4. Export during off-peak hours: Schedule exports when traffic is low

Import Fails with "Package Validation Failed"

Symptoms: Import rejected before processing

Solutions:

  1. Verify ZIP integrity: unzip -t app-export.zip
  2. Check manifest: unzip -p app-export.zip manifest.json | jq .
  3. Verify package structure: unzip -l app-export.zip

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.

Solutions:

# 1. List imported secrets
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/secrets/?app={app-slug}

# 2. 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"}'

# 3. Update function auth_config (if using authenticated webhooks)
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"}}'
Post-Import Checklist

After importing an app package, always:

  1. Update all secret values
  2. Update function auth_config for authenticated webhooks
  3. Verify API integrations work correctly

Worker Deployed but Not Accessible

Symptoms: Import succeeds but frontend worker URL returns 404

Solutions:

# 1. Check worker status
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/

# 2. Check build list
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/builds/

Also verify that active_build_uuid is set, and wait 1-5 minutes for CDN/DNS propagation.

Data Tables Not Created

Solutions:

# 1. Check app's data tables
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/

# 2. Materialize manually
curl -X POST https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/{table-name}/materialize/ \
-H "Authorization: Bearer YOUR_TOKEN"

App Already Exists

Behavior: The import uses create-or-update (upsert) semantics. This is usually the desired behavior - it allows you to re-import packages to update existing apps.

Storage Import Fails or Incomplete

Check import statistics for files_failed and warnings fields. For large storage buckets, export/import app structure separately and use AWS CLI to sync large files.

Storage Package Too Large

Cause: Package exceeds 100MB limit. Export different bucket categories separately, or increase limits via MAX_IMPORT_SIZE_BYTES in StorageHandler (admin only).

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

Export packages do NOT include:

  • Environment variables
  • API keys or tokens
  • Database credentials
  • OAuth client secrets

After import, configure:

  1. Environment-specific variables
  2. API credentials
  3. Third-party integrations
  4. 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

ResourceLimit
Maximum package size100MB
Maximum workers per appUnlimited
Maximum builds per workerUnlimited (but only active builds exported)
Maximum data tables per appUnlimited
Export timeout5 minutes
Import timeout10 minutes

Note: Limits are configurable by platform administrators.

API Response Codes

CodeDescription
200OK - Export/Import successful
400Bad Request - Invalid package or parameters
401Unauthorized - Authentication required
403Forbidden - Insufficient permissions
404Not Found - App not found
413Payload Too Large - Package exceeds size limit
500Internal Server Error - Server error occurred

Next Steps

Need help? Contact your Taruvi administrator or check the interactive API documentation at /api/docs/.