{
  "manifest_version": "1.0.0",
  "template": {
    "id": "d920fba8-3894-41c3-aabc-96745fffaed8",
    "slug": "openmed-api",
    "name": "OpenMed | Medical NER and PII De-identification API",
    "description": "OpenMed REST API: clinical entity extraction and PII de-identification",
    "url": "https://railway.com/deploy/openmed-api",
    "upstream": {
      "image": "ghcr.io/maziyarpanahi/openmed:v3.1.0"
    }
  },
  "status": "unvalidated",
  "validated_at": null,
  "success_rate_30d": null,
  "services": [
    {
      "name": "OpenMed",
      "source": {
        "image": "ghcr.io/maziyarpanahi/openmed:v3.1.0"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": true
    }
  ],
  "required_inputs": [
    {
      "key": "PORT",
      "service": "OpenMed",
      "description": "Port OpenMed listens on - leave as is",
      "secret": false,
      "strategy": "default",
      "default": "8080"
    },
    {
      "key": "HF_HOME",
      "service": "OpenMed",
      "description": "Hugging Face download cache on the volume, so models download once - leave as is",
      "secret": false,
      "strategy": "default",
      "default": "/data/huggingface"
    },
    {
      "key": "MKL_NUM_THREADS",
      "service": "OpenMed",
      "description": "Same cap for the math library - keep equal to OMP_NUM_THREADS",
      "secret": false,
      "strategy": "default",
      "default": "2"
    },
    {
      "key": "OMP_NUM_THREADS",
      "service": "OpenMed",
      "description": "CPU threads per model call - keeps requests fast and memory low (torch otherwise starts one per host core)",
      "secret": false,
      "strategy": "default",
      "default": "2"
    },
    {
      "key": "OPENMED_API_KEY",
      "service": "OpenMed",
      "description": "API key - send it as the X-API-Key header; change it and redeploy to rotate",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "OPENMED_CACHE_DIR",
      "service": "OpenMed",
      "description": "Model and vocabulary cache on the volume - leave as is",
      "secret": false,
      "strategy": "default",
      "default": "/data/openmed"
    },
    {
      "key": "OPENMED_SERVICE_KEEP_ALIVE",
      "service": "OpenMed",
      "description": "Unload a model after this long idle to free memory (30s, 10m, 1h; 0 = after each request)",
      "secret": false,
      "strategy": "default",
      "default": "10m"
    },
    {
      "key": "OPENMED_SERVICE_AUTH_ENABLED",
      "service": "OpenMed",
      "description": "Require the API key on every model route - leave on for a public domain",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "OPENMED_SERVICE_TRUSTED_HOSTS",
      "service": "OpenMed",
      "description": "Allowed Host headers - add your custom domain here when you add one",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "OPENMED_SERVICE_MAX_RESIDENT_MODELS",
      "service": "OpenMed",
      "description": "Most models kept in memory at once; the least recently used idle one is unloaded",
      "secret": false,
      "strategy": "default",
      "default": "2"
    }
  ],
  "deploy": {
    "mcp": {
      "server": "railway",
      "tool": "deploy_template",
      "args": {
        "template_code": "openmed-api"
      }
    },
    "cli": "railway deploy --template openmed-api",
    "api": {
      "method": "POST",
      "path": "/graphql/v2",
      "body": {
        "query": "mutation templateDeploy($input: TemplateDeployV2Input!) { templateDeployV2(input: $input) { projectId workflowId } }",
        "variables": {
          "input": {
            "templateId": "d920fba8-3894-41c3-aabc-96745fffaed8",
            "serializedConfig": {
              "services": {
                "b32cbb14-e777-411c-b1b5-b4fc2c8f4cf6": {
                  "icon": "https://raw.githubusercontent.com/maziyarpanahi/openmed/v3.1.0/docs/website/favicon.svg",
                  "name": "OpenMed",
                  "build": {},
                  "deploy": {
                    "startCommand": "python -c \"import os,json,hashlib,sys;k=os.environ.get('OPENMED_API_KEY','').strip();k or sys.exit('OPENMED_API_KEY is empty');os.environ['OPENMED_SERVICE_AUTH_API_KEYS']=json.dumps([{'id':'railway','key_hash':hashlib.sha256(k.encode()).hexdigest(),'scopes':['*']}]);os.execvp('python',['python','-m','openmed.service.logging','--host','0.0.0.0','--port',os.environ.get('PORT','8080')])\"",
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "ghcr.io/maziyarpanahi/openmed:v3.1.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "Port OpenMed listens on - leave as is",
                      "defaultValue": "8080"
                    },
                    "HF_HOME": {
                      "isOptional": false,
                      "description": "Hugging Face download cache on the volume, so models download once - leave as is",
                      "defaultValue": "/data/huggingface"
                    },
                    "HF_TOKEN": {
                      "isOptional": true,
                      "description": "Optional Hugging Face token for gated models and higher download limits",
                      "defaultValue": ""
                    },
                    "MKL_NUM_THREADS": {
                      "isOptional": false,
                      "description": "Same cap for the math library - keep equal to OMP_NUM_THREADS",
                      "defaultValue": "2"
                    },
                    "OMP_NUM_THREADS": {
                      "isOptional": false,
                      "description": "CPU threads per model call - keeps requests fast and memory low (torch otherwise starts one per host core)",
                      "defaultValue": "2"
                    },
                    "OPENMED_API_KEY": {
                      "isOptional": false,
                      "description": "API key - send it as the X-API-Key header; change it and redeploy to rotate",
                      "defaultValue": "{{OPENMED_API_KEY}}"
                    },
                    "OPENMED_CACHE_DIR": {
                      "isOptional": false,
                      "description": "Model and vocabulary cache on the volume - leave as is",
                      "defaultValue": "/data/openmed"
                    },
                    "OPENMED_SERVICE_KEEP_ALIVE": {
                      "isOptional": false,
                      "description": "Unload a model after this long idle to free memory (30s, 10m, 1h; 0 = after each request)",
                      "defaultValue": "10m"
                    },
                    "OPENMED_SERVICE_AUTH_ENABLED": {
                      "isOptional": false,
                      "description": "Require the API key on every model route - leave on for a public domain",
                      "defaultValue": "true"
                    },
                    "OPENMED_SERVICE_TRUSTED_HOSTS": {
                      "isOptional": false,
                      "description": "Allowed Host headers - add your custom domain here when you add one",
                      "defaultValue": "${{RAILWAY_PUBLIC_DOMAIN}},healthcheck.railway.app,localhost,127.0.0.1"
                    },
                    "OPENMED_SERVICE_PRELOAD_MODELS": {
                      "isOptional": true,
                      "description": "Comma-separated models to load at boot (aliases or Hugging Face ids); empty loads on first request",
                      "defaultValue": ""
                    },
                    "OPENMED_SERVICE_MAX_RESIDENT_MODELS": {
                      "isOptional": false,
                      "description": "Most models kept in memory at once; the least recently used idle one is unloaded",
                      "defaultValue": "2"
                    }
                  },
                  "networking": {
                    "serviceDomains": {
                      "<hasDomain>": {}
                    }
                  },
                  "volumeMounts": {
                    "b32cbb14-e777-411c-b1b5-b4fc2c8f4cf6": {
                      "mountPath": "/data"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "post_deploy": {
    "healthcheck": {
      "service": "OpenMed",
      "method": "GET",
      "path": "/readyz",
      "expect_status": 200
    }
  },
  "resources": {
    "expected_services": 1,
    "needs_volume": true
  },
  "generated_at": "2026-11-07T16:15:10.050Z",
  "generator_version": "0.1.0"
}
