Skip to Content

GraphQL API - Services

The Services API allows you to query and manage various services within the Layer One platform. This is currently limited to order management for BIMIT Plan) (areaplan).

Order Management Schema

type Order { order_id: ID! order_name: String! created: DateTime! order_status: String! product: String! estimated_sqft: Int address: String model_units: String metadata: JSON } type UploadSession { asset_id: ID! order_id: ID! asset_type: String! upload_url: String expires_in: Int part_urls: [String!] upload_id: String upload_status: String! } enum OrderStatus { DRAFT INITIALIZED UPLOADING PROCESSING COMPLETED ERROR } enum ProductType { AREAPLAN } enum ModelUnits { METRIC IMPERIAL }

Order Management

The order management system allows you to place and track orders for processing services. The workflow involves:

  1. Initialize Order - Create a draft order with metadata
  2. Upload Assets - Upload required files via pre-signed URLs
  3. Finalize Upload - Confirm upload completion
  4. Finalize Order - Submit order for processing
  5. Query Order Status - Track progress and get results

Order Flow

  1. App sends post request with initial order metadata (initializeOrder)
  2. App uploads any reference/input files, API stores files and links them to order (see upload flow)
  3. App finalizes upload (confirms upload if single/multipart uploads) (finalizeUpload)
  4. App finalizes order, API confirms order details and files are valid (finalizeOrder)
  5. After finalizing, app can query for status updates or register a webhook to receive push notifications (Query Order, create Webhook)

Supported File Formats

Point Cloud Formats

  • E57 - Industry standard for 3D point clouds
  • XYZ - Simple ASCII point cloud format

File Size Limits

  • Single Upload: Up to 5GB
  • Multipart Upload: No limit (recommended for files > 5GB)

Upload Methods

  1. URL Method - Provide a download URL for the API to fetch
  2. Single Upload - Direct upload via presigned URL
  3. Multipart Upload - Split large files into chunks for parallel upload

Mutations

Initialize Order

Creates a draft order with initial metadata to begin the order process.

mutation InitializeOrder( $order_name: String! $product: String! $address: String $estimated_sqft: Int $model_units: String! ) { initializeOrder( order_name: $order_name product: $product address: $address estimated_sqft: $estimated_sqft model_units: $model_units ) { order_id order_name created product address model_units order_status metadata } }

Variables:

{ "order_name": "Test Order 3 URL", "product": "areaplan", "address": "123 Test St.", "estimated_sqft": 1234, "model_units": "Metric" }

Response:

{ "data": { "initializeOrder": { "order_id": "<order_id>", "created": "2021-01-26T17:19:19Z", "order_status": "Order Initialized", "order_name": "Test Project 1", "product": "areaplan", "estimated_sqft": 1234, "address": "123 Test St.", "model_units": "Metric", "metadata": "{\"project_id\":\"123\"}" } } }

Parameters

ParameterTypeDescriptionRequired
order_nameStringOrder name
productStringOnly “areaplan” is supported
addressStringOrder address (provides Geolocation services)Optional
estimated_sqftIntEstimated square feet of scanned spaceOptional
model_unitsStringUnit of measurement (Metric/Imperial)

Finalize Upload

Finalizes the upload session after assets have been uploaded via pre-signed URLs.

mutation FinalizeUpload( $asset_id: ID! $upload_id: String $parts: String ) { finalizeUpload( asset_id: $asset_id upload_id: $upload_id parts: $parts ) { asset_id order_id asset_type upload_url expires_in part_urls upload_id upload_status } }

Variables for Single File:

{ "asset_id": "c3bb498f-ee0c-4547-a364-4e702285917f", "upload_id": "e57", "parts": "url/single" }

Variables for Multipart Upload:

{ "asset_id": "c3bb498f-ee0c-4547-a364-4e702285917f", "upload_id": "multipart_upload_id", "parts": "url/multi" }

Response:

{ "data": { "finalizeUpload": { "asset_id": "c3bb498f-ee0c-4547-a364-4e702285917f", "order_id": "199cca76-172b-46b6-b59e-18e4ee8dd820", "asset_type": "xyz", "upload_url": "https://ip-public-docs.s3.us-east-1.amazonaws.com/test_pointcloud/small-test.xyz", "expires_in": null, "part_urls": null, "upload_id": null, "upload_status": "Upload Successful" } } }

Parameters

ParameterTypeDescriptionRequired
asset_idIDOrder ID
upload_idStringe57, xyz format identifier
partsStringurl/single/multi

Upload Flow Details

Single File Upload:

  • App sends a request to start an upload session (API expects xyz or e57 file input)
  • User decides on upload method: url, single, multi
  • If url method: supply a download url via the “url” param
  • If single: API will return a presigned url to upload the file (good for files up to 5 GB)

Multipart Upload:

  • If multi: API expects “total_parts” as an input, will return part_urls object(PartNumber, signedUrl)) and the upload_id
  • If multi: user needs to save PartNumber and Etag, and original upload_id to submit back to finalizeUpload endpoint

Finalize Order

After uploading and finalizing all assets, finalize the order to submit it for processing.

mutation FinalizeOrder($id: ID!) { finalizeOrder(id: $id) { order_id order_name created product order_status model_units } }

Variables:

{ "id": "199cca76-172b-46b6-b59e-18e4ee8dd820" }

Response:

{ "data": { "finalizeOrder": { "order_id": "199cca76-172b-46b6-b59e-18e4ee8dd820", "order_name": "Test Order 3 URL", "created": "2025-06-05T06:18:33.147Z", "product": "areaplan", "order_status": "Order Processing", "model_units": "Metric" } } }

Parameters

ParameterTypeDescriptionRequired
idIDOrder id

Queries

Get Order Status

Track the order’s progress by fetching the current status using the order ID. App has two ways of requesting the order status: querying for order details or via a webhook event.

query GetOrder($id: ID!) { order(order_id: $id) { order_id created order_name product order_status address metadata assets { asset_id asset_type } } }

Variables:

{ "id": "81cfe88b-a211-4531-be1e-680256f52f9f" }

Response:

{ "data": { "order": { "order_id": "81cfe88b-a211-4531-be1e-680256f52f9f", "created": "2025-06-05T04:11:21.869Z", "order_name": "Test Order 2 URL", "product": "areaplan", "order_status": null, "address": null, "metadata": "{\"project_id\":\"123\"}", "assets": [ { "asset_id": "7fdf84ac-b20d-454e-9dd7-8d88f51bbe87", "asset_type": "xyz" }, { "asset_id": "a9e270fd-c98c-4656-a030-41a9add8ea0b", "asset_type": "pdf" } ] } } }

Parameters

ParameterTypeDescriptionRequired
idIDOrder id

Get Orders by Status

Query orders filtered by status or other criteria.

query GetOrders($filter: OrderFilter, $pagination: PaginationInput) { orders(filter: $filter, pagination: $pagination) { edges { node { order_id order_name created order_status product estimated_sqft address } } pageInfo { hasNextPage hasPreviousPage startCursor endCursor } totalCount } }

Variables:

{ "filter": { "status": "PROCESSING", "product": "areaplan" }, "pagination": { "first": 20, "after": "cursor-string" } }

Complete Order Example

Here’s a complete workflow example using TypeScript:

// Step 1: Initialize Order const initializeOrderMutation = ` mutation InitializeOrder( $order_name: String! $product: String! $address: String $estimated_sqft: Int $model_units: String! ) { initializeOrder( order_name: $order_name product: $product address: $address estimated_sqft: $estimated_sqft model_units: $model_units ) { order_id order_name created order_status product address model_units } } `; const orderVariables = { order_name: "Building Scan Project", product: "areaplan", address: "123 Main Street, City, State", estimated_sqft: 2500, model_units: "Metric" }; const orderResponse = await graphqlClient.request(initializeOrderMutation, orderVariables); const orderId = orderResponse.initializeOrder.order_id; // Step 2: Upload Assets (via pre-signed URLs) // This would involve getting upload URLs and uploading files // Implementation depends on your file upload strategy // Step 3: Finalize Upload const finalizeUploadMutation = ` mutation FinalizeUpload( $asset_id: ID! $upload_id: String $parts: String ) { finalizeUpload( asset_id: $asset_id upload_id: $upload_id parts: $parts ) { asset_id order_id upload_status } } `; const uploadVariables = { asset_id: "your-asset-id", upload_id: "e57", parts: "url/single" }; const uploadResponse = await graphqlClient.request(finalizeUploadMutation, uploadVariables); // Step 4: Finalize Order const finalizeOrderMutation = ` mutation FinalizeOrder($id: ID!) { finalizeOrder(id: $id) { order_id order_name order_status created product } } `; const finalizeResponse = await graphqlClient.request(finalizeOrderMutation, { id: orderId }); console.log("Order finalized:", finalizeResponse.finalizeOrder.order_status); // Step 5: Query Order Status const getOrderQuery = ` query GetOrder($id: ID!) { order(order_id: $id) { order_id order_status created order_name product assets { asset_id asset_type } } } `; const statusResponse = await graphqlClient.request(getOrderQuery, { id: orderId }); console.log("Order Status:", statusResponse.order.order_status); console.log("Assets:", statusResponse.order.assets);

Error Handling

Common order-related errors and their solutions:

{ "errors": [ { "message": "Invalid product type", "extensions": { "code": "INVALID_PRODUCT", "supportedProducts": ["areaplan"] } }, { "message": "Order not found", "extensions": { "code": "ORDER_NOT_FOUND", "orderId": "invalid-order-id" } }, { "message": "Upload session expired", "extensions": { "code": "UPLOAD_EXPIRED", "assetId": "asset-123" } } ] }

Error Codes

  • INVALID_PRODUCT - Unsupported product type (only “areaplan” supported)
  • ORDER_NOT_FOUND - Order does not exist
  • UPLOAD_EXPIRED - Upload session has expired
  • UPLOAD_FAILED - File upload failed
  • INSUFFICIENT_PERMISSIONS - User lacks permission for operation
  • MISSING_POINTCLOUD - Point cloud data is missing or not provided
  • INCOMPLETE_UPLOAD - File upload was not completed successfully
  • UNSUPPORTED_FORMAT - File format is not supported by the system (only e57, xyz supported)

Subscriptions

Order Status Updates

Subscribe to real-time order status changes:

subscription OrderStatusUpdates($orderId: ID!) { orderStatusChanged(orderId: $orderId) { order_id order_status previousStatus changedAt progress { percentage currentStep estimatedCompletion } } }

Upload Progress

Monitor upload progress for large files:

subscription UploadProgress($assetId: ID!) { uploadProgressUpdated(assetId: $assetId) { asset_id upload_status progress { bytesUploaded totalBytes percentage } error { code message } } }

Best Practices

Order Management

  • Always initialize orders before uploading assets
  • Check file size limits - use multipart upload for files > 5GB
  • Handle upload timeouts - presigned URLs have expiration times
  • Monitor order status via subscriptions or polling
  • Implement retry logic for failed uploads

File Upload Optimization

const uploadFile = async (file: File, presignedUrl: string) => { const maxRetries = 3; let attempt = 0; while (attempt < maxRetries) { try { const response = await fetch(presignedUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } }); if (response.ok) { return true; } throw new Error(`Upload failed with status: ${response.status}`); } catch (error) { attempt++; if (attempt === maxRetries) { throw error; } // Exponential backoff await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); } } };

Multipart Upload Management

const uploadLargeFile = async (file: File, partUrls: Array<{PartNumber: number, signedUrl: string}>) => { const chunkSize = 5 * 1024 * 1024; // 5MB chunks const parts = []; for (const partInfo of partUrls) { const start = (partInfo.PartNumber - 1) * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); const response = await fetch(partInfo.signedUrl, { method: 'PUT', body: chunk }); if (response.ok) { const etag = response.headers.get('ETag'); parts.push({ PartNumber: partInfo.PartNumber, ETag: etag }); } } return parts; };

Webhook Integration

Set up webhooks to receive real-time notifications about order status changes:

// Webhook payload example for order status change interface OrderWebhookPayload { event: 'order.status_changed'; timestamp: string; data: { order_id: string; order_status: string; previous_status: string; progress?: { percentage: number; currentStep: string; estimatedCompletion?: string; }; }; } // Express.js webhook handler app.post('/webhooks/orders', (req, res) => { const payload: OrderWebhookPayload = req.body; switch (payload.data.order_status) { case 'PROCESSING': console.log(`Order ${payload.data.order_id} started processing`); break; case 'COMPLETED': console.log(`Order ${payload.data.order_id} completed successfully`); // Download results, notify user, etc. break; case 'ERROR': console.log(`Order ${payload.data.order_id} failed processing`); // Handle error, notify user, retry logic, etc. break; } res.status(200).send('OK'); });
Last updated on