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
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)
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=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.
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:
- REST API
- Python
- JavaScript
curl -X GET https://your-tenant.taruvi.app/api/apps/blog-app/packages/export-config/ \
-H "Authorization: Bearer YOUR_TOKEN"
import requests
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/export-config/",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
config = response.json()
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/export-config/",
{
headers: { "Authorization": "Bearer YOUR_TOKEN" }
}
);
const config = await response.json();
The response includes:
order: ordered list ofinclude_*keysoptions: 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 checksumsapp/metadata.json: App configurationapp_roles/metadata.json: App roles (portable by role name; hierarchy viaparent_name)datatables/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
- REST API
- Python
- JavaScript
# 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
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# Export an app with all modules (default)
response = requests.post(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/",
headers={**headers, "Content-Type": "application/json"}
)
with open("blog-app-export.zip", "wb") as f:
f.write(response.content)
# Export without storage (only app structure)
response = requests.post(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/",
headers={**headers, "Content-Type": "application/json"},
json={"include_storage": False}
)
with open("blog-app-export.zip", "wb") as f:
f.write(response.content)
const headers = {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
};
// Export an app with all modules (default)
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/",
{ method: "POST", headers }
);
const blob = await response.blob();
// Export without storage (only app structure)
const structureResponse = await fetch(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/",
{
method: "POST",
headers,
body: JSON.stringify({ include_storage: false })
}
);
const structureBlob = await structureResponse.blob();
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": "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..."
}
},
"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 viaparent_name)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
- REST API
- Python
- JavaScript
# 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
import requests
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/1",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
with open("blog-app-package.zip", "wb") as f:
f.write(response.content)
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/1",
{
headers: { "Authorization": "Bearer YOUR_TOKEN" }
}
);
const blob = await response.blob();
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"
}
],
"total": 2,
"page": 1,
"page_size": 10
}
Example
- REST API
- Python
- JavaScript
# 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"
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# List all export jobs
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/",
headers=headers
)
# List only completed exports
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/",
headers=headers,
params={"status": "completed"}
)
jobs = response.json()
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// List all export jobs
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/",
{ headers }
);
const jobs = await response.json();
// List only completed exports
const completedResponse = await fetch(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/?status=completed",
{ headers }
);
Get Export Job Details
Endpoint
GET /api/apps/{app_slug}/packages/{uuid}/
Authentication
Requires JWT authentication with appropriate permissions.
Example
- REST API
- Python
- JavaScript
curl -X GET https://your-tenant.taruvi.app/api/apps/blog-app/packages/550e8400-e29b-41d4-a716-446655440000/ \
-H "Authorization: Bearer YOUR_TOKEN"
import requests
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/550e8400-e29b-41d4-a716-446655440000/",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
job = response.json()
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/blog-app/packages/550e8400-e29b-41d4-a716-446655440000/",
{
headers: { "Authorization": "Bearer YOUR_TOKEN" }
}
);
const job = await response.json();
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
Example
- REST API
- Python
- JavaScript
# 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"
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# Standard import
response = requests.post(
"https://your-tenant.taruvi.app/api/apps/imports/",
headers=headers,
files={"file": open("blog-app-export.zip", "rb")}
)
result = response.json()
# Dry run (validation only)
response = requests.post(
"https://your-tenant.taruvi.app/api/apps/imports/",
headers=headers,
files={"file": open("blog-app-export.zip", "rb")},
data={"dry_run": "true"}
)
preview = response.json()
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// Standard import
const formData = new FormData();
formData.append("file", blogAppExportZip);
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/imports/",
{ method: "POST", headers, body: formData }
);
const result = await response.json();
// Dry run (validation only)
const dryRunForm = new FormData();
dryRunForm.append("file", blogAppExportZip);
dryRunForm.append("dry_run", "true");
const dryRunResponse = await fetch(
"https://your-tenant.taruvi.app/api/apps/imports/",
{ method: "POST", headers, body: dryRunForm }
);
const preview = await dryRunResponse.json();
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 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)
Example
- REST API
- Python
- JavaScript
# 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"
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# List all import jobs
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/",
headers=headers
)
# List only completed imports
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/",
headers=headers,
params={"status": "completed"}
)
# Filter by app slug
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/",
headers=headers,
params={"target_app_slug": "blog-app"}
)
jobs = response.json()
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// List all import jobs
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/",
{ headers }
);
// List only completed imports
const completedResponse = await fetch(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/?status=completed",
{ headers }
);
// Filter by app slug
const filteredResponse = await fetch(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/?target_app_slug=blog-app",
{ headers }
);
const jobs = await filteredResponse.json();
Get Import Job Details
Endpoint
GET /api/apps/imports/jobs/{uuid}/
Authentication
Requires JWT authentication with appropriate permissions.
Example
- REST API
- Python
- JavaScript
curl -X GET https://your-tenant.taruvi.app/api/apps/imports/jobs/550e8400-e29b-41d4-a716-446655440000/ \
-H "Authorization: Bearer YOUR_TOKEN"
import requests
response = requests.get(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/550e8400-e29b-41d4-a716-446655440000/",
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
job = response.json()
const response = await fetch(
"https://your-tenant.taruvi.app/api/apps/imports/jobs/550e8400-e29b-41d4-a716-446655440000/",
{
headers: { "Authorization": "Bearer YOUR_TOKEN" }
}
);
const job = await response.json();
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
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:
- REST API
- Python
- JavaScript
# 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
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# Get the worker details
response = requests.get(
"https://your-tenant.taruvi.app/api/frontend_workers/admin-portal/",
headers=headers
)
# Update to your preferred subdomain
response = requests.patch(
"https://your-tenant.taruvi.app/api/frontend_workers/admin-portal/",
headers={**headers, "Content-Type": "application/json"},
json={"subdomain_input": "my-admin"}
)
# Result: Worker now accessible at dev-my-admin.taruvi.app
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// Get the worker details
const worker = await fetch(
"https://your-tenant.taruvi.app/api/frontend_workers/admin-portal/",
{ headers }
).then(r => r.json());
// Update to your preferred subdomain
await fetch(
"https://your-tenant.taruvi.app/api/frontend_workers/admin-portal/",
{
method: "PATCH",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ 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,
"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_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:
- REST API
- Python
- JavaScript
# 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"}'
import requests
# 1. Export from development
dev_response = requests.post(
"https://dev.yourapp.com/api/apps/blog-app/packages/",
headers={"Authorization": f"Bearer {DEV_TOKEN}"}
)
with open("blog-app.zip", "wb") as f:
f.write(dev_response.content)
# 2. Import to production
prod_response = requests.post(
"https://prod.yourapp.com/api/apps/imports/",
headers={"Authorization": f"Bearer {PROD_TOKEN}"},
files={"file": open("blog-app.zip", "rb")}
)
# 3. Materialize data tables
requests.post(
"https://prod.yourapp.com/api/apps/blog-app/datatables/posts/materialize/",
headers={"Authorization": f"Bearer {PROD_TOKEN}"}
)
# 4. Update frontend worker subdomains
requests.patch(
"https://prod.yourapp.com/api/frontend_workers/admin-portal/",
headers={"Authorization": f"Bearer {PROD_TOKEN}", "Content-Type": "application/json"},
json={"subdomain_input": "admin"}
)
// 1. Export from development
const devResponse = await fetch(
"https://dev.yourapp.com/api/apps/blog-app/packages/",
{ method: "POST", headers: { "Authorization": `Bearer ${DEV_TOKEN}` } }
);
const exportBlob = await devResponse.blob();
// 2. Import to production
const importForm = new FormData();
importForm.append("file", exportBlob, "blog-app.zip");
const prodResponse = await fetch(
"https://prod.yourapp.com/api/apps/imports/",
{
method: "POST",
headers: { "Authorization": `Bearer ${PROD_TOKEN}` },
body: importForm
}
);
// 3. Materialize data tables
await fetch(
"https://prod.yourapp.com/api/apps/blog-app/datatables/posts/materialize/",
{ method: "POST", headers: { "Authorization": `Bearer ${PROD_TOKEN}` } }
);
// 4. Update frontend worker subdomains
await fetch(
"https://prod.yourapp.com/api/frontend_workers/admin-portal/",
{
method: "PATCH",
headers: { "Authorization": `Bearer ${PROD_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ subdomain_input: "admin" })
}
);
Use Case 2: Application Templates
Create reusable application templates:
- REST API
- Python
- JavaScript
# 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"
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# 1. Create and configure template app
requests.post(
"https://your-tenant.taruvi.app/api/apps/",
headers={**headers, "Content-Type": "application/json"},
json={"name": "E-commerce Template", "slug": "ecommerce-template"}
)
# 2. Add data tables, frontend workers, etc.
# 3. Export template
export_resp = requests.post(
"https://your-tenant.taruvi.app/api/apps/ecommerce-template/packages/",
headers=headers
)
with open("ecommerce-template.zip", "wb") as f:
f.write(export_resp.content)
# 4. Import template
requests.post(
"https://your-tenant.taruvi.app/api/apps/imports/",
headers=headers,
files={"file": open("ecommerce-template.zip", "rb")}
)
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// 1. Create template app
await fetch("https://your-tenant.taruvi.app/api/apps/", {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ name: "E-commerce Template", slug: "ecommerce-template" })
});
// 2. Setup template...
// 3. Export template
const exportResp = await fetch(
"https://your-tenant.taruvi.app/api/apps/ecommerce-template/packages/",
{ method: "POST", headers }
);
const templateBlob = await exportResp.blob();
// 4. Import template
const importForm = new FormData();
importForm.append("file", templateBlob, "ecommerce-template.zip");
await fetch("https://your-tenant.taruvi.app/api/apps/imports/", {
method: "POST", headers, body: importForm
});
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.
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:
- 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
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
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:
- REST API
- Python
- JavaScript
# 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"}}'
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# 1. List imported secrets
secrets = requests.get(
"https://your-tenant.taruvi.app/api/secrets/?app={app-slug}",
headers=headers
).json()
# 2. Update each secret
requests.put(
"https://your-tenant.taruvi.app/api/secrets/{secret-key}/?app={app-slug}",
headers={**headers, "Content-Type": "application/json"},
json={"value": "your-actual-secret-value"}
)
# 3. Update function auth_config
requests.patch(
"https://your-tenant.taruvi.app/api/apps/{app-slug}/functions/{function-slug}/",
headers={**headers, "Content-Type": "application/json"},
json={"auth_config": {"type": "bearer", "token": "your-actual-token"}}
)
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// 1. List imported secrets
const secrets = await fetch(
"https://your-tenant.taruvi.app/api/secrets/?app={app-slug}",
{ headers }
).then(r => r.json());
// 2. Update each secret
await fetch(
"https://your-tenant.taruvi.app/api/secrets/{secret-key}/?app={app-slug}",
{
method: "PUT",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ value: "your-actual-secret-value" })
}
);
// 3. Update function auth_config
await fetch(
"https://your-tenant.taruvi.app/api/apps/{app-slug}/functions/{function-slug}/",
{
method: "PATCH",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ 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
Worker Deployed but Not Accessible
Symptoms: Import succeeds but frontend worker URL returns 404
Solutions:
- REST API
- Python
- JavaScript
# 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/
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# 1. Check worker status
worker = requests.get(
"https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/",
headers=headers
).json()
# 2. Check build list
builds = requests.get(
"https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/builds/",
headers=headers
).json()
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// 1. Check worker status
const worker = await fetch(
"https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/",
{ headers }
).then(r => r.json());
// 2. Check build list
const builds = await fetch(
"https://your-tenant.taruvi.app/api/frontend_workers/{worker-slug}/builds/",
{ headers }
).then(r => r.json());
Also verify that active_build_uuid is set, and wait 1-5 minutes for CDN/DNS propagation.
Data Tables Not Created
Solutions:
- REST API
- Python
- JavaScript
# 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"
import requests
headers = {"Authorization": "Bearer YOUR_TOKEN"}
# 1. Check app's data tables
tables = requests.get(
"https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/",
headers=headers
).json()
# 2. Materialize manually
requests.post(
"https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/{table-name}/materialize/",
headers=headers
)
const headers = { "Authorization": "Bearer YOUR_TOKEN" };
// 1. Check app's data tables
const tables = await fetch(
"https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/",
{ headers }
).then(r => r.json());
// 2. Materialize manually
await fetch(
"https://your-tenant.taruvi.app/api/apps/{app-slug}/datatables/{table-name}/materialize/",
{ method: "POST", headers }
);
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:
- 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/.