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:
- Initialize Order - Create a draft order with metadata
- Upload Assets - Upload required files via pre-signed URLs
- Finalize Upload - Confirm upload completion
- Finalize Order - Submit order for processing
- Query Order Status - Track progress and get results
Order Flow
- App sends post request with initial order metadata (
initializeOrder
) - App uploads any reference/input files, API stores files and links them to order (see upload flow)
- App finalizes upload (confirms upload if single/multipart uploads) (
finalizeUpload
) - App finalizes order, API confirms order details and files are valid (
finalizeOrder
) - 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
- URL Method - Provide a download URL for the API to fetch
- Single Upload - Direct upload via presigned URL
- 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
Parameter | Type | Description | Required |
---|---|---|---|
order_name | String | Order name | ✓ |
product | String | Only “areaplan” is supported | ✓ |
address | String | Order address (provides Geolocation services) | Optional |
estimated_sqft | Int | Estimated square feet of scanned space | Optional |
model_units | String | Unit 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
Parameter | Type | Description | Required |
---|---|---|---|
asset_id | ID | Order ID | ✓ |
upload_id | String | e57, xyz format identifier | ✓ |
parts | String | url/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
Parameter | Type | Description | Required |
---|---|---|---|
id | ID | Order 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
Parameter | Type | Description | Required |
---|---|---|---|
id | ID | Order 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 existUPLOAD_EXPIRED
- Upload session has expiredUPLOAD_FAILED
- File upload failedINSUFFICIENT_PERMISSIONS
- User lacks permission for operationMISSING_POINTCLOUD
- Point cloud data is missing or not providedINCOMPLETE_UPLOAD
- File upload was not completed successfullyUNSUPPORTED_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');
});