<!-- source: https://secondlayer.tools/docs/api-reference#deploy-subgraph -->

## Deploy or redeploy a subgraph

`POST /api/subgraphs` (self-hosted instances only)

Takes bundled handler code (see `/api/subgraphs/bundle`) plus the schema and sources extracted from it. The server reads the definition from the bundle without running it, validates it, lints print fields, then applies the schema. A new subgraph, or a breaking schema change, queues a reindex and returns its `operationId`. Redeploying the same name updates in place; `action` says what happened. With `dryRun` it returns the DDL plan and writes nothing.

### Body

| Field | Type | Description |
| --- | --- | --- |
| `name` | string, required | Subgraph name. Lowercase letters, digits and hyphens. Deploying an existing name updates it. |
| `sources` | object, required | Event sources keyed by name, as read from the handler module. At least one. |
| `schema` | object, required | Table definitions keyed by table name. |
| `handlerCode` | string, required | The bundled handler module. 1 MB max. |
| `startBlock` | integer | Overrides the definition's `startBlock` for this deploy and forces a reindex from it. Refused past the chain tip (`START_BLOCK_PAST_TIP`). |
| `description` | string | One line on what the subgraph indexes. |
| `sourceCode` | string | The unbundled TypeScript source, kept so `secondlayer subgraphs source` can return it. 1 MB max. |
| `dryRun` | boolean | Validate and return the DDL plan without writing anything. |

### Responses

- `200` Redeployed in place (`action` is anything but `created`), or the dry-run plan when `dryRun` is set
- `201` Created. A reindex from `start_block` is queued; poll `operationId`
- `400` The body failed validation (here `error` is an object of field errors, not a string), the bundle could not be read (`HANDLER_IMPORT_FAILED`), the definition is invalid, or `startBlock` is past the chain tip (`START_BLOCK_PAST_TIP`)
- `401` This bind requires the instance token, and it was missing or wrong
- `409` A reindex or backfill is already queued or running for this subgraph (`OPERATION_IN_PROGRESS`). Wait for it, or stop it first.
- `415` Missing or non-JSON `Content-Type` (`UNSUPPORTED_MEDIA_TYPE`) — writes must send `Content-Type: application/json`
- `422` The definition deploys but would index wrong: a declared print field is never seen on-chain (`PRINT_FIELD_MISMATCH`), handlers write 0 rows against observed prints (`EMPTY_MAPPING`), a tip-first deploy has delta handlers (`TIP_FIRST_NON_REPLAYABLE_HANDLER`) or a breaking schema change (`TIP_FIRST_BREAKING_CHANGE`), or a subgraph with provable sources has a nondeterministic handler (`NONDETERMINISTIC_HANDLER`, with `verification.reasons` naming each use)

### Example

```bash
curl -X POST http://127.0.0.1:3800/api/subgraphs \
  -H "Authorization: Bearer $INSTANCE_TOKEN" \
  --json '{
  "name": "sbtc-flows",
  "sources": {
    "transfers": {
      "type": "ft_transfer",
      "assetIdentifier": "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token::sbtc-token"
    }
  },
  "schema": {
    "transfers": {
      "columns": {
        "amount": {
          "type": "uint"
        },
        "sender": {
          "type": "principal"
        },
        "recipient": {
          "type": "principal"
        }
      }
    }
  },
  "handlerCode": "// secondlayer-stub:@secondlayer/subgraphs\nfunction defineSubgraph(def) {\n  return def;\n}\n\n// <stdin>\nvar stdin_default = defineSubgraph({\n  name: \"sbtc-flows\",\n  startBlock: 8e6,\n  sources: {\n    transfers: {\n      type: \"ft_transfer\",\n      assetIdentifier: \"SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token::sbtc-token\"\n    }\n  },\n  schema: {\n    transfers: {\n      columns: {\n        amount: { type: \"uint\" },\n        sender: { type: \"principal\" },\n        recipient: { type: \"principal\" }\n      }\n    }\n  },\n  handlers: {\n    transfers: (event, ctx) => {\n      ctx.insert(\"transfers\", { amount: event.amount, sender: event.sender, recipient: event.recipient });\n    }\n  }\n});\nexport {\n  stdin_default as default\n};\n",
  "sourceCode": "import { defineSubgraph } from \"@secondlayer/subgraphs\";\n\nexport default defineSubgraph({\n  name: \"sbtc-flows\",\n  startBlock: 8000000,\n  sources: {\n    transfers: {\n      type: \"ft_transfer\",\n      assetIdentifier: \"SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token::sbtc-token\",\n    },\n  },\n  schema: {\n    transfers: {\n      columns: {\n        amount: { type: \"uint\" },\n        sender: { type: \"principal\" },\n        recipient: { type: \"principal\" },\n      },\n    },\n  },\n  handlers: {\n    transfers: (event, ctx) => {\n      ctx.insert(\"transfers\", { amount: event.amount, sender: event.sender, recipient: event.recipient });\n    },\n  },\n});\n"
}'
```

```json
{
  "action": "handler_updated",
  "subgraphId": "b7e2d4a1-5c3f-4e8b-a0d9-2f6c1e8b7a34",
  "version": "1.0.3",
  "start_block": 8000000,
  "message": "Subgraph \"sbtc-flows\" handler_updated",
  "warnings": [
    "handler changed but schema did not: new logic applies from the current tip only. Rows already indexed were computed by the previous handler. To recompute history, redeploy with --reindex (or backfill a range)."
  ]
}
```
