# App submissions

> Publish a payload to an AnyRouter app, such as an Explain figure, and manage what you published.


# App submissions

Publish a payload to an app with one request. Any valid AnyRouter API key (`sk-ar-...`) or signed-in session works. Explain is the first app: `app` is `explain`.

## Read the schema

`GET /api/v1/apps/{app}/schema` needs no key. It returns `json_schema`, `version`, `instructions`, `example`, `limits` and `default_visibility`. Validate your payload against `json_schema` before you send it. The JSON Schema covers types and lengths only; the server also checks each app's own rules (for Explain, see [Publish an Explain figure](/docs/guides/publish-explain-figure.md)).

## Check without saving

Add `?validate_only=1` to the submit request. The server runs every check, then answers `200 {"valid": true}` and stores nothing. It does not count toward the daily limit and does not replay an `Idempotency-Key`.

## Submit

```bash
curl https://anyrouter.dev/api/v1/apps/explain/submissions \
  -H "Authorization: Bearer $ANYROUTER_API_KEY" \
  -H "Idempotency-Key: my-figure-1" \
  -H "Content-Type: application/json" \
  -d '{"visibility":"unlisted","tags":["locks"],"payload":{...}}'
```

Body fields: `payload` (required), `title`, `visibility` (`private`, `unlisted`, `public`), `tags` (up to 8, `a-z0-9-`), `source_url` (https only). Unknown fields are rejected.

The response is `{id, url, status, visibility}` with `201`. Repeating a request with the same `Idempotency-Key` returns the original with `200`.

Visibility defaults to `unlisted`: anyone with the link can open it, and it is not listed. `public` submissions have status `pending_review` until approved. `private` is visible only to you. Making a submission public again after a change of visibility puts it back in review.

## Manage

| Route | Purpose |
|-------|---------|
| `GET /api/v1/apps/{app}/submissions` | Your submissions, newest first (`limit`, `cursor`) |
| `GET /api/v1/apps/{app}/submissions/{id}` | One of yours, with its payload |
| `PATCH /api/v1/apps/{app}/submissions/{id}` | Change `visibility` or `title` |
| `DELETE /api/v1/apps/{app}/submissions/{id}` | Delete it |

Another user's id returns `404`.

## Limits and errors

- Body at most the app's `limits.max_bytes` (64 KiB for Explain) (`413 payload_too_large`).
- 20 submissions per app per day, UTC (`429 daily_limit_reached`, with `Retry-After`).
- A payload with something that looks like a credential is refused with `422 secret_detected` and the field path. Nothing is stored.
- A payload that fails the schema or the app's checks gets `422 invalid_payload`. `error.metadata.issues` lists each problem as `{path, code, message, fix}`, for example `{"path": "parts[5].on", "code": "host_missing", ...}`.
- Unknown app: `404 app_not_found`.


## Related

- [API Keys](/docs/api-reference/keys.md)
