> ## Documentation Index
> Fetch the complete documentation index at: https://docs.localops.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Service

> Create a service inside an environment. The service is registered with the provisioner and persisted, service level secrets are optionally seeded, and an immediate first deployment is optionally triggered.

The service `subdomain` is generated by LocalOps and cannot be supplied.

**`deploy_now`** triggers a deployment right after creation with an empty target - the latest commit on the configured branch for git sources, and the `latest` tag for `docker_image`. No chart version is passed for `helm_chart`, so prefer `deploy_now: false` plus an explicit deploy call with `helm_chart_version`. `deploy_now` does not return a deployment id. If creation succeeds but the deployment fails, the service still exists and the call returns `500` with `Service created successfully, but failed to deploy the service`.



## OpenAPI

````yaml /openapi.json post /v1/environments/{envId}/services
openapi: 3.0.0
info:
  title: LocalOps API
  version: 1.0.0
  description: >-
    API for programmatically managing LocalOps environments and services.


    Every successful response (except `GET /health` and `DELETE
    /v1/environments/{envId}/services/{serviceId}`) is wrapped in an envelope:
    `{ "message": "success", "data": { ... } }`. Errors are returned unwrapped
    as `{ "error_code": "...", "message": "...", "errors": [...] }`.


    Deploys, deletes, secret writes and custom domain deploys are asynchronous.
    A 2xx confirms that the request was accepted and passed synchronous
    validation - observe the outcome by polling deployment state, service state
    or custom domain state.


    Endpoints badged **COMING SOON** are not live yet. Their request and
    response shapes are published so you can plan an integration, and may change
    before release - calls to those paths return `404` today.
servers:
  - url: https://sdk.localops.co
security:
  - bearerAuth: []
tags:
  - name: Health
    description: Liveness probe.
  - name: Environments
    description: Read an environment's identity, network edges and workload identity.
  - name: Services
    description: Create, read, update and delete services inside an environment.
  - name: Deployments
    description: Trigger deployments and poll their state.
  - name: Operations
    description: Track asynchronous work, with per operation status, error and logs.
  - name: Custom Domains
    description: Attach and verify custom domains for a service.
  - name: Secrets
    description: Read and replace environment level and service level secrets.
paths:
  /v1/environments/{envId}/services:
    post:
      tags:
        - Services
      summary: Create Service
      description: >-
        Create a service inside an environment. The service is registered with
        the provisioner and persisted, service level secrets are optionally
        seeded, and an immediate first deployment is optionally triggered.


        The service `subdomain` is generated by LocalOps and cannot be supplied.


        **`deploy_now`** triggers a deployment right after creation with an
        empty target - the latest commit on the configured branch for git
        sources, and the `latest` tag for `docker_image`. No chart version is
        passed for `helm_chart`, so prefer `deploy_now: false` plus an explicit
        deploy call with `helm_chart_version`. `deploy_now` does not return a
        deployment id. If creation succeeds but the deployment fails, the
        service still exists and the call returns `500` with `Service created
        successfully, but failed to deploy the service`.
      operationId: createService
      parameters:
        - name: envId
          in: path
          required: true
          description: The unique identifier of the environment
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - source
              properties:
                name:
                  type: string
                  minLength: 3
                  maxLength: 50
                  description: >-
                    Letters and spaces only. Digits, hyphens and underscores are
                    rejected on create.
                type:
                  type: string
                  enum:
                    - web
                    - internal
                    - worker
                    - cron
                    - job
                  description: Optional, but drives other requirements
                source:
                  type: string
                  enum:
                    - github
                    - gitlab
                    - docker_image
                    - helm_chart
                port:
                  type: integer
                  minimum: 1
                  maximum: 65535
                  description: Required when `type` is `web` or `internal`
                cron_schedule:
                  type: string
                  maxLength: 100
                  description: Required when `type` is `cron`
                replica_count:
                  type: integer
                  minimum: 0
                  description: Required unless `source` is `helm_chart`
                cpu_count_min:
                  type: number
                  description: Required unless `source` is `helm_chart`
                cpu_count_max:
                  type: number
                memory_mb_min:
                  type: integer
                  description: Required unless `source` is `helm_chart`
                memory_mb_max:
                  type: integer
                prov_node_group_id:
                  type: string
                  format: uuid
                  description: Pins the service to a node group
                is_protected:
                  type: boolean
                  description: >-
                    When true, the service cannot be deleted until protection is
                    removed
                deploy_now:
                  type: boolean
                  description: Trigger a first deployment immediately after creation
                secrets:
                  type: array
                  description: >-
                    Service level secrets, written to the provisioner after
                    creation
                  items:
                    $ref: '#/components/schemas/ServiceSecret'
                installation_id:
                  type: string
                  format: uuid
                  description: >-
                    GitHub App installation record id for `github`. For `gitlab`
                    this field carries the GitLab connection id.
                git_repo_full_name:
                  type: string
                  description: Required for `github` and `gitlab`, in `org/repo` form
                git_repo_branch:
                  type: string
                  description: Required for `github` and `gitlab`
                auto_deploy:
                  type: boolean
                  description: >-
                    Deploy on push. Git sources only, and rejected when `type`
                    is `job`.
                dockerfile_path:
                  type: string
                  description: Git sources only. Trimmed server side.
                build_context:
                  type: string
                  maxLength: 100
                  description: >-
                    Docker build context directory, relative to the repository
                    root. Git sources only. Surrounding whitespace is trimmed
                    and a blank value is treated as unset.
                run_command:
                  type: string
                  description: Git and `docker_image` sources
                ops_json_path:
                  type: string
                  description: Git sources only. Trimmed server side.
                ops_json:
                  type: string
                  description: >-
                    Inline ops JSON. Supported only when `source` is
                    `docker_image`.
                enable_previews:
                  type: boolean
                  description: >-
                    GitHub only, and allowed only when `type` is `web`. Required
                    before PR preview deployments can be triggered.
                enable_code_review:
                  type: boolean
                  description: GitHub only
                docker_registry:
                  type: string
                  description: Registry to pull the image from. `docker_image` source.
                docker_image:
                  type: string
                  description: Required when `source` is `docker_image`
                helm_chart_repo:
                  type: string
                  description: Required when `source` is `helm_chart`
                helm_chart_name:
                  type: string
                  description: Required when `source` is `helm_chart`
                helm_values_yml:
                  type: string
                  description: Contents of `values.yaml` as a single string
            examples:
              helm:
                summary: Helm chart service with secrets
                value:
                  name: checkout api
                  type: web
                  source: helm_chart
                  port: 8080
                  helm_chart_repo: https://charts.example.com
                  helm_chart_name: checkout
                  helm_values_yml: |
                    replicaCount: 2
                    image:
                      tag: 1.4.0
                  secrets:
                    - key: API_KEY
                      value: s3cr3t
                      is_sensitive: true
              docker:
                summary: Docker image worker, deployed immediately
                value:
                  name: worker
                  type: worker
                  source: docker_image
                  replica_count: 1
                  cpu_count_min: 0.25
                  cpu_count_max: 0.5
                  memory_mb_min: 256
                  memory_mb_max: 512
                  docker_registry: 123456789012.dkr.ecr.us-east-1.amazonaws.com
                  docker_image: acme/worker
                  deploy_now: true
              github:
                summary: GitHub service with PR previews
                value:
                  name: storefront
                  type: web
                  source: github
                  port: 3000
                  replica_count: 2
                  cpu_count_min: 0.5
                  memory_mb_min: 512
                  installation_id: 1e2c8f30-0000-0000-0000-000000000000
                  git_repo_full_name: acme/storefront
                  git_repo_branch: main
                  auto_deploy: true
                  enable_previews: true
      responses:
        '200':
          description: Service created
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  data:
                    type: object
                    properties:
                      service:
                        $ref: '#/components/schemas/Service'
              example:
                message: success
                data:
                  service:
                    id: b1f0a9c2-0000-0000-0000-000000000000
                    name: checkout api
                    type: web
                    state: deploy_pending
                    run_status: ''
                    ready_count: 0
                    source: helm_chart
                    subdomain: chec-a1b2c3d4
                    account_id: 6c1d0f7a-0000-0000-0000-000000000000
                    private_instance_id: 9a3b2c10-0000-0000-0000-000000000000
                    helm_chart_repo: https://charts.example.com
                    helm_chart_name: checkout
                    helm_values_yml: |
                      replicaCount: 2
                    created_at: '2026-08-10T09:12:33Z'
                    updated_at: '2026-08-10T09:12:33Z'
                    kube_svc_alias: ''
                    kube_secret: ''
                    origin: https://chec-a1b2c3d4.space.example.com
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
        '500':
          $ref: '#/components/responses/ServerError'
components:
  schemas:
    ServiceSecret:
      type: object
      required:
        - key
      properties:
        key:
          type: string
          description: Required, non empty after trimming, and unique within the array
        value:
          type: string
        description:
          type: string
        use_preview:
          type: boolean
          description: Also inject this secret into PR preview services of this service
        is_sensitive:
          type: boolean
        expose_to_chart:
          type: boolean
          nullable: true
        chart_default_val:
          type: string
          nullable: true
        dep_export:
          type: boolean
          nullable: true
        updated_at:
          type: string
          format: date-time
          readOnly: true
    Service:
      type: object
      description: >-
        A service inside an environment. Fields that are unset are omitted from
        the response.
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        type:
          type: string
          nullable: true
          enum:
            - web
            - internal
            - worker
            - cron
            - job
        state:
          type: string
          enum:
            - deploy_pending
            - deploy_queued
            - deploying
            - deploy_failed
            - running
            - delete_pending
            - delete_queued
            - deleting
            - deleted
            - delete_failed
          description: Lifecycle state, mirrored from the provisioner
        run_status:
          type: string
          description: >-
            Runtime status reported by the provisioner. Empty unless the
            response hydrates live state.
        ready_count:
          type: integer
          description: >-
            Number of ready replicas. Empty unless the response hydrates live
            state.
        replica_count:
          type: integer
          nullable: true
        cpu_count_min:
          type: number
        cpu_count_max:
          type: number
        memory_mb_min:
          type: integer
        memory_mb_max:
          type: integer
        source:
          type: string
          enum:
            - github
            - gitlab
            - docker_image
            - helm_chart
        port:
          type: integer
          nullable: true
        cron_schedule:
          type: string
          nullable: true
        auto_deploy:
          type: boolean
          description: Deploy on push. Git sources only.
        gitlab_connection_id:
          type: string
          format: uuid
        git_repo_full_name:
          type: string
        git_repo_branch:
          type: string
        dockerfile_path:
          type: string
        build_context:
          type: string
          description: >-
            Docker build context directory, relative to the repository root. Git
            sources only, omitted when unset.
        run_command:
          type: string
        ops_json_path:
          type: string
        ops_json:
          type: string
          description: Inline ops JSON. Docker image and Helm chart sources only.
        docker_registry:
          type: string
        docker_image:
          type: string
        helm_chart_repo:
          type: string
        helm_chart_name:
          type: string
        helm_values_yml:
          type: string
        prov_node_group_id:
          type: string
          format: uuid
          nullable: true
        account_id:
          type: string
          format: uuid
        private_instance_id:
          type: string
          format: uuid
          description: Id of the environment this service belongs to
        subdomain:
          type: string
          description: >-
            Generated by LocalOps as `<first 4 chars of name>-<8 char id>`. Not
            accepted on create.
        is_protected:
          type: boolean
          description: When true, the service cannot be deleted until protection is removed
        is_deleted:
          type: boolean
        latest_deployment:
          type: object
          nullable: true
          description: Metadata of the most recent deployment, when the edge is loaded
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        kube_svc_alias:
          type: string
          description: >-
            In cluster DNS alias. Only hydrated by `GET
            /v1/environments/{envId}/services/{serviceId}`.
        namespace_name:
          type: string
          description: >-
            Kubernetes namespace the service runs in. Only hydrated by `GET
            /v1/environments/{envId}/services/{serviceId}`, and omitted until
            the service is provisioned.
        ip_whitelist:
          type: array
          items:
            type: string
          description: >-
            CIDRs allowed to reach the service. Only hydrated by `GET
            /v1/environments/{envId}/services/{serviceId}`, and omitted when the
            service is unrestricted.
        ops_dependencies:
          type: array
          items:
            $ref: '#/components/schemas/OpsDependency'
          description: >-
            Cloud resources declared in the service's ops.json. Only hydrated by
            `GET /v1/environments/{envId}/services/{serviceId}`, where a service
            that declares none gets an empty array.
        kube_secret:
          type: string
        kube_dep_secret:
          type: string
        kube_preview_dep_secret:
          type: string
        custom_domain_id:
          type: string
          format: uuid
          description: Zero UUID when no custom domain is attached
        custom_domain:
          type: object
          nullable: true
        origin:
          type: string
          description: Public URL of the service, or the custom domain when one is attached
        parent_service_id:
          type: string
          format: uuid
          nullable: true
          description: Set on preview services
        enable_previews:
          type: boolean
        enable_code_review:
          type: boolean
        is_preview:
          type: boolean
        pr_number:
          type: integer
          nullable: true
        pr_comment_id:
          type: integer
          nullable: true
    OpsDependency:
      type: object
      description: >-
        A cloud resource declared in the service's
        [ops.json](/environment/services/ops-json), reported as it was declared.
      properties:
        id:
          type: string
          description: >-
            Resource identifier from ops.json. Free form, not a UUID - for
            example `orders-primary`.
        kind:
          type: string
          description: >-
            Resource kind, for example `s3`, `rds`, `elasticache` or `sqs`, or
            provider prefixed like `gcp:cloudsql`. New kinds are added over
            time, so treat this as an open set.
        exports:
          type: object
          additionalProperties:
            type: string
          description: >-
            Environment variable name to the resource attribute it resolves to,
            for example `{"DATABASE_URL": "$dsn"}`. The `$...` tokens are
            resolved when the variables are injected into your service; this
            response returns them verbatim.
      example:
        id: sessions
        kind: elasticache
        exports:
          REDIS_HOST: $address
          REDIS_PORT: $port
    Error:
      type: object
      properties:
        error_code:
          type: string
          enum:
            - unauthorized
            - forbidden
            - notfound
            - conflict
            - validation
            - unknown
          description: >-
            Machine readable error code. Treat this, and not the HTTP status, as
            the signal for whether the request was at fault - some business rule
            violations are returned as `500` with `error_code: validation`.
        message:
          type: string
          description: Human readable error message
        errors:
          type: array
          nullable: true
          description: >-
            Per field details. Present only on validation errors, and can be
            null when the failure has no field level detail.
          items:
            $ref: '#/components/schemas/FieldError'
    FieldError:
      type: object
      properties:
        field:
          type: string
          description: Name of the request field that failed validation
        error:
          type: string
          description: Reason the field was rejected
  responses:
    Unauthorized:
      description: Missing, malformed or unknown API token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error_code: unauthorized
            message: You are not authorized to do this action
    Forbidden:
      description: >-
        The account has no active plan, the plan does not include the requested
        feature, or the service is being deleted
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error_code: forbidden
            message: You don't have an active plan
    NotFound:
      description: >-
        The environment, service, deployment, custom domain or connection does
        not exist, or belongs to another account
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error_code: notfound
            message: Service not found
    ValidationError:
      description: >-
        Field validation failed, or the request body was missing or malformed.
        Endpoints that take a body always need at least `{}` - a zero byte body
        fails to bind.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            field:
              summary: Field validation
              value:
                error_code: validation
                message: Invalid data
                errors:
                  - field: replica_count
                    error: replica_count is a required field
            body:
              summary: Missing or malformed body
              value:
                error_code: validation
                message: Please check your request body
    ServerError:
      description: >-
        Unexpected failure. Some business rule violations are also returned here
        with `error_code: validation` - check `error_code` rather than the
        status to decide whether the request was at fault.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            unknown:
              summary: Unexpected failure
              value:
                error_code: unknown
                message: Something went wrong. Please try again later
            rule:
              summary: Business rule violation
              value:
                error_code: validation
                message: Ops Json is only supported for docker image source
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: UUID
      description: >-
        Your account API token, sent as `Authorization: Bearer <api_token>`.
        Owners and admins can read the token from the LocalOps console.

````