# HTTP API Reference This is the canonical public HTTP contract for Scriptorium. ## Service And Route `POST /v1/runs` runs one prompt request and returns generated output, validation, and metadata. The service has no built-in authentication or authorization; deploy it behind appropriate network and authentication controls. The service address and HTTP limits are configured as described in the [configuration reference](config.md). `serve` invocation is defined in the [CLI reference](cli.md). Requests and responses are JSON objects. Requests are decoded as JSON regardless of their `Content-Type`; successful JSON responses use `Content-Type: application/json`. There are no query parameters. ## Request Limits The configured request-body limit includes inline artifact bodies. The artifact limit applies to HTTP `file` inputs. The response limit applies to the encoded response, including the artifact body and optional raw output. A limit of zero disables that limit. A request body over its limit returns `413 request_too_large`; an oversized file input returns `413 artifact_too_large`; an oversized encoded response returns `413 response_too_large`. ## `POST /v1/runs` ### Request Body The maintained [request example](../examples/http-run.json) is a complete copyable shape. At the HTTP adapter boundary, the smallest valid shape is: ```json { "prompt_id": "generic.markdown_summary" } ``` | Field | Required | Meaning | | --- | --- | --- | | `prompt_id` | yes | Non-blank prompt ID. | | `prompt_version` | no | Prompt version filter. | | `profile_id` | no | Execution-profile ID; otherwise the prompt must set `default_profile`. | | `session_id` | no | Optional direct, non-secret session identifier. | | `inputs` | no | Optional object mapping input names to references. Promptkit decides whether the selected definition needs them. | | `vars` | no | Object mapping template-variable names to strings. | | `model` | no | Runtime model-override object. | | `include_raw_output` | no | Include `raw_model_output` when true. | An input reference has a required `type` of `file` or `inline`. A `file` reference requires `uri`; an `inline` reference requires `body`. HTTP file references require a configured artifact root. Relative paths resolve within that root. Absolute paths must be lexically within it; traversal outside it is rejected with `400 artifact_not_allowed`. This lexical check does not resolve symlinks: the operating system follows symlinks inside the root, including ones that target outside it. Keep the root narrow and inaccessible to untrusted writers. The optional `model` object accepts `endpoint`, `model`, `temperature`, `max_tokens`, `top_p`, `timeout_seconds`, `service_tier`, `reasoning_effort`, `api_key_env`, and `extra_params`. Numeric ranges and framework credential semantics are defined by the [Promptkit format reference](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.9.0/docs/formats.md). Explicit zero values for the numeric fields are overrides; zero `timeout_seconds` disables the per-generation deadline only, retaining the request context and configured transport cap. The timeout layers are defined in the [Promptkit outbound integration contract](https://gitea.maximumdirect.net/eric/promptkit/src/tag/v0.9.0/docs/integrations/openai-compatible-chat.md#timeout-and-cancellation). Raw API-key values are not accepted. `api_key` and any other unknown model field cause `400 invalid_json`. `model.reasoning_effort` is an optional JSON string with three states: omission inherits the selected profile, a non-empty string replaces its value, and an empty string explicitly clears it. JSON `null` is treated as omission. `session_id` is passed directly to Promptkit. A nonblank value replaces a definition-rendered session ID; omission or a blank value lets the definition provide one. Promptkit trims direct values and limits them to 256 Unicode code points. Session IDs are not credentials and may be included in prepared data, results, and provider-facing requests, so use stable non-sensitive identifiers. ### Strict JSON Request decoding rejects malformed JSON, unknown fields at every request level, and trailing JSON tokens with `400 invalid_json`. A blank `prompt_id` returns `400 invalid_request`. Omitted or empty `inputs` are passed to Promptkit, which reports any definition-required or template-referenced inputs. ### Success Response A completed run returns `200 OK`, including when generated content fails its validation contract. The response contains: - `artifact`: `name`, `content_type`, `body`, `size`, `hash`, and optional `uri`; - `validation`: `status`, `mode`, `repair_attempts`, `is_valid`, plus optional `errors` and `schema_path`; - `metadata`: run, prompt, rendered-prompt, profile, model, input-hash, usage, timing, validation, and repair-attempt metadata; and - optional `raw_model_output` when requested. `metadata.model_params` has `endpoint`, `model`, `temperature`, `max_tokens`, `top_p`, and `timeout_seconds`, plus optional `service_tier`, `reasoning_effort`, `api_key_env`, and `extra_params`. `metadata.usage` always includes `prompt_tokens`, `completion_tokens`, `total_tokens`, `cached_tokens`, and `cache_write_tokens`; unavailable cache usage is reported as zero. When Promptkit resolves a direct or definition-rendered session ID, `metadata.session_id` contains that effective result value. It is omitted when no effective session ID exists. A validation failure has `validation.status: "failed"`, `is_valid: false`, and any available diagnostic errors, while still returning the artifact and metadata. ## Error Responses Errors have this shape: ```json {"error":{"code":"invalid_request","message":"prompt_id is required"}} ``` Messages are concise and do not expose wrapped internal causes. | Status | Code | Meaning | | --- | --- | --- | | `400` | `invalid_json` | Malformed JSON, unknown field, or trailing JSON. | | `400` | `invalid_request` | Missing or invalid request data or runtime override. | | `400` | `profile_required` | No profile ID and no prompt default profile. | | `400` | `prompt_load_failed` | Prompt definition failed to load. | | `400` | `profile_load_failed` | Profile failed to load. | | `400` | `artifact_not_allowed` | HTTP file input is disabled or outside the artifact root. | | `400` | `artifact_read_failed` | Input artifact is invalid or cannot be read. | | `400` | `prompt_render_failed` | Prompt template rendering failed. | | `400` | `api_key_env_missing` | The selected credential environment variable is unset or empty. | | `404` | `not_found` | Route does not exist. | | `404` | `prompt_not_found` | Prompt ID or version does not exist. | | `404` | `profile_not_found` | Profile ID does not exist. | | `405` | `method_not_allowed` | The route does not accept the method. | | `413` | `request_too_large` | Encoded request exceeds its limit. | | `413` | `artifact_too_large` | File input exceeds its limit. | | `413` | `response_too_large` | Encoded response exceeds its limit. | | `500` | `validation_runtime_failed` | Schema or validator runtime failure. | | `500` | `internal_error` | Unclassified server failure. | | `502` | `llm_failed` | Outbound model request failed. | ## Retry And Idempotency Scriptorium provides no idempotency keys, pagination, caching headers, or rate limits. Clients may retry transport failures or `5xx` responses only when their workflow tolerates another model call: a retry can produce different output and incur another provider request.