{"openapi":"3.1.0","info":{"title":"AFM local API — companion contract","version":"0.9.14","description":"A public, implementation-audited snapshot of the AFM HTTP contract. The API itself runs locally; inspect GET /openapi.json on the running binary for its bundled compact spec.","license":{"name":"MIT"}},"externalDocs":{"description":"Human guide and compatibility notes","url":"https://maclocal.ai/docs/api-reference"},"servers":[{"url":"http://127.0.0.1:9999","description":"Default local AFM server"}],"tags":[{"name":"Chat","description":"OpenAI-compatible chat completion and cancellation"},{"name":"Models","description":"Model and capability discovery"},{"name":"Token budgeting","description":"Tokenizer endpoints available with an MLX model"},{"name":"Apple native","description":"Embeddings, Vision OCR, speech recognition, and speech synthesis"},{"name":"Batch","description":"MLX-only multiplex and asynchronous batch surfaces"},{"name":"Operations","description":"Health, metrics, and local contract discovery"}],"components":{"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["message","type"],"properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":["string","null"]},"request_id":{"type":["string","null"]}}}}},"Message":{"type":"object","required":["role"],"properties":{"role":{"type":"string","enum":["system","developer","user","assistant","tool"]},"content":{"oneOf":[{"type":"string"},{"type":"array","items":{"$ref":"#/components/schemas/ContentPart"}},{"type":"null"}]},"name":{"type":"string"},"tool_calls":{"type":"array","items":{"$ref":"#/components/schemas/ToolCall"}},"tool_call_id":{"type":"string"}}},"ContentPart":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text","image_url","input_audio"]},"text":{"type":"string"},"image_url":{"type":"object","properties":{"url":{"type":"string"},"detail":{"type":"string","enum":["auto","low","high"]}}},"input_audio":{"type":"object","properties":{"data":{"type":"string","contentEncoding":"base64"},"format":{"type":"string"},"language":{"type":"string"}}}}},"FunctionDefinition":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"parameters":{"type":"object","additionalProperties":true},"strict":{"type":"boolean"}}},"Tool":{"type":"object","required":["type","function"],"properties":{"type":{"const":"function"},"function":{"$ref":"#/components/schemas/FunctionDefinition"}}},"ToolCall":{"type":"object","required":["id","type","function"],"properties":{"id":{"type":"string"},"type":{"const":"function"},"function":{"type":"object","required":["name","arguments"],"properties":{"name":{"type":"string"},"arguments":{"type":"string","description":"JSON encoded as a string"}}}}},"ResponseFormat":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text","json_object","json_schema"]},"json_schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"schema":{"type":"object","additionalProperties":true},"strict":{"type":"boolean"}}}}},"ChatCompletionRequest":{"type":"object","required":["messages"],"properties":{"model":{"type":"string"},"messages":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/Message"}},"temperature":{"type":"number"},"top_p":{"type":"number"},"top_k":{"type":"integer"},"min_p":{"type":"number"},"max_tokens":{"type":"integer"},"max_completion_tokens":{"type":"integer"},"repetition_penalty":{"type":"number"},"repeat_penalty":{"type":"number"},"frequency_penalty":{"type":"number","deprecated":true,"description":"Parsed but currently ignored"},"presence_penalty":{"type":"number"},"seed":{"type":"integer"},"logprobs":{"type":"boolean"},"top_logprobs":{"type":"integer","minimum":0,"maximum":20},"stop":{"type":"array","items":{"type":"string"}},"stream":{"type":"boolean"},"stream_options":{"type":"object","properties":{"include_usage":{"type":"boolean","default":true}}},"user":{"type":"string"},"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"}},"tool_choice":{"oneOf":[{"type":"string","enum":["auto","none","required"]},{"type":"object","additionalProperties":true}]},"parallel_tool_calls":{"type":"boolean"},"response_format":{"$ref":"#/components/schemas/ResponseFormat"},"chat_template_kwargs":{"type":"object","additionalProperties":true},"reasoning_effort":{"type":"string","enum":["low","high","max"]}}},"ChatCompletionResponse":{"type":"object","required":["id","object","created","model","choices","usage"],"properties":{"id":{"type":"string"},"object":{"const":"chat.completion"},"created":{"type":"integer"},"model":{"type":"string"},"system_fingerprint":{"type":"string"},"choices":{"type":"array","items":{"type":"object","additionalProperties":true}},"usage":{"type":"object","additionalProperties":true},"timings":{"type":"object","additionalProperties":true},"afm_profile":{"type":"object","additionalProperties":true},"afm_profile_extended":{"type":"object","additionalProperties":true}}},"TokenizeRequest":{"type":"object","properties":{"model":{"type":"string"},"text":{"type":"string"},"prompt":{"type":"string","description":"vLLM alias for text"}},"anyOf":[{"required":["text"]},{"required":["prompt"]}]},"EmbeddingsRequest":{"type":"object","required":["input"],"properties":{"input":{"description":"String, string array, token ID array, or array of token ID arrays"},"model":{"type":"string"},"encoding_format":{"type":"string","enum":["float","base64"],"default":"float"},"dimensions":{"type":"integer","minimum":1},"user":{"type":"string"}}},"Cancellation":{"type":"object","required":["id","object","cancelled"],"properties":{"id":{"type":"string"},"object":{"const":"chat.completion.cancellation"},"cancelled":{"type":"boolean"}}}}},"paths":{"/v1/chat/completions":{"post":{"tags":["Chat"],"summary":"Create a chat completion","operationId":"createChatCompletion","parameters":[{"name":"X-Request-ID","in":"header","required":false,"description":"Optional correlation ID. AFM echoes it as both X-Request-ID and OpenAI-Request-ID; otherwise it mints req_<uuid12>.","schema":{"type":"string"}},{"name":"X-AFM-Profile","in":"header","required":false,"schema":{"type":"string","enum":["true","extended"]},"description":"MLX-only profiling extension"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"}}}},"responses":{"200":{"description":"JSON completion or an SSE stream ending with data: [DONE]","headers":{"X-Request-ID":{"schema":{"type":"string"}},"OpenAI-Request-ID":{"schema":{"type":"string"}},"X-Grammar-Constraints":{"schema":{"type":"string","enum":["downgraded"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionResponse"}},"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Backend unavailable or server at capacity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/chat/completions/{id}/cancel":{"post":{"tags":["Chat"],"summary":"Cancel an in-flight completion","operationId":"cancelChatCompletion","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Use the X-Request-ID/OpenAI-Request-ID value, not the chatcmpl id"}],"responses":{"200":{"description":"Cancellation triggered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cancellation"}}}},"404":{"description":"Unknown, completed, or expired request ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cancellation"}}}}}}},"/v1/models":{"get":{"tags":["Models"],"summary":"List advertised models and capabilities","operationId":"listModels","responses":{"200":{"description":"OpenAI-style model list plus AFM model details"}}}},"/v1/tokenize":{"post":{"tags":["Token budgeting"],"summary":"Return token IDs and count","operationId":"tokenize","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenizeRequest"}}}},"responses":{"200":{"description":"tokens, count, model, and optional max_model_len"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"No public tokenizer for the active backend","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/count_tokens":{"post":{"tags":["Token budgeting"],"summary":"Return an Anthropic-shaped input token count","operationId":"countTokens","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenizeRequest"}}}},"responses":{"200":{"description":"input_tokens and model"},"422":{"description":"No public tokenizer for the active backend","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/embeddings":{"post":{"tags":["Apple native"],"summary":"Create Apple NaturalLanguage embeddings","operationId":"createEmbeddings","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmbeddingsRequest"}}}},"responses":{"200":{"description":"Embedding vectors and usage"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown embedding model"},"503":{"description":"Backend unavailable or server at capacity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/vision/ocr":{"post":{"tags":["Apple native"],"summary":"Extract text and document structure with Apple Vision","operationId":"visionOCR","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}},"multipart/form-data":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"JSON document result or text"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Requested mode needs a newer macOS release"},"503":{"description":"Backend unavailable or server at capacity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audio/transcriptions":{"post":{"tags":["Apple native"],"summary":"Transcribe audio with Apple Speech","operationId":"createTranscription","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"json, verbose_json, text, srt, or vtt output"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Backend unavailable or server at capacity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audio/speech":{"post":{"tags":["Apple native"],"summary":"Synthesize speech with an installed Apple voice","operationId":"createSpeech","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Audio bytes"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Backend unavailable or server at capacity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audio/voices":{"get":{"tags":["Apple native"],"summary":"List installed synthesis voices","operationId":"listVoices","responses":{"200":{"description":"Voice list"},"503":{"description":"Backend unavailable or server at capacity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/batch/completions":{"post":{"tags":["Batch"],"summary":"Multiplex up to 64 chat completions over SSE","operationId":"createMultiplexBatch","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requests"],"properties":{"requests":{"type":"array","minItems":1,"maxItems":64,"items":{"type":"object","required":["custom_id","body"],"properties":{"custom_id":{"type":"string"},"body":{"$ref":"#/components/schemas/ChatCompletionRequest"}}}}}}}}},"responses":{"200":{"description":"SSE events tagged with custom_id, followed by data: [DONE]"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/files":{"post":{"tags":["Batch"],"summary":"Upload JSONL input for an OpenAI-style batch","operationId":"uploadBatchFile","responses":{"200":{"description":"File object"}}}},"/v1/files/{file_id}":{"get":{"tags":["Batch"],"summary":"Get file metadata","operationId":"getBatchFile","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"File object"},"404":{"description":"Unknown file"}}},"delete":{"tags":["Batch"],"summary":"Delete a batch file","operationId":"deleteBatchFile","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deletion object"},"404":{"description":"Unknown file"}}}},"/v1/files/{file_id}/content":{"get":{"tags":["Batch"],"summary":"Download JSONL file content","operationId":"downloadBatchFile","parameters":[{"name":"file_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"application/jsonl content"},"404":{"description":"Unknown file"}}}},"/v1/batches":{"post":{"tags":["Batch"],"summary":"Create an asynchronous batch from an uploaded JSONL file","operationId":"createBatch","responses":{"200":{"description":"Batch object"}}},"get":{"tags":["Batch"],"summary":"List batch jobs","operationId":"listBatches","responses":{"200":{"description":"Batch list"}}}},"/v1/batches/{batch_id}":{"get":{"tags":["Batch"],"summary":"Get a batch job","operationId":"getBatch","parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Batch object"},"404":{"description":"Unknown batch"}}}},"/v1/batches/{batch_id}/cancel":{"post":{"tags":["Batch"],"summary":"Cancel an in-progress batch","operationId":"cancelBatch","parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated batch object"},"400":{"description":"Batch is no longer in progress"},"404":{"description":"Unknown batch"}}}},"/health":{"get":{"tags":["Operations"],"summary":"Liveness probe","operationId":"health","responses":{"200":{"description":"Healthy response"}}}},"/metrics":{"get":{"tags":["Operations"],"summary":"Prometheus metrics","operationId":"metrics","responses":{"200":{"description":"Prometheus text exposition","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/openapi.json":{"get":{"tags":["Operations"],"summary":"Read the running binary's compact OpenAPI document","operationId":"runtimeOpenAPI","responses":{"200":{"description":"OpenAPI 3.1 JSON"}}}},"/docs":{"get":{"tags":["Operations"],"summary":"Open the running binary's Scalar reference","operationId":"runtimeDocs","responses":{"200":{"description":"HTML"}}}}},"x-afm-contract-source":{"repository":"https://github.com/scouzi1966/maclocal-api","implementation_paths":["Sources/AFMServer/Controllers","Sources/AFMOpenAICompat/OpenAIRequest.swift","Sources/AFMOpenAICompat/OpenAIResponse.swift"],"notes":["This hosted document describes the local AFM process; maclocal.ai is documentation, not an inference endpoint.","File and batch-job routes are registered only when the concrete MLX batch service is active."]}}