---
name: graphiant-push-device-config
description: Validate device readiness, submit a declarative configuration fragment,
  and resolve the returned job to confirm the change actually applied. This skill
  mutates production network state.
api: openapi/graphiant-portal-openapi-original.json
generated: '2026-08-01'
method: generated
risk: high
operations:
- getV1EdgesSummary
- getV1DevicesByDeviceIdInterfaces
- putV1DevicesByDeviceIdConfig
- getV1DevicesByDeviceIdJobsByJobId
- getV1DevicesByDeviceIdDraft
- postV1DevicesByDeviceIdDraft
- deleteV1DevicesByDeviceIdDraft
title: Push a Graphiant device configuration safely
---

# Push a Graphiant device configuration safely

**This skill changes production network configuration.** It has no idempotency key, it
is asynchronous, and a `null` value means *delete*. Require explicit human approval
before the `PUT` in step 3. Do not run this unattended.

Complete `graphiant-authenticate-and-scope.md` first and confirm
`permissions.networkConfiguration == "read_write"`.

## 1. Preflight — the device must be ready

`GET /v1/edges-summary`, find your device, and assert **both**:

- `portalStatus == "Ready"` (the device is in sync with the portal)
- `ttConnCount == 2` (the device has both tunnel-terminator connections to Graphiant)

If either check fails, stop. Graphiant documents this as a required precondition. A push
to a device that is not Ready will not land, and you will not get a synchronous error
telling you so.

## 2. Read the current shape

`GET /v1/devices/{deviceId}/interfaces` to see what exists before you change it.

## 3. Submit the desired state

`PUT /v1/devices/{deviceId}/config`

The body is a **partial desired-state document**, not a full replacement:

```json
{
  "edge": {
    "interfaces": {
      "GigabitEthernet8/0/0": {
        "interface": {
          "subinterfaces": {
            "18": {
              "interface": {
                "vlan": 18,
                "lan": "lan-7-test",
                "alias": "non_production",
                "description": "lan-7",
                "adminStatus": true,
                "ipv4": { "address": { "address": "10.2.7.1/24" } },
                "ipv6": { "address": { "address": "2001:10:2:7::1/64" } }
              }
            }
          }
        }
      }
    },
    "segments": {
      "lan-7-test": {
        "networks": [], "bgpRedistribution": {}, "bgpNeighbors": {},
        "syslogTargets": {}, "staticRoutes": {}, "dhcpSubnets": {},
        "bgpAggregations": {}, "ipfixExporters": {}
      }
    }
  },
  "description": "adding a subinterface",
  "configurationMetadata": { "name": "" }
}
```

### The two semantics you must not confuse

| You write | Graphiant does |
|---|---|
| key **omitted** | leave the existing value untouched |
| key set to **`null`** | **delete that element** |

Deleting subinterface 18:

```json
{"edge":{"interfaces":{"GigabitEthernet8/0/0":{"interface":{"subinterfaces":{"18":{"interface":null}}}}}},
 "description":"deleting a subinterface","configurationMetadata":{"name":""}}
```

A serialiser that emits absent optional fields as `null` will silently delete
configuration. Serialise omissions as *omissions*.

Always set a meaningful `description` — it is recorded against the job and is the only
audit trail a later reader gets.

Circuits are referenced from an interface **by name** (`"circuit": "c-gigabitethernet5-0-0"`)
and LAN segments **by name** (`"lan": "lan-7-test"`), not by id. The referenced object
must exist or be created in the same document.

## 4. Resolve the job — the response is not the outcome

The `PUT` returns HTTP 200 with:

```json
{ "jobId": 1552067 }
```

**200 means accepted, not applied.** Poll:

`GET /v1/devices/{deviceId}/jobs/{jobId}`

until it reaches a terminal state. Report the job outcome, never the HTTP status, as the
result of the change.

## 5. Retry discipline

There is **no `Idempotency-Key`** on this endpoint. Replaying the same desired-state
document converges to the same device state — that is a property of the declarative
model, not a provider guarantee — but each submission creates a *new job*.

Therefore: **never blind-retry a config PUT.** On a timeout or a 5xx, first list or poll
for a job that may already exist, and only resubmit if none does.

## Drafts

`/v1/devices/{deviceId}/draft` supports `GET`, `POST` and `DELETE`. Where a review step
is wanted, stage into a draft rather than pushing straight to `config`.

## Errors

`{"errorCode": …, "displayError": …, "detailedError": …}`. A `403` with
`displayError: "Token Expired"` mid-workflow is the dangerous case — the submit may have
succeeded before the token lapsed. Refresh (`GET /v1/auth/refresh`) and **poll for the
job**; do not resubmit.

See `conventions/graphiant-conventions.yml` and `errors/graphiant-problem-types.yml`.
