@spicyapi/skill 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SpicyAPI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @spicyapi/skill
2
+
3
+ Official standalone Agent Skill for SpicyAPI. It follows the portable Agent Skills directory format
4
+ and contains the Skill assets plus an atomic installer; it does not include the SDK, CLI, or MCP
5
+ server. Codex is one supported client, not the package's scope or identity.
6
+
7
+ ```bash
8
+ npx --yes --package=@spicyapi/skill spicyapi-skill install
9
+ ```
10
+
11
+ The default destination is `~/.agents/skills/spicyapi`. Set `AGENTS_SKILLS_DIR` to another shared
12
+ skills directory, or pass the exact client-specific directory with `--target`.
13
+
14
+ Inspect the packaged source directory with:
15
+
16
+ ```bash
17
+ npx --yes --package=@spicyapi/skill spicyapi-skill path
18
+ ```
19
+
20
+ Use `--target <absolute-directory>` for Codex, Claude Code, Cursor, or another Agent
21
+ Skills-compatible client when it uses a different skills directory. Use `--force` only when you
22
+ intend to replace that exact destination.
package/SECURITY.md ADDED
@@ -0,0 +1,4 @@
1
+ # Security
2
+
3
+ Report suspected vulnerabilities privately at <https://spicyapi.ai/security>. Never include API
4
+ keys, private media, signed URLs, recovery codes, or customer data in a public issue.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import process from "node:process";
3
+ import { getBundledSkillPath, installSpicySkill } from "./skill/index.js";
4
+ function usage() {
5
+ return [
6
+ "Usage: spicyapi-skill [path | install] [--target <directory>] [--force] [--json]",
7
+ "",
8
+ "Commands:",
9
+ " path Show the packaged Skill directory",
10
+ " install Install the Skill into the shared Agent Skills directory (default)",
11
+ ].join("\n");
12
+ }
13
+ function optionValue(args, name) {
14
+ const index = args.indexOf(name);
15
+ if (index < 0)
16
+ return undefined;
17
+ const value = args[index + 1];
18
+ if (!value || value.startsWith("--"))
19
+ throw new Error(`${name} requires a value`);
20
+ return value;
21
+ }
22
+ async function main() {
23
+ const args = process.argv.slice(2);
24
+ if (args.includes("--help") || args.includes("-h")) {
25
+ process.stdout.write(`${usage()}\n`);
26
+ return;
27
+ }
28
+ const command = args[0]?.startsWith("--") ? "install" : (args[0] ?? "install");
29
+ const json = args.includes("--json");
30
+ let result;
31
+ if (command === "path") {
32
+ result = { path: await getBundledSkillPath() };
33
+ }
34
+ else if (command === "install") {
35
+ const target = optionValue(args, "--target");
36
+ result = await installSpicySkill({
37
+ ...(target === undefined ? {} : { destination: target }),
38
+ force: args.includes("--force"),
39
+ });
40
+ }
41
+ else {
42
+ throw new Error(`unknown command: ${command}`);
43
+ }
44
+ process.stdout.write(json ? `${JSON.stringify(result)}\n` : `${JSON.stringify(result, null, 2)}\n`);
45
+ }
46
+ main().catch((error) => {
47
+ const message = error instanceof Error ? error.message : "unknown error";
48
+ process.stderr.write(`${message}\n`);
49
+ process.exitCode = 1;
50
+ });
51
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1E,SAAS,KAAK;IACZ,OAAO;QACL,kFAAkF;QAClF,EAAE;QACF,WAAW;QACX,gDAAgD;QAChD,iFAAiF;KAClF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,IAAc,EAAE,IAAY;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,mBAAmB,CAAC,CAAC;IAClF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC;IAC/E,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,MAAe,CAAC;IACpB,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACvB,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,mBAAmB,EAAE,EAAE,CAAC;IACjD,CAAC;SAAM,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC7C,MAAM,GAAG,MAAM,iBAAiB,CAAC;YAC/B,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;YACxD,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;SAChC,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAC9E,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;IACzE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface InstallSpicySkillOptions {
2
+ destination?: string;
3
+ force?: boolean;
4
+ env?: NodeJS.ProcessEnv;
5
+ }
6
+ export interface InstalledSpicySkill {
7
+ destination: string;
8
+ replaced: boolean;
9
+ }
10
+ export declare function getBundledSkillPath(): Promise<string>;
11
+ export declare function getDefaultAgentSkillPath(env?: NodeJS.ProcessEnv): string;
12
+ export declare function installSpicySkill(options?: InstallSpicySkillOptions): Promise<InstalledSpicySkill>;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/skill/index.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAYD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,CAU3D;AAED,wBAAgB,wBAAwB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGrF;AAED,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,mBAAmB,CAAC,CA+B9B"}
@@ -0,0 +1,66 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { access, cp, mkdir, rename, rm } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ async function exists(target) {
7
+ try {
8
+ await access(target);
9
+ return true;
10
+ }
11
+ catch (error) {
12
+ if (error.code === "ENOENT")
13
+ return false;
14
+ throw error;
15
+ }
16
+ }
17
+ export async function getBundledSkillPath() {
18
+ const candidates = [
19
+ new URL("../../../skills/spicyapi/", import.meta.url),
20
+ new URL("../../skills/spicyapi/", import.meta.url),
21
+ ];
22
+ for (const candidate of candidates) {
23
+ const directory = fileURLToPath(candidate);
24
+ if (await exists(path.join(directory, "SKILL.md")))
25
+ return directory;
26
+ }
27
+ throw new Error("the packaged SpicyAPI Skill could not be found");
28
+ }
29
+ export function getDefaultAgentSkillPath(env = process.env) {
30
+ const skillsRoot = env.AGENTS_SKILLS_DIR?.trim() || path.join(homedir(), ".agents", "skills");
31
+ return path.resolve(skillsRoot, "spicyapi");
32
+ }
33
+ export async function installSpicySkill(options = {}) {
34
+ const source = await getBundledSkillPath();
35
+ const destination = path.resolve(options.destination ?? getDefaultAgentSkillPath(options.env));
36
+ const destinationExists = await exists(destination);
37
+ if (destinationExists && !options.force) {
38
+ throw new Error(`skill already exists at ${destination}; pass --force to replace that exact directory`);
39
+ }
40
+ const parent = path.dirname(destination);
41
+ const nonce = randomUUID();
42
+ const staging = path.join(parent, `.spicyapi-install-${nonce}`);
43
+ const backup = path.join(parent, `.spicyapi-backup-${nonce}`);
44
+ await mkdir(parent, { recursive: true });
45
+ try {
46
+ await cp(source, staging, { recursive: true, errorOnExist: true, force: false });
47
+ if (destinationExists)
48
+ await rename(destination, backup);
49
+ try {
50
+ await rename(staging, destination);
51
+ }
52
+ catch (error) {
53
+ if (destinationExists && (await exists(backup)))
54
+ await rename(backup, destination);
55
+ throw error;
56
+ }
57
+ if (destinationExists)
58
+ await rm(backup, { recursive: true, force: true });
59
+ }
60
+ catch (error) {
61
+ await rm(staging, { recursive: true, force: true });
62
+ throw error;
63
+ }
64
+ return { destination, replaced: destinationExists };
65
+ }
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/skill/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@spicyapi/skill",
3
+ "version": "0.1.0",
4
+ "description": "Official installable Agent Skill for SpicyAPI",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/SpicyAPI/spicy-devkit.git",
10
+ "directory": "packages/skill"
11
+ },
12
+ "homepage": "https://spicyapi.ai",
13
+ "bugs": {
14
+ "url": "https://spicyapi.ai/contact"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "engines": {
20
+ "node": ">=22.13.0"
21
+ },
22
+ "main": "./dist/src/skill/index.js",
23
+ "types": "./dist/src/skill/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/src/skill/index.d.ts",
27
+ "import": "./dist/src/skill/index.js"
28
+ }
29
+ },
30
+ "bin": {
31
+ "spicyapi-skill": "dist/src/cli.js"
32
+ },
33
+ "files": [
34
+ "dist/src",
35
+ "skills/spicyapi",
36
+ "README.md",
37
+ "SECURITY.md",
38
+ "LICENSE"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit"
43
+ }
44
+ }
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: spicyapi
3
+ description:
4
+ Use the SpicyAPI public developer API through the official SDK, CLI, or MCP server to inspect live
5
+ models, create, retry, and wait for media tasks, upload inputs, retrieve outputs, check balances,
6
+ and verify webhooks. Use for SpicyAPI integration and API operations; not for browser-account
7
+ administration, payments, or publishing provider models.
8
+ metadata:
9
+ short-description: Operate SpicyAPI through its SDK, CLI, or MCP server
10
+ ---
11
+
12
+ # SpicyAPI
13
+
14
+ Use the packaged `spicyapi_*` MCP tools when they are connected. Otherwise use the `spicyapi` CLI
15
+ for operations or `SpicyClient` for application code. Treat the live model catalog and the bundled
16
+ OpenAPI contract as authoritative.
17
+
18
+ Read [references/api-workflows.md](references/api-workflows.md) before composing API, CLI, or MCP
19
+ operations. Read [references/safety.md](references/safety.md) before billable calls, real
20
+ integration tests, webhook handling, credential work, or cleanup.
21
+
22
+ ## Required behavior
23
+
24
+ - Query the live catalog and fetch the selected model with its input schema before creating a task.
25
+ Never invent a model ID, price, provider capability, or input field.
26
+ - Pass the model-specific `input` object through unchanged. The SpicyAPI server owns schema, price,
27
+ balance, operational API-key scope, and routing decisions.
28
+ - Treat `mature` as caller-declared execution metadata. Pass the requested boolean unchanged; the
29
+ platform does not inspect or gate it by account, API key, model, channel, age, consent, or a
30
+ content classifier. A selected model service may still refuse under its own behavior, and the
31
+ customer remains responsible for applicable law, age, consent, and end-user controls.
32
+ - Treat task creation and retry as billable. Obtain explicit confirmation and preserve one
33
+ idempotency key for the entire logical attempt, including recovery from an uncertain response.
34
+ - Use environment variables for API keys and webhook secrets. Never put them in arguments, source
35
+ code, prompts, logs, support messages, or committed configuration.
36
+ - Preserve `request_id`, task IDs, upload keys, and idempotency keys in operational results. They
37
+ are necessary for reconciliation and support.
38
+ - Do not invent task listing, cancellation, deletion, estimate, or webhook-redelivery calls. They
39
+ are absent from the public developer contract.
40
+ - Keep browser-session identity, payment, privacy, account closure, and admin operations in the
41
+ SpicyAPI Console. Do not emulate cookie/CSRF Console routes in this server-side skill.
42
+ - Provider/model onboarding and production enablement are outside this skill unless the user
43
+ explicitly supplies a separate supported contract.
44
+
45
+ ## Real validation
46
+
47
+ When the user asks for a real test, make a real request against the selected environment; do not
48
+ substitute a mock and call it verified. Record every created identifier before continuing. Use the
49
+ smallest user-approved billable scenario that the live catalog actually offers, then verify it
50
+ through task retrieval or waiting.
51
+
52
+ Clean up only exact artifacts that the environment exposes a supported deletion mechanism for. The
53
+ public developer API has no task or uploaded-object delete operation, so report those retained
54
+ artifacts and their identifiers honestly instead of claiming deletion or reaching into unrelated
55
+ data stores.
56
+
57
+ ## Completion
58
+
59
+ Report the surface used (SDK, CLI, or MCP), live model ID when applicable, idempotency key,
60
+ task/request identifiers, final observed state, and any artifact that could not be removed through a
61
+ supported API. Never report a provider call or cleanup as successful without observing it.
@@ -0,0 +1,8 @@
1
+ interface:
2
+ display_name: "SpicyAPI"
3
+ short_description: "Operate SpicyAPI safely through SDK, CLI, or MCP"
4
+ default_prompt:
5
+ "Use $spicyapi to inspect the live model catalog and run this SpicyAPI workflow safely."
6
+
7
+ policy:
8
+ allow_implicit_invocation: true
@@ -0,0 +1,108 @@
1
+ # SpicyAPI API workflows
2
+
3
+ ## Runtime configuration
4
+
5
+ Set credentials through the process environment or a secret manager:
6
+
7
+ - `SPICY_API_KEY`: required for authenticated public API operations.
8
+ - `SPICY_API_BASE_URL`: defaults to `https://api.spicyapi.ai/api/v1`.
9
+ - `SPICY_SERVICE_BASE_URL`: defaults to `https://api.spicyapi.ai` and is used for health/readiness.
10
+ - `SPICY_WEBHOOK_SECRET`: used only for local webhook verification.
11
+
12
+ Plain HTTP base URLs are accepted only for loopback development hosts.
13
+
14
+ ## Verified public surface
15
+
16
+ | Purpose | CLI | MCP tool | Public route |
17
+ | -------------- | --------------------------------------- | ------------------------------ | ------------------------------------ |
18
+ | Service status | `spicyapi status` | `spicyapi_service_status` | `/healthz`, `/readyz` |
19
+ | Search docs | `spicyapi docs search [query]` | `spicyapi_docs_search` | Bundled first-party index |
20
+ | List models | `spicyapi models list --include-schema` | `spicyapi_models_list` | `GET /models` |
21
+ | Get model | `spicyapi models get <model>` | `spicyapi_model_get` | `GET /models/{model}` |
22
+ | Balance | `spicyapi balance` | `spicyapi_balance_get` | `GET /chat/credit` |
23
+ | Create task | `spicyapi tasks create ...` | `spicyapi_task_create` | `POST /jobs/createTask` |
24
+ | Get task | `spicyapi tasks get <task-id>` | `spicyapi_task_get` | `GET /jobs/recordInfo` |
25
+ | Wait for task | `spicyapi tasks wait <task-id>` | `spicyapi_task_wait` | Composes task retrieval |
26
+ | Retry task | `spicyapi tasks retry <task-id>` | `spicyapi_task_retry` | `POST /jobs/retry` |
27
+ | Prepare upload | SDK | `spicyapi_upload_prepare` | `POST /common/upload-url` |
28
+ | Upload file | `spicyapi files upload <path>` | Keep bytes outside MCP | Presigned storage `PUT`, then commit |
29
+ | Commit upload | SDK | `spicyapi_upload_commit` | `POST /files/{fileId}/commit` |
30
+ | Output URL | `spicyapi files download-url <task-id>` | `spicyapi_download_url_create` | `POST /common/download-url` |
31
+ | Verify webhook | `spicyapi webhooks verify ...` | Local library/CLI | No network call |
32
+
33
+ These are the complete public developer operations in the bundled contract. Model listing is not
34
+ paginated.
35
+
36
+ ## Generation sequence
37
+
38
+ 1. Check readiness and authenticated balance.
39
+ 2. List models with `--include-schema` and get the exact candidate model.
40
+ 3. Build only the `input` object described by the returned live schema. Preserve unknown future
41
+ fields rather than remapping them.
42
+ 4. If the caller requests mature execution, send `mature: true` unchanged. It is recorded and
43
+ forwarded as execution metadata; it is not checked against an account, API key, catalog label,
44
+ channel, or platform content classifier. The selected model service may still refuse.
45
+ 5. Upload local media first when the model input requires a hosted asset. Use the returned committed
46
+ file reference exactly as the model schema specifies.
47
+ 6. Choose one idempotency key for the logical creation attempt.
48
+ 7. Confirm the current action and create the task. CLI confirmation is interactive unless `--yes` is
49
+ explicitly supplied; MCP uses protocol elicitation.
50
+ 8. Save the returned task ID, request ID when present, and idempotency key.
51
+ 9. Poll with `tasks wait`, or retrieve the task after a signed terminal webhook.
52
+ 10. Create a short-lived download URL for a successful task output. Do not persist the presigned URL
53
+ as a durable asset identifier.
54
+
55
+ Example CLI shape, after inspecting the live schema:
56
+
57
+ ```bash
58
+ spicyapi --json models get provider/model
59
+ spicyapi --json tasks create \
60
+ --model provider/model \
61
+ --input-file ./input.json \
62
+ --mature \
63
+ --idempotency-key 7b5a89dd-1ec3-4ec8-8246-6c76cc863665 \
64
+ --yes \
65
+ --wait
66
+ ```
67
+
68
+ The model and fields above are structural placeholders, not claims that a model exists. Replace them
69
+ only with values returned by the live catalog. Omit `--mature` when the caller did not request that
70
+ execution mode.
71
+
72
+ ## Uncertain responses and retries
73
+
74
+ - Read-only requests can use bounded automatic retries for transient failures.
75
+ - Task creation and retry are automatically retryable only when an idempotency key is present.
76
+ - After a timeout or lost response, reuse the same idempotency key. Never generate a second key
77
+ merely because the first response was uncertain.
78
+ - A task retry creates a new task and may reserve funds again. It is valid only for server-accepted
79
+ source states such as `failed` or `expired`; the server remains authoritative.
80
+ - Use `request_id` and `Retry-After` metadata from structured errors. Do not expose the API key
81
+ while reporting an error.
82
+
83
+ ## SDK outline
84
+
85
+ ```ts
86
+ import { SpicyClient } from "@spicyapi/sdk";
87
+
88
+ const client = new SpicyClient();
89
+ const model = await client.getModel("live/catalog-id");
90
+ const accepted = await client.createTask(
91
+ { model: model.model, input: {/* fields from model.inputSchema */} },
92
+ { idempotencyKey: crypto.randomUUID() },
93
+ );
94
+ const terminal = await client.waitForTask(accepted.taskId);
95
+ ```
96
+
97
+ ## Webhook verification
98
+
99
+ Verify the exact raw request bytes before parsing. Current signatures are Base64-encoded HMAC-SHA256
100
+ over:
101
+
102
+ ```text
103
+ taskId.timestamp.hex(sha256(raw_body))
104
+ ```
105
+
106
+ Payload version 1 reads `task_id`; version 2 reads `data.taskId`. Perform constant-time signature
107
+ comparison before checking timestamp tolerance. For v2, use `request_id` as the stable delivery
108
+ identifier when present.
@@ -0,0 +1,82 @@
1
+ # SpicyAPI safety and cleanup
2
+
3
+ ## Credentials and privacy
4
+
5
+ - Load `SPICY_API_KEY`, webhook secrets, and local MCP tokens from environment variables or a secret
6
+ manager.
7
+ - Never accept an API key through a CLI flag. Do not serialize secrets into JSON output, shell
8
+ history, MCP prompts, screenshots, telemetry, test snapshots, or error reports.
9
+ - Treat generation prompts, uploaded media, outputs, callbacks, and task metadata as private
10
+ customer content. Log identifiers and correlation metadata, not payload bodies or presigned URLs.
11
+ - A presigned upload or download URL is a temporary credential. Redact it and do not send the Spicy
12
+ API key to its storage host.
13
+
14
+ ## Billable operations
15
+
16
+ Task creation and retry can reserve funds. Before execution:
17
+
18
+ 1. Fetch the live model record and balance.
19
+ 2. Explain the exact model ID and that live price, operational API-key scope, balance, and routing
20
+ are decided on acceptance.
21
+ 3. Obtain explicit user confirmation, unless the user's current request already explicitly
22
+ authorizes that exact billable test.
23
+ 4. Mint or preserve one idempotency key and surface it to the user.
24
+
25
+ Decline, cancellation of confirmation, schema mismatch, or missing confirmation must result in zero
26
+ API calls.
27
+
28
+ ## Mature request metadata
29
+
30
+ - `mature` is a caller-declared request value, not an account or API-key entitlement.
31
+ - Forward the boolean unchanged. Do not preflight it against the catalog capability, selected
32
+ channel, age, consent, or a platform content policy, and do not claim the platform approved it.
33
+ - A model service may still return its own refusal. Report that observed task failure normally;
34
+ never invent a platform gate or retry under a different mode without user direction.
35
+ - Applicable legal, age, consent, real-person, and end-user access responsibilities remain with the
36
+ customer and are not established by technical success.
37
+
38
+ ## Real integration tests
39
+
40
+ - Use a dedicated test account/key and a unique marker in permissible metadata or prompt text when
41
+ the live model schema supports it.
42
+ - Record start time, model ID, idempotency key, request IDs, task IDs, uploaded file IDs/keys, and
43
+ output keys immediately.
44
+ - Bound cost and count before starting. Stop after the first representative success for each
45
+ distinct route unless more cases are explicitly required.
46
+ - Verify boundaries with real server responses: unauthorized, invalid schema, insufficient balance,
47
+ idempotent replay, terminal polling, ownership-safe 404, and webhook tampering where the
48
+ environment safely permits them.
49
+ - Never manufacture a success when provider credentials, model availability, funds, or policy
50
+ prevent the route from completing. Preserve the observed error and request ID.
51
+
52
+ ## Cleanup
53
+
54
+ Build an exact artifact ledger during testing. Delete or revoke only entries from that ledger and
55
+ verify each deletion through the same supported control plane.
56
+
57
+ The public SpicyAPI developer contract does not expose task cancellation, task deletion,
58
+ uploaded-object deletion, output deletion, or webhook redelivery. Therefore:
59
+
60
+ - Do not claim public-API cleanup for those records.
61
+ - Do not delete adjacent account data or reach into production databases as a workaround.
62
+ - If an authorized isolated local environment exposes direct cleanup, match exact unique IDs,
63
+ confirm the environment, remove only those rows/objects, and verify zero remaining matches.
64
+ - Otherwise report retained task/file IDs and their documented retention behavior to the user.
65
+
66
+ ## MCP boundaries
67
+
68
+ - Prefer stdio for local clients.
69
+ - The packaged HTTP server binds only to loopback and requires a separate `SPICY_MCP_HTTP_TOKEN` of
70
+ at least 32 bytes. It must not equal `SPICY_API_KEY`.
71
+ - Never expose the local static-token HTTP mode to a remote interface. Internet-facing MCP requires
72
+ a standards-compliant OAuth resource server with audience-bound tokens and is intentionally not
73
+ provided here.
74
+ - MCP tool annotations are hints, not authorization. Billable execution remains gated by protocol
75
+ elicitation and signed request state.
76
+
77
+ ## Account and payment boundaries
78
+
79
+ Registration, login, password recovery, team membership, checkout, saved payment methods, automatic
80
+ recharge, privacy export/deletion, account closure, support, and admin operations use
81
+ browser-session Console contracts. Keep those flows in the product UI unless a first-class
82
+ non-browser authorization contract is explicitly added.