---
title: "Publish an owned node's app, or make it private again"
method: POST
path: "/api/nodes/{node_id}/shares/{service_name}/access"
tags: ["nodes"]
---

# Publish an owned node's app, or make it private again

`POST /api/nodes/{node_id}/shares/{service_name}/access`

## Request

- Base URL: `https://app.nerdit.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://app.nerdit.ai/api/nodes/{node_id}/shares/{service_name}/access`
- Auth: none declared

Publish one app on an owned machine, or make it private again.

The change is written on the **node** — this cloud stores no share state
beyond the access rules of ADR-W9 — so the call is one bounded ``PUT``
down the tunnel, and the answer is
the share as the node now reports it: the same row shape
``GET …/shares`` lists, so a console can redraw what it just changed
without re-reading the card.

Refused, in this order: a node that is not the caller's own (``404``); a
name outside the service grammar (``422``); an account that is suspended
or being deleted (``403``); a node that is offline (``409``).
Past that the node's own refusal is forwarded with
its own sentence and its ``daemon_code`` in ``details``.

## Path parameters

- `service_name` string, required
- `node_id` string, uuid, required

## Request body

- ShareAccessRequest: The owner's exposure decision for ONE app (X16: public shares are Pro). Only ``access``, and deliberately not the daemon's second field. ``nerditd``'s ``PUT /api/services/{name}/share`` also takes ``consent: bool``, which it requires (with, or instead of, a ``[deploy].edge_auth`` block) before it will accept ``public`` — its own guard against an operator publishing something by accident from a script. That flag is **not** re-exposed here, because on this path it would be a field whose only honest value is the one the caller already sent: reaching this route at all means an authenticated owner sent a same-origin, CSRF-bearing POST whose entire body is the word ``public``, and the console puts a confirm dialog naming the app in front of it ("Anyone with the link can open <app>"). There is no accidental caller to protect. Forwarding ``consent`` unconditionally would be different and wrong — the route sends it only on ``public``, where it means what the person did.
  - `preserve_existing` boolean
  - `access` 'private' | 'public', required

## Response `200`

Successful Response

- HostedShareView: One shared service on one node, as the node itself reports it. Every field but ``open_url`` is the daemon's answer passed through, because the daemon is the only holder of the share itself (D-P26-H1; the cloud keeps only who else may open a private one, ADR-W9): ``access`` is the stored exposure mode, ``state`` is the daemon's machine token for whether the share can serve *now*, and ``url`` is the address the daemon composed from the domain this cloud told it about. ``open_url`` is the one thing the cloud adds: where the owner's browser goes to be let in, which is a ticket this deployment mints and therefore a path on this deployment.
  - `service_name` string, required
  - `access` 'private' | 'public', required
  - `state` 'ready' | 'link_down' | 'not_entitled' | 'pending', required
  - `url` string, nullable, required
  - `open_url` string, required

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not Found
- `409`: Conflict
- `422`: Unprocessable Content
- `502`: Bad Gateway

---

[API](https://skmtc.dev/nerdit/apis/nerdit-cloud-web.md) · [All operations](https://skmtc.dev/nerdit/apis/nerdit-cloud-web/llms.txt) · [OpenAPI document](https://skmtc.dev/nerdit/apis/nerdit-cloud-web/revisions/06e3b44b58be?raw)
