ERP Example Calls
This page provides copy-paste examples for the most common ERP integration calls. Use the cURL / Python selector on each example to view it in your preferred language. Replace the placeholder values with your actual endpoint and authentication token before running.
In every example below, replace:
YOUR_PICO_API_ENDPOINT— your Pico API GraphQL URL (e.g.https://yourcompany.picomes.com/graph/v2)YOUR_API_TOKEN— your Pico API authentication token (see Authentication)
The Python examples use the requests library (pip install requests). Selecting cURL or Python on any example switches every example on the page to that language.
1. List Operations
Operations are Pico's representation of your products and processes — they define what can be built or performed. When your ERP creates a work order to request that something be built, Pico represents that as an operation order. Query the available operations to discover what can be ordered. Deploying a product or process in Pico will create or update an operation.
The ERP Mapping UI flow in sections 7–9 can connect your ERP's products and operations to Pico's for you: ingest them, a Pico manager maps them in the UI, and you read back the mapped picoOperationId. Listing operations and matching in your own code (this section) remains fully supported — it's how many existing integrations work.
You can receive these updates in real time three ways: (a) subscribe to operationsStream over WebSocket — see the Subscriptions Guide for client setup; (b) consume the same subscription as a Server-Sent Events (SSE) stream by POSTing the subscription query with Accept: text/event-stream (example below); or (c) register a webhook with webhookSubscribe — see section 3 for the webhook-style pattern (the same responseFragment approach works with any subscription, including operationsStream).
Use the returned operation id as the operationId when saving an operation order in section 2.
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "{ operations { id externalId name updatedAt } }"
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={"query": "{ operations { id externalId name updatedAt } }"},
)
print(response.json())
SSE subscription example (real-time stream over HTTP):
- cURL
- Python
curl -N -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{"query": "subscription { operationsStream(cursor: { initialValue: { updatedAt: \"2024-01-01T00:00:00Z\" } }, batchSize: 100) { id externalId name updatedAt } }"}'
import json
import requests
with requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={
"x-pico-api-org": "YOUR_API_TOKEN",
"Accept": "text/event-stream",
},
json={"query": 'subscription { operationsStream(cursor: { initialValue: { updatedAt: "2024-01-01T00:00:00Z" } }, batchSize: 100) { id externalId name updatedAt } }'},
stream=True,
) as response:
for line in response.iter_lines(decode_unicode=True):
if line and line.startswith("data: "):
print(json.loads(line[6:]))
Expected response:
{
"data": {
"operations": [
{
"id": "op-abc-123",
"externalId": "PROD-101",
"name": "Main Assembly",
"updatedAt": "2025-01-15T10:00:00.000Z"
}
]
}
}
2. Save an Operation Order
Use the operationOrderSave mutation to create an operation order — it tells Pico to build a specific product for a specific order (e.g. a sales order from your ERP). ERPs often call this a "work order", but in Pico a work order is the smaller unit the operation order fans out into: one process run by an operator at a station, per the operation's designed workflow.
Required fields:
operationId— the Pico operation ID: the mappedpicoOperationIdread back in section 8, or matched yourself from section 1externalOrderId— your ERP's order identifier for tracking (technically optional in the schema, but strongly recommended for ERP integrations)
Optional fields:
stationIds/stationLineIds— limit where the order may be built: any listed station, or any station currently assigned to a listed station line (an ERP "work center" typically maps to a Pico station line). Leaving both empty keeps the order buildable at any station running the operation. IDs are validated at save; station-line assignment changes apply to existing orders immediately. Look the IDs up with thestationLinesquery — see section 10.- See
OperationOrderSaveInputfor all available fields.
For example, to restrict the order below to one station line, add to the input:
{ "stationLineIds": ["your-station-line-id"] }
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "mutation SaveOrder($input: OperationOrderSaveInput!) { operationOrderSave(input: $input) { message } }",
"variables": {
"input": {
"operationId": "your-operation-id",
"externalOrderId": "ERP-ORDER-123"
}
}
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={
"query": "mutation SaveOrder($input: OperationOrderSaveInput!) { operationOrderSave(input: $input) { message } }",
"variables": {
"input": {
"operationId": "your-operation-id",
"externalOrderId": "ERP-ORDER-123",
}
},
},
)
print(response.json())
Expected response:
{
"data": {
"operationOrderSave": {
"message": "created"
}
}
}
If an operation order with the same externalOrderId and operationId already exists, the response message will be "updated" instead of "created".
3. Subscribe to Operation Order Completion Webhook
Use the webhookSubscribe mutation to register a webhook that receives a POST request every time an operation order completes.
Required fields:
subscriptionID— a URL-safe identifier you choose for this subscription (used to unsubscribe later)responseFragment— a GraphQL fragment that describes which fields to include in the webhook payload (must inherit from one of the subscription response types)webhookUrl— the URL where Pico willPOSTcompletion events
Optional fields:
webhookHeaders— custom headers to include on every callback (e.g. for authenticating with your endpoint)
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "mutation Subscribe($input: SubscribeRequestInput!) { webhookSubscribe(input: $input) { subscriptionId webhookUrl createdAt } }",
"variables": {
"input": {
"subscriptionID": "erp-order-completions",
"responseFragment": "fragment OrderComplete on OperationOrderComplete { externalOrderId operation { id externalId name } orderIndex at operationSummary { startedAt consumedSerials { resultId attrId value } } endState { producedSerial } }",
"webhookUrl": "https://your-erp-system.com/api/pico-webhooks",
"webhookHeaders": {"X-API-Key": "your-erp-api-key"}
}
}
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={
"query": "mutation Subscribe($input: SubscribeRequestInput!) { webhookSubscribe(input: $input) { subscriptionId webhookUrl createdAt } }",
"variables": {
"input": {
"subscriptionID": "erp-order-completions",
"responseFragment": "fragment OrderComplete on OperationOrderComplete { externalOrderId operation { id externalId name } orderIndex at operationSummary { startedAt consumedSerials { resultId attrId value } } endState { producedSerial } }",
"webhookUrl": "https://your-erp-system.com/api/pico-webhooks",
"webhookHeaders": {"X-API-Key": "your-erp-api-key"},
}
},
},
)
print(response.json())
Expected response:
{
"data": {
"webhookSubscribe": {
"subscriptionId": "erp-order-completions",
"webhookUrl": "https://your-erp-system.com/api/pico-webhooks",
"createdAt": "2025-01-15T10:30:00.000Z"
}
}
}
Webhook Delivery Behavior
Successful delivery: Pico considers a webhook delivered when your endpoint returns a 2xx HTTP status code.
Failed delivery (non-2xx response): If your endpoint returns an HTTP status >= 400, Pico logs the failure. The current event delivery is skipped, but the subscription remains active and will attempt delivery for future events.
Connection retry: If the internal event stream disconnects, Pico automatically reconnects with a 5-second delay between attempts. Reconnection retries indefinitely until the subscription is explicitly unsubscribed.
Extended downtime: If your webhook endpoint is unreachable or consistently failing, events that occur during the outage will not be redelivered. The subscription continues to listen, and delivery resumes for new events once connectivity is restored.
4. Unsubscribe from a Webhook
Use the webhookUnsubscribe mutation with the subscriptionId you used when subscribing.
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "mutation Unsubscribe($id: String!) { webhookUnsubscribe(subscriptionId: $id) { success subscriptionId message } }",
"variables": {
"id": "erp-order-completions"
}
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={
"query": "mutation Unsubscribe($id: String!) { webhookUnsubscribe(subscriptionId: $id) { success subscriptionId message } }",
"variables": {"id": "erp-order-completions"},
},
)
print(response.json())
Expected response:
{
"data": {
"webhookUnsubscribe": {
"success": true,
"subscriptionId": "erp-order-completions",
"message": "Subscription cancelled"
}
}
}
5. List Active Webhook Subscriptions
To verify which webhooks are currently active, query webhookSubscriptions:
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "{ webhookSubscriptions { subscriptionId responseFragment webhookUrl createdAt lastEventAt } }"
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={"query": "{ webhookSubscriptions { subscriptionId responseFragment webhookUrl createdAt lastEventAt } }"},
)
print(response.json())
Expected response:
{
"data": {
"webhookSubscriptions": [
{
"subscriptionId": "erp-order-completions",
"responseFragment": "fragment OrderComplete on OperationOrderComplete { externalOrderId ... }",
"webhookUrl": "https://your-erp-system.com/api/pico-webhooks",
"createdAt": "2025-01-15T10:30:00.000Z",
"lastEventAt": "2025-01-15T14:22:00.000Z"
}
]
}
}
6. Example Webhook Payload
When an operation order completes, Pico sends a POST request to your webhookUrl with the fields specified in your responseFragment. Below is an example payload based on the subscription created in section 3:
{
"externalOrderId": "ERP-ORDER-123",
"operation": {
"id": "op-abc-123",
"externalId": "PROD-101",
"name": "Main Assembly"
},
"orderIndex": 0,
"at": "2025-01-15T14:22:00.000Z",
"operationSummary": {
"startedAt": "2025-01-15T14:00:00.000Z",
"consumedSerials": [
{
"resultId": 84213,
"attrId": "PROD-101-attr-serial-1",
"value": "SN-20250115-001"
},
{
"resultId": 84217,
"attrId": "PROD-101-attr-serial-2",
"value": "SN-20250115-042"
}
]
},
"endState": {
"producedSerial": "ASSY-20250115-007"
}
}
Payload Field Reference
| Field | Description |
|---|---|
externalOrderId | The ERP order identifier you provided when creating the operation order |
operation.id | Pico's internal operation identifier |
operation.externalId | The part number configured in Pico |
operation.name | Human-readable operation name |
orderIndex | 0-based index when multiple of the same operation are on the same order |
at | ISO 8601 timestamp of when the order was completed |
operationSummary.startedAt | When the first process in the operation began |
operationSummary.consumedSerials | Materials consumed during the build (resolve attrId via upfront operation mapping) |
endState.producedSerial | Serial number of the produced unit (if captured) |
7. Ingest ERP Operations for Mapping
Sections 7–9 cover the ERP Mapping UI flow: your ERP pushes its own products and operations (and the serialized materials each consumes) into Pico, a Pico manager maps them to the matching Pico operations in the ERP Mapping UI, and you read the result back to place orders. (The operations-query matching approach in section 1 remains fully supported as a self-managed alternative.) Use the erpOperationsSave mutation to create or update these ERP operations in batch.
Fields per operation:
id— your ERP's immutable identifier for the operation (primary key; re-sending the sameidupdates it)name— display name shown to the mapping managerexternalId— your public-facing code / part number (the match key)externalRevision— version/revision string (optional)subs— child sub-operations, each{ id, quantity }, conveying your operation hierarchy with an instance count per child (optional)hasPlannedOrder— whether your ERP places orders against this operation. Only operations with a planned order get a mapping decision (see section 8). An operation without a planned order carries no decision in isolation — it is mapped only in the context of an operation with a planned order, as a sub-operation or a group of work, and then producesoperationOrderProgressevents through that operation's orders.consumedSerials— the serialized materials this operation consumes, each with its ownid,name,externalId, andquantity(optional)
The batch below sends two operations: ERP-OP-1001 with two children conveyed via subs, and ERP-OP-1020 — one of those children — also in the batch as its own operation with its own planned order (a subassembly that is both a sub-operation of ERP-OP-1001 and independently ordered). The other child, ERP-OP-1010, would be ingested the same way with hasPlannedOrder: false.
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "mutation IngestERPOperations($input: ERPOperationsSaveInput!) { erpOperationsSave(input: $input) { message } }",
"variables": {
"input": {
"operations": [
{
"id": "ERP-OP-1001",
"name": "Frame Weld Assembly",
"externalId": "FRAME-WELD",
"externalRevision": "B",
"hasPlannedOrder": true,
"subs": [{ "id": "ERP-OP-1010", "quantity": 1 }, { "id": "ERP-OP-1020", "quantity": 1 }],
"consumedSerials": [
{ "id": "ERP-CS-1", "name": "Down Tube", "externalId": "TUBE-DOWN", "quantity": 1 },
{ "id": "ERP-CS-2", "name": "Head Tube", "externalId": "TUBE-HEAD", "quantity": 1 }
]
},
{
"id": "ERP-OP-1020",
"name": "Headset Press-Fit",
"externalId": "HEADSET-PRESS",
"externalRevision": "B",
"hasPlannedOrder": true,
"subs": [],
"consumedSerials": [
{ "id": "ERP-CS-3", "name": "Headset Bearing Set", "externalId": "BEARING-SET", "quantity": 2 }
]
}
]
}
}
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={
"query": "mutation IngestERPOperations($input: ERPOperationsSaveInput!) { erpOperationsSave(input: $input) { message } }",
"variables": {
"input": {
"operations": [
{
"id": "ERP-OP-1001",
"name": "Frame Weld Assembly",
"externalId": "FRAME-WELD",
"externalRevision": "B",
"hasPlannedOrder": True,
"subs": [{ "id": "ERP-OP-1010", "quantity": 1 }, { "id": "ERP-OP-1020", "quantity": 1 }],
"consumedSerials": [
{"id": "ERP-CS-1", "name": "Down Tube", "externalId": "TUBE-DOWN", "quantity": 1},
{"id": "ERP-CS-2", "name": "Head Tube", "externalId": "TUBE-HEAD", "quantity": 1},
],
},
{
"id": "ERP-OP-1020",
"name": "Headset Press-Fit",
"externalId": "HEADSET-PRESS",
"externalRevision": "B",
"hasPlannedOrder": True,
"subs": [],
"consumedSerials": [
{"id": "ERP-CS-3", "name": "Headset Bearing Set", "externalId": "BEARING-SET", "quantity": 2},
],
},
]
}
},
},
)
print(response.json())
Expected response:
{
"data": {
"erpOperationsSave": {
"message": "created"
}
}
}
Re-sending an operation with an id that already exists updates it in place. Send your operations in batches rather than one call per operation.
8. Read Back the Operation Mapping
Once a Pico manager has mapped your ingested operations in the UI, read the result with the erpOperations query. Fetch a single operation by your ERP-assigned id with where (as below, or filter by externalId); without arguments the query returns all of your ERP operations. Typical timing: call it just before creating an operation order (to resolve the current mapping) or when processing a completion event.
hasPlannedOrder— echoed back exactly as you ingested it (see section 7). Important for reading the result: only operations with a planned order carry a mapping decision and the fields below.mappings— the Pico operation mappings, one entry per mapped operation. The entry whoseoperationIdequals the operation's own id is its self mapping; any other entries map sub-operations in the context of this operation (optional operations, or sub-operation completion/progress events). Each entry'spicoOperationIdnames the target Pico operation.neverMappedReason/neverMappedAt— set when a manager marked the operation never-to-be-mapped instead of mapping it.neverMappedReasonis a stable key (e.g.notBuiltInPico), not display text.orderedViaSubOps— true when the operation is ordered via its sub-operations rather than mapped directly (a specialized case — see below).consumedSerials— echoed back as ingested (slim:id,externalId,name,quantity). Their Pico allocations are not nested here — they're reported inconsumedSerialMappingsbelow.consumedSerialMappings— the consumed-serial allocations, each keyed back to a serial byconsumedSerialIdand allocating it to a Pico attribute marked as a consumed serial (picoAttrId+picoProcessId) with aquantity.
Each operation with a planned order is in one of three states: mapped (mappings has a self entry — pass its picoOperationId to operationOrderSave), never-mapped (neverMappedReason / neverMappedAt set), or pending (neither yet).
Allocations can absorb the subtree: a consumed serial may physically belong to any operation in the subtree, and because allocations are keyed by consumedSerialId on the operation (not nested on the serial), a parent can map everything for its subtree (see ERP-CS-3 below). Drilling in and mapping a sub-operation with its own planned order independently is also valid — parent-level mapping is simply sufficient.
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "{ erpOperations(where: { id: { _eq: \"ERP-OP-1001\" } }) { id name externalId externalRevision subs { id quantity } hasPlannedOrder orderedViaSubOps mappings { operationId picoOperationId mappedAt } neverMappedReason neverMappedAt consumedSerials { id externalId name quantity } consumedSerialMappings { consumedSerialId picoAttrId picoProcessId quantity mappedAt } } }"
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={"query": '{ erpOperations(where: { id: { _eq: "ERP-OP-1001" } }) { id name externalId externalRevision subs { id quantity } hasPlannedOrder orderedViaSubOps mappings { operationId picoOperationId mappedAt } neverMappedReason neverMappedAt consumedSerials { id externalId name quantity } consumedSerialMappings { consumedSerialId picoAttrId picoProcessId quantity mappedAt } } }'},
)
print(response.json())
Expected response:
{
"data": {
"erpOperations": [
{
"id": "ERP-OP-1001",
"name": "Frame Weld Assembly",
"externalId": "FRAME-WELD",
"externalRevision": "B",
"subs": [{ "id": "ERP-OP-1010", "quantity": 1 }, { "id": "ERP-OP-1020", "quantity": 1 }],
"hasPlannedOrder": true,
"orderedViaSubOps": false,
"mappings": [
{
"operationId": "ERP-OP-1001",
"picoOperationId": "op-frame-weld",
"mappedAt": "2025-01-15T10:00:00.000Z"
},
{
"operationId": "ERP-OP-1010",
"picoOperationId": "op-frame-inspect",
"mappedAt": "2025-01-15T10:02:00.000Z"
}
],
"neverMappedReason": null,
"neverMappedAt": null,
"consumedSerials": [
{ "id": "ERP-CS-1", "externalId": "TUBE-DOWN", "name": "Down Tube", "quantity": 1 },
{ "id": "ERP-CS-2", "externalId": "TUBE-HEAD", "name": "Head Tube", "quantity": 1 },
{ "id": "ERP-CS-3", "externalId": "BEARING-SET", "name": "Headset Bearing Set", "quantity": 2 }
],
"consumedSerialMappings": [
{
"consumedSerialId": "ERP-CS-1",
"picoAttrId": "attr-downtube",
"picoProcessId": "proc-frame-weld",
"quantity": 1,
"mappedAt": "2025-01-15T10:05:00.000Z"
},
{
"consumedSerialId": "ERP-CS-2",
"picoAttrId": "attr-headtube",
"picoProcessId": "proc-frame-weld",
"quantity": 1,
"mappedAt": "2025-01-15T10:05:00.000Z"
},
{
"consumedSerialId": "ERP-CS-3",
"picoAttrId": "attr-bearing",
"picoProcessId": "proc-frame-weld",
"quantity": 2,
"mappedAt": "2025-01-15T10:06:00.000Z"
}
]
}
]
}
}
Walking the example
- The self mapping — the
mappingsentry withoperationId: "ERP-OP-1001"— points atop-frame-weld. ThatpicoOperationIdis what you pass tooperationOrderSavein section 9. - The in-context entry maps the sub-operation
ERP-OP-1010(which has no planned order of its own) toop-frame-inspect— use it to correlate that sub-operation'soperationOrderProgressevents. ERP-CS-3is absorbed from the subtree: it physically belongs to the sub-operationERP-OP-1020, but its allocation is carried here at the parent.ERP-OP-1020has its own planned order — fetch it by its own id when you order it independently.
Reading the mapping states
| State | How to detect it |
|---|---|
| Mapped | hasPlannedOrder: true; mappings contains a self entry (operationId == the operation's id). May also carry in-context sub-op entries plus consumedSerials / consumedSerialMappings. |
| Never-mapped | hasPlannedOrder: true; mappings empty; neverMappedReason set (e.g. notBuiltInPico); neverMappedAt set. |
| Ordered via sub-operations | hasPlannedOrder: true; orderedViaSubOps: true; mappings empty — expect mappings and completions at its sub-operations instead (see below). |
| Pending (undecided) | hasPlannedOrder: true; mappings empty; neverMapped* null; orderedViaSubOps: false. |
| No planned order | hasPlannedOrder: false, as ingested — carries no mapping decision or serials of its own; it may still appear in a parent's in-context mappings. |
Ordered via sub-operations
A specialized case worth knowing once the basics are in place: orderedViaSubOps: true marks an operation with a planned order that is ordered via its sub-operations instead of being mapped directly: Pico builds its work through the operation's own sub-operations with planned orders — each of those maps on its own and carries its own consumed serials. When you see the flag, expect mappings — and completions — at the sub-operations rather than at the top-level operation.
Each consumedSerialMappings entry is one Pico target for one ERP serial, identified by consumedSerialId (which may belong to a sub-operation in the planned-order operation's subtree). picoAttrId is the target Pico attribute (marked as a consumed serial) and picoProcessId disambiguates the process variant; quantity is how much of the serial's total was allocated to that target. A serial can be split across several targets (several entries with the same consumedSerialId) or be left unallocated (no entry).
9. Create the Work Order from the Mapped Operation
Take the operation's self mapping from section 8 — the mappings entry whose operationId equals the ordered operation's own id — and pass its picoOperationId as the operationId on operationOrderSave to order the build. This closes the loop: your ERP ingests its operations, a manager maps them, and your ERP orders builds against the mapped Pico operation without hard-coding Pico ids.
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "mutation SaveOrder($input: OperationOrderSaveInput!) { operationOrderSave(input: $input) { message } }",
"variables": {
"input": {
"operationId": "op-abc-123",
"externalOrderId": "ERP-ORDER-123"
}
}
}'
import requests
# operation_id is the self mapping's picoOperationId from erpOperations (section 8):
# the mappings entry whose operationId equals the operation's own id
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={
"query": "mutation SaveOrder($input: OperationOrderSaveInput!) { operationOrderSave(input: $input) { message } }",
"variables": {
"input": {
"operationId": "op-abc-123",
"externalOrderId": "ERP-ORDER-123",
}
},
},
)
print(response.json())
Only map-complete operations should be ordered: skip any operation with a planned order but no self mapping (still pending, ordered via sub-operations, or never-mapped). Operations without a planned order (hasPlannedOrder: false) have no mapping to order against in the first place — they're structural, not something your ERP orders directly. For an operation ordered via sub-operations, order its mapped sub-operations (the ones with their own planned orders) instead. See section 2 for the full set of operationOrderSave options.
10. List Station Lines and Their Stations
Use the stationLines query to discover the IDs accepted by the stationIds and stationLineIds restrictions in section 2. Each station is returned with its name, nested under the line it is assigned to — an ERP "work center" typically maps to a Pico station line.
- cURL
- Python
curl -X POST YOUR_PICO_API_ENDPOINT \
-H "Content-Type: application/json" \
-H "x-pico-api-org: YOUR_API_TOKEN" \
-d '{
"query": "query { stationLines { lines { id name stations { id name } } unassignedStations { id name } } }"
}'
import requests
response = requests.post(
"YOUR_PICO_API_ENDPOINT",
headers={"x-pico-api-org": "YOUR_API_TOKEN"},
json={
"query": "query { stationLines { lines { id name stations { id name } } unassignedStations { id name } } }"
},
)
print(response.json())
Example response:
{
"data": {
"stationLines": {
"lines": [
{
"id": "128d1d99-2211-4179-b1d2-0be106474deb",
"name": "Line 1",
"stations": [
{ "id": "cudpi3d7u6gc6o0e5un0", "name": "Assembly 1" }
]
},
{
"id": "a26d27cc-3a1a-407d-94e9-4336b677b39f",
"name": "Line 2",
"stations": null
}
],
"unassignedStations": [
{ "id": "d03s5c57u6gc1agkasa0", "name": "Rework" }
]
}
}
}
Notes:
- Every station appears exactly once — either under the line it is assigned to, or in
unassignedStations. - A line with no stations assigned yet returns
nullforstations, asLine 2does above. - Station line assignments are read live, so a station moved to a different line in the Pico manage UI is reflected on the next call and immediately changes which orders that station can build.
See Also
- Work Order Integration Guide — End-to-end walkthrough of the ERP integration pattern
- Subscriptions Guide — Real-time event streaming with WebSocket subscriptions
- Authentication — How to obtain and use API tokens
- OperationOrderSave Mutation — Full mutation reference
- erpOperationsSave Mutation — Ingest ERP operations for mapping
- erpOperations Query — Read back the operation mapping
- stationLines Query — Station lines with their stations, for the order station restrictions
- webhookSubscribe Mutation — Full webhook subscription reference
- webhookUnsubscribe Mutation — Full unsubscribe reference