Release Notes

Upcoming Qiskit Serverless release

New Features

  • BackgroundExecutor.submit accepts an optional replace_key: a task that is still waiting with the same key in the same pool is replaced by the new one, which takes its place in the queue and needs no new slot. There is a new outcome replaced in background_executor_tasks_total, and BackgroundExecutor.would_accept tells whether a submit would be accepted. The outcome skipped of that metric is now called canceled.

  • The backoffice “Stop job” button now works on a Fleets job. It cancels the job’s Code Engine fleet and records STOPPING, and the scheduler confirms the stop like any other. Before, the button was only shown for Ray jobs, so a Fleets job could not be stopped from the backoffice at all. The job is billed for what it really ran, the same as a stop from the client.

  • BackgroundExecutor.discard(pool, replace_key) removes a task that is still waiting in the queue under that replace_key, so a result that is no longer needed does not take a thread. A task that already started is never touched. The discarded task is counted as canceled.

  • The scheduler no longer submits a queued Fleets job whose account, plan and subscription are blocked. The job stays QUEUED and does not take a free slot from other users, and it is submitted normally once the block is lifted.

  • GET /api/v1/jobs/{id}/ now returns a short-lived job status token in the X-Job-Status-Token header, to the author of a job that has not ended. The new GET /api/v1/jobs/{id}/status/ accepts only that token, and returns the job status, sub_status and fleet_id from two small database reads, with no IBM Cloud call. The token is valid for 5 minutes.

  • status() and wait_for_final_state() now use the job status token when the gateway returns one. So most status checks no longer send the API key. The client goes back to the API key when the token is older than 4 minutes and 30 seconds, or when the gateway refuses it. With an older gateway nothing changes.

  • Added wait_for_final_state(), which waits until a job is DONE, ERROR or CANCELED and returns that status:

    job.wait_for_final_state(timeout=600)
    # 'DONE'
    

    It checks the status after 1, 2, 4, 8, 16 and 32 seconds, then every 60 seconds, and starts again from 1 second when the main status changes or the job enters post-processing. So a short job is seen within seconds, and a long job makes fewer requests than a loop that checks every few seconds. If the status does not change for a while, the end can be seen up to about 60 seconds late. poll_interval sets a fixed interval instead, of at least 1 second.

    A timeout raises TimeoutError and never cancels the job. Short network errors and 429, 502, 503 or 504 answers are retried at the same pace, until the timeout, or for up to 5 minutes in a row when there is no timeout. Any other error, or one that lasts that long, is raised. verbose=True prints one line per status change and per retried error, and the error message if the job ends in ERROR.

  • status() now asks the service only until the job reaches DONE, ERROR or CANCELED, and then returns that status without a request, because a final status cannot change. So calling result() or error_message() on a finished job no longer sends the same status request again and again.

  • Fleets jobs can now be mirrored to the Runtime API as workloads. While the workloads.mirror.enabled setting is on (it is off by default), each new Fleets job that has an instance CRN is sent to the Runtime API when it is created, and its final status is sent when it ends. A job created before the setting was turned on is not mirrored, and turning the setting off does not stop the jobs already mirrored from reporting their final status. Inside the container of a mirrored job, the environment variable QISKIT_FUNCTIONS_IDENTIFIER holds the job id. Instead of true the setting can be a comma separated list of providers (qctrl), functions (qctrl/management) and custom (functions without a provider), and only the jobs of those are mirrored.

  • The scheduler exports three new metrics: scheduler_code_engine_request_duration_seconds by endpoint and status, scheduler_cos_request_duration_seconds by operation and status, and scheduler_fleets_breaker_open by dependency and region. A Code Engine 429 shows up as a status="429" series. The gateway does not record them.

  • The scheduler exposes scheduler_loop_duration_max_seconds and scheduler_task_duration_max_seconds (per task): the longest duration among the loops and tasks that ended in the last 120 seconds, so one very slow loop can be read in Grafana without averaging. A loop that is still running does not show up until it ends.

  • The scheduler now records how long it takes to submit one Fleets job, as the new fleet_submit operation of scheduler_operation_duration_seconds. It covers the whole submit, from the Code Engine request to the job reaching PENDING, and records one sample per attempt, failed ones included, with status ok, timeout or error.

  • The scheduler_operation_duration_seconds histogram has new operations that time the job status transitions the scheduler makes, wherever they are called from: job_transition_queued_to_pending, job_transition_pending_to_running and job_transition_to_terminal. The last one also counts the jobs that end because they are filler jobs or because their fleet is blocked, not only the ones that UpdateFleetsJobsStatuses finishes. job_transition_pending_to_running includes the job_started event sent right after the transaction, so it is not database time alone. Another new operation is job_in_progress_event (the progress event sent for a job that is still running, not a transition, and never for a filler job). Together with fleet_status_read they show how much of UpdateFleetsJobsStatuses is database and how much is Cloud Object Storage. A transition rejected because the job was already moved by someone else (a user stopped it) is recorded with the status rejected and not error. job_transition_to_terminal covers a job that succeeds, fails or is stopped by to_stopped, but not the status written by stop() (the stop of a job by a user or by the timeout), which also cancels the fleet in Code Engine. The gateway records nothing.

  • The scheduler now cancels the PENDING and RUNNING Fleets jobs whose account, plan and subscription are blocked, so a resource whose non-quantum allowance is exhausted stops consuming Code Engine capacity. The job goes through STOPPING like any other stop and is billed for what it really ran. The scheduler.blocked_jobs_stop.enabled configuration turns this off at runtime without a deploy.

Upgrade Notes

  • qiskit-paulice==0.2.1 has been added to the dynamic dependencies allowlist bundled in the custom functions image. Qiskit Functions that declare it as a dependency can now use it.

  • A stop from the backoffice no longer writes STOPPED on the job row by hand. It goes through the same transition service as every other stop, so an operator sees an error instead of a silent write when Code Engine cannot be reached. The Qiskit Runtime jobs a job started are not cancelled from the backoffice: that needs the caller’s own service credentials.

  • The api_job.compute_profile database column has been dropped, along with its historical data. The gateway stopped reading and writing it in 0.37.0 and reads the compute profile from Job.compute_profile_fk instead.

    Gateway releases before 0.37.0 can no longer be deployed against a database that has run this migration, because their Job model still declares the column. Upgrade to 0.37.0 before upgrading to this release.

  • Migration 0078 fills function_size_name for existing jobs with the current label of their size row. A row renamed before the upgrade gives its new label, because the old one was never stored. Jobs whose row was already deleted stay without a size.

  • While workloads.mirror.enabled is on, running a Fleets function with an instance CRN can fail with a 503 error, and no job is created, if the Runtime API does not accept the job. Retrying the request later is safe.

  • scheduler_loop_duration_seconds and scheduler_task_duration_seconds now have buckets up to 30 minutes. The old buckets stopped at 10 seconds.

  • scheduler_loop_duration_seconds and scheduler_task_duration_seconds have three more buckets, 3600, 7200 and 14400 seconds.

Security Issues

  • A job status token lets its holder read the status of one job for up to 5 minutes, even if the API key is revoked in that time. It only gives the status, sub-status and fleet id of a job the user authored. A deactivated user cannot use it.

Bug Fixes

  • The gateway and the scheduler now reuse a Code Engine Fleets project’s COS client for 60 seconds. Before, every status poll of every running job and every COS request made its own Code Engine call to read the COS HMAC secret, so a busy scheduler could reach the Code Engine API rate limit by itself. A rotated HMAC secret is picked up within 60 seconds.

  • A request that reads or writes a Code Engine Fleets job’s files in COS now answers 503 “please retry” when Code Engine rate limits the gateway, answers a 5xx, or does not answer in time. It used to answer 500. This covers running a function, and reading its logs, result and files. Reading the COS HMAC secret from Code Engine now has a request timeout, so such a request no longer hangs.

  • A failed request for an IAM token that the caller retries is now logged as a warning instead of an error. The wait for IAM is 3 seconds to connect and 5 seconds to read, as it was for Code Engine and COS before they shared the IAM token provider, instead of 3 seconds for both. A rejected API key is still logged as an error.

  • A Fleets job now keeps the size it ran with. The new Job.function_size_name column stores the size label when the job is created, and the license fee, the Runtime API mirror payload, ENV_JOB_FUNCTION_SIZE and the admin job pages read it instead of the Job.function_size row. Before, renaming that row in the backoffice, or deleting it by re-uploading the function with a different sizes map, changed the size of every job still running with it, so the license fee was charged at the new size’s rate or waived. Job.function_size is kept as a link to the catalog row.

  • The scheduler now measures the duration of its loop, its tasks and its internal waits with a monotonic clock instead of the wall clock, so a clock adjustment (NTP correction, VM pause) can no longer show up as a very long loop or task in the scheduler metrics. The scheduler_start_timestamp_seconds and scheduler_last_tick_timestamp_seconds metrics stay on the wall clock.

  • Authentication now writes only the user’s groups that changed, so a request whose groups did not change makes no database write. This also removes a short window in which a parallel request from the same user could see no groups.

  • The authentication errors that asked users to try again later, where retrying alone cannot help, now say what to check first, or to contact IBM support.

Other Notes

  • The scheduler now exposes scheduler_operation_duration_seconds, a histogram of how long it takes to read the status of a Fleets job, to upload the files of a job when submitting it, to request an IAM token and to send a workload to the Runtime API. It is labeled by operation and by status: ok, timeout or error, or the HTTP code for the Runtime API call. Only the requests that really reach IAM are timed, not the ones answered from the token cache. The gateway does not record it.

  • The IAM token used for Cloud Object Storage and the Runtime API is now renewed in a background thread when less than 40% of its life, or 10 minutes, is left, so a request no longer waits for the renewal.

  • The scheduler now exposes scheduler_task_items_total (jobs submitted and jobs whose status was checked, per task), scheduler_fleets_jobs_skipped_by_breaker_total (jobs a Code Engine circuit breaker kept queued, per region) and scheduler_process_threads (live threads of the process).

  • GET /api/v1/jobs/{id}/ now reads the job, its program, its provider and its compute profile in one database query.

v0.37.0

Prelude

qiskit_serverless v0.37.0 tightens circuit serialization: the gateway now injects an authoritative Qiskit configuration into every job and enforces a minimum QPY version of 13 (Qiskit 1.3.0), so circuits serialized with an older Qiskit are rejected at load time. Cancelling a job now reports a new STOPPING status until the compute is actually released, instead of being reported as stopped straight away. The deprecated compute_profile input on run() is removed in favour of function_size, whose label is restricted to S, M, L and XL. This release also adds a provider filter to function listing, bundles the CUDA runtime libraries and qiskit-fermions for GPU chemistry in the custom functions image, and raises the required qiskit-ibm-runtime version to 0.50. Finally, it removes the Ray-coupling client helpers that were deprecated in 0.34.0 (the distribute_task decorator, the get, put and get_refs_by_status helpers, distribute_qiskit_function and qiskit_serverless.core.remote); functions that need in-function parallelism should use Ray directly.

New Features

  • Running Qiskit Functions can now read the compute sizing their job resolved to, with two new helpers, get_function_size() and get_compute_profile():

    from qiskit_serverless import get_function_size, get_compute_profile
    
    get_function_size()     # e.g. "M"
    get_compute_profile()   # e.g. "24x120x1a100p"
    

    This lets a function check which function_size the user submitted and which compute profile it mapped to. Both are always set for Fleets jobs (the requested size, or the function’s default when none was requested) and are empty when sizes do not apply, i.e. a non-Fleets (Ray) job. The values are set by the gateway at job submission through the ENV_JOB_FUNCTION_SIZE and ENV_JOB_COMPUTE_PROFILE environment variables.

  • qiskit-fermions==0.2.0 has been added to the dynamic dependencies allowlist bundled in the custom functions image. Qiskit Functions that declare it as a dependency can now use it.

  • The custom functions image now ships the CUDA runtime libraries gpu4pyscf needs at import, so GPU chemistry functions no longer have to install those wheels themselves.

  • The gateway now injects an authoritative Qiskit user configuration file (settings.conf) into every Ray and Code Engine Fleets job container and points QISKIT_SETTINGS at it. The file currently sets a single key, min_qpy_version (default 13, configurable fleet-wide with the QISKIT_MIN_QPY_VERSION environment variable), which makes Qiskit reject QPY payloads older than that version. For Fleets the file is written by the job wrapper at container startup; for Ray it is written into the job working directory and referenced by a cwd-relative QISKIT_SETTINGS.

  • Listing Qiskit Functions now accepts a provider argument that returns only the functions belonging to that provider:

    client.functions(provider="q-ctrl")
    

    The filter narrows the functions the caller can already see, so it never widens access, and a provider the caller has no functions under returns an empty list.

  • Cancelling a job now reports a new STOPPING status, shown by the client SDK as CANCELING, until the job has really stopped and its compute is released. Previously the job was reported as stopped straight away, while its compute was still being released. Sub-status and error reporting keep working while a job is stopping.

Upgrade Notes

  • Qiskit Serverless now requires qiskit-ibm-runtime==0.50, raised from 0.49.

  • The optional ray extra (pip install qiskit-serverless[ray]) now requires ray>=2.59, raised from 2.58.

  • Several packages have been added to the dynamic dependencies allowlist bundled in the custom functions image: h5py, mpi4py, openmpi, pybind11 and qrmi[ibm]. Qiskit Functions that declare any of them as a dependency can now use them. ffsim is updated to 0.0.70 and qiskit-addon-sqd to 0.13.1.

  • qiskit-fermions==0.2.0 requires qiskit~=2.5.0, so the qiskit version in the custom functions image is now held to the 2.5 line. Functions that relied on an earlier qiskit in the image should review the Qiskit 2.3-2.5 release notes for API changes.

  • The gateway-provided settings.conf is authoritative: a function’s own Qiskit configuration file is no longer consulted in the Functions runtime. Qiskit reads exactly the one file QISKIT_SETTINGS names, with no merging, and the gateway sets QISKIT_SETTINGS in the system-controlled environment layer that overrides any value a function supplies. A function that shipped or wrote its own settings.conf (at the default ~/.qiskit/settings.conf or elsewhere) will find it ignored. Function vendors that need Qiskit configuration beyond min_qpy_version must apply it programmatically in their function code rather than through a configuration file.

  • The distribute_task decorator and the get, put and get_refs_by_status helpers have been removed. They were deprecated in 0.34.0 as thin wrappers around Ray’s native API, which must now be used directly. Functions that need in-function parallelism should import ray and use Ray’s own decorators:

    Before (using the removed Qiskit Serverless wrappers):

    from qiskit_serverless import get_arguments, save_result, distribute_task, get
    
    @distribute_task()
    def distributed_transpilation(circuit, target_backend):
        ...  # returns an ISA circuit
        return isa_circuit
    
    refs = [distributed_transpilation(circuit, backend) for circuit in circuits]
    isa_circuits = get(refs)
    save_result({"results": isa_circuits})
    

    After (using Ray directly):

    import ray
    from qiskit_serverless import get_arguments, save_result
    
    @ray.remote
    def distributed_transpilation(circuit, target_backend):
        ...  # returns an ISA circuit
        return isa_circuit
    
    refs = [distributed_transpilation.remote(circuit, backend) for circuit in circuits]
    isa_circuits = ray.get(refs)
    save_result({"results": isa_circuits})
    
  • The distribute_qiskit_function decorator has been removed. It was deprecated in 0.30. Use the upload functionality of ServerlessClient instead.

  • qiskit_serverless.core.remote, a thin proxy around ray.remote, has been removed. Use ray.remote directly, as shown above.

  • The OpenTelemetry spans that distribute_task injected around each distributed task have been removed along with it, including the per-task qs.meta.*, qs.resources.*, qs.args.* and qs.kwargs.* span attributes. The client-side job.*, function.* and files.* spans, and gateway and scheduler tracing, are unaffected.

  • The deprecated compute_profile input on POST /programs/run/ has been removed. Use function_size with one of the labels the function declares instead. Sending compute_profile now returns a 400 naming the replacement, instead of being silently ignored.

    The client SDK’s compute_profile keyword on run() now raises QiskitServerlessException locally instead of sending the request, so upgrade the client to get a clear error rather than a generic 400 from the gateway.

  • The size label declared for a function is now restricted to S, M, L and XL. A size outside this catalog is rejected on /programs/upload. A size is accepted case-insensitively and stored and read back in uppercase.

  • A cancelled job stays in STOPPING until its compute is released, and counts against the user’s active and running job limits for that time. A cancellation that is never confirmed is forced to stopped after 5 minutes.

  • Stopping a job now answers “Serverless job stop has been requested.” where it used to say the job “was running and has been stopped”, since the request only asks for the stop and does not wait for it to finish.

Security Issues

  • The custom_token authentication mechanism no longer accepts the local channel, which checks a static mock token and needs no instance. The Helm chart also stops passing SETTINGS_AUTH_MOCK_TOKEN to the pods unless the mechanism is mock_token.

Bug Fixes

  • A request whose authentication could not be completed because IBM Cloud was unreachable, timed out, or returned a server error now fails with HTTP 503 and a message asking the user to retry, instead of a 401 telling them to review their API key or CRN. Valid credentials and a valid CRN are no longer reported as invalid during an IBM Cloud outage; the 401 messages are kept only when IBM Cloud actually rejects the API key or the CRN.

  • A request that fails to reach the service because the connection was dropped, the TLS handshake failed, or the request timed out now raises a QiskitServerlessException with code CONN1001 and a message saying the failure is usually temporary and to try again. These transient failures were previously reported with code AUTH1001 and a message telling the user to check their host and authentication, which pointed at configuration that was not the problem.

  • A successful response that arrives with an empty body, which happens on a transient blip and clears on retry, now raises a QiskitServerlessException with code EMPTY1001 and a message saying to try again. It was previously reported with code JSON1001 as a server json decoding error, which read as a permanent problem.

  • The gateway and the scheduler now exit when their startup migration fails, instead of starting against a database that does not match the code.

  • Fixed GPU chemistry functions silently running on the CPU, because the CUDA libraries gpu4pyscf loads at import were missing from the image. The image now installs them and its build checks that they resolve.

  • A request to the IBM Cloud and IBM Quantum Platform channels without the Service-CRN header now fails with an authentication error that asks for the header, instead of an internal server error.

v0.36.0

Prelude

qiskit_serverless v0.36.0 updates the pinned qiskit-ibm-runtime requirement to 0.49.0 and refreshes the client, gateway and test dependency groups. The rest of the release is internal platform work and is not part of the public surface; upgrading is optional for users of the Python client.

Upgrade Notes

  • qiskit-ibm-runtime is now required at 0.49.0 (raised from 0.47.0). Environments that pin the client’s transitive dependencies should widen the constraint accordingly.

v0.35.1

Prelude

qiskit_serverless v0.35.1 is a bug-fix release with a few improvements over v0.35.0; upgrading is advised but not required. GET /api/v1/jobs/provider now scopes its results to the requested provider, running a Qiskit Function with a large batch of arguments no longer fails with a 500 error, and get_runtime_service() is now compatible with VPE configurations. The author of a provider function is also now treated as an admin of that function for the provider operations, so an automated publisher can manage the functions it uploads.

Upgrade Notes

  • Two new settings, both also exposed as chart values under gateway.application.limits: MAX_REQUEST_BODY_SIZE_MB (maxRequestBodySizeMb, 50) and MAX_ARGUMENTS_LENGTH_MB (maxArgumentsLengthMb, 32). Size them against the pod’s memory and worker count, since a body costs about three times its size in the worker holding it and every worker can hold one at once. specs/ARGUMENTS_LIMIT.md has the table.

  • The author of a provider function is now treated as an admin of that one function for the six provider operations: uploading a new version, listing the function’s jobs, reading its provider logs, and the three provider file operations. This lets an automated publisher manage the functions it uploads without extra grants.

    The grant is scoped to that single function. It does not list the function in the catalog, does not grant the right to run it, and does not make the author an admin of the provider as a whole. Creating a brand-new provider function still requires the upload permission, and access that worked before is unaffected.

Bug Fixes

  • Running a Qiskit Function with a large batch of arguments no longer fails with 500 Internal server error. The gateway now accepts request bodies of up to 50 MB, and reports going over that as 413 Request Entity Too Large naming the limit.

  • Raised the maximum length of the arguments validated against a function’s arguments_schema from 1 MB to 32 MB. The old figure fit only about twenty-five encoded circuits, fewer than callers send in a single batch.

  • get_runtime_service() now honours the QISKIT_IBM_PRIVATE_ENDPOINT environment variable, forwarding it to QiskitRuntimeService as private_endpoint. Previously the helper ignored it, so a Qiskit Function configured for a private endpoint still connected through public URLs. Set it to true and the service connects through private URLs; the instance must be a CRN for the private host to resolve. Existing usage is unchanged.

    Example usage from within a Qiskit Function, with the variable set in the environment:

    from qiskit_serverless import get_runtime_service
    
    service = get_runtime_service()
    backend = service.backend("my_backend")
    
  • GET /api/v1/jobs/provider now scopes its results to the requested provider. Because function titles are unique per provider rather than globally, a title alone could match same-named functions under other providers, so the listing could include jobs outside the requested provider. It now returns only jobs belonging to that provider. The response carries job metadata only (id, status, timestamps and the function’s id, title and provider); retrieving a job, its results or its provider logs is authorized separately against the job’s own provider and is unchanged.

  • GET /api/v1/jobs?filter=catalog&provider=<name> no longer returns 500 Internal server error when the provider is given by name.

v0.35.0

Prelude

qiskit_serverless v0.35.0 is a feature release that adds argument validation for Qiskit Functions: functions can now declare a JSON Schema for their arguments and the gateway validates submissions before any compute is used, with a new validate_arguments client method for pre-flight checks. The QiskitFunction.schema field has been renamed to arguments_schema. This release also removes the deprecated name parameter from ServerlessClient and bumps the ray-node default Python version to 3.12.

New Features

  • Qiskit Functions can now declare the arguments they accept as a JSON Schema, and the gateway validates submitted arguments against it before any compute is used. Previously a function given bad input failed mid-execution, after the user had already waited through queue time and container startup.

    Attach the schema when uploading a function through the arguments_schema field of QiskitFunction:

    function = QiskitFunction(
        title="my-function",
        entrypoint="main.py",
        working_dir="./src",
        arguments_schema={
            "type": "object",
            "required": ["shots"],
            "properties": {"shots": {"type": "integer"}},
        },
    )
    runnable = client.upload(function)
    

    From then on, running that function with arguments that do not match the schema is rejected with a 400 before the job is created, so no job is queued and no partial rows are left behind. The error names the keyword that failed, what the schema asked for, and the value that was rejected, and path points at the offending field:

    {
      "message": "'type' validation failed, schema requires 'integer': rejected value 'wrong-type'",
      "path": ["shots"]
    }
    

    The feature is opt-in and backwards compatible: functions without a schema are never validated.

    Re-uploading a function without the field keeps the schema it already had, so a schema is removed by uploading an empty one:

    function.arguments_schema = {}
    client.upload(function)
    
  • Two things to know when writing a schema. First, the schema is applied to the arguments as the SDK encodes them, not as they look in Python, so a QuantumCircuit arrives as {"__type__": "QuantumCircuit", "__value__": "<base64 QPY>"} and a numpy array as an object rather than an array. Describing a circuit or an array argument with {"type": "array"} therefore rejects every legitimate call. The advice is to constrain the plain arguments (counts, names, options, flags) and to check only the __type__ tag of the Qiskit ones.

  • New endpoint POST /api/v1/programs/validate_arguments/ checks arguments against a function’s schema without submitting a run, so inputs can be verified before paying for a job. It resolves the function with the same permission rules as /run and returns 200 {"valid": true} or a 400 describing the problem.

    From the client SDK:

    runnable.validate_arguments({"shots": 1024})
    

    Invalid arguments raise QiskitServerlessException.

  • Functions read back with client.function(...) or client.functions() now include arguments_schema, so the arguments a function expects can be inspected even by callers who did not upload it.

Upgrade Notes

  • QiskitFunction.schema has been renamed to arguments_schema and its type changed from Optional[str] to Optional[Union[Dict[str, Any], bool]]. The old field was never read by the client nor sent to the gateway, so there is no deprecation path. Pass a plain dict rather than a JSON string. A boolean is accepted because true and false are valid JSON Schemas in their own right, accepting and rejecting every argument respectively.

  • The unused validate field has been removed from QiskitFunction. It gated a client-side validation hook that was never implemented and always reported success, so passing validate=False had no effect. Argument validation now happens on the gateway against arguments_schema.

  • The supported range for qiskit-ibm-runtime has been updated to >=0.47.0, <0.50.0.

  • The default Python version in the ray-node image has been bumped from 3.11 to 3.12. The Dockerfile now uses ARG PYTHON_VERSION=3.12 so the version can still be overridden at build time if needed.

  • The Apple Silicon local-development image is no longer a separate ray-node/Dockerfile.arm64 file. It is now the arm64 stage of ray-node/Dockerfile and is built with --target arm64 --platform linux/arm64. The docker-compose-dev.arm64.yaml override has been updated accordingly; no changes are needed to existing local-dev workflows beyond rebuilding the image.

  • The name attribute has been removed from ServerlessClient and BaseClient. This parameter was deprecated in v0.33.0 and emitted a DeprecationWarning; it is now fully removed. Passing name when constructing a client will raise a TypeError.

    Migration guide

    Remove the name argument from any ServerlessClient (or BaseClient) construction. It was never used for authentication or routing, so it can be deleted without any replacement.

    Before:

    client = ServerlessClient(
        name="my-client",
        host="<HOST>",
        token="<TOKEN>",
        instance="<CRN>",
    )
    

    After:

    client = ServerlessClient(
        host="<HOST>",
        token="<TOKEN>",
        instance="<CRN>",
    )
    

    Additional notes for existing code:

    • ServerlessClient.from_dict(...) continues to accept dictionaries that still contain a name key; the key is silently ignored, so previously serialized clients keep deserializing without changes.

    • BaseClient.__eq__ now compares clients by their host, token, instance, and channel instead of by name. Code that relied on name-based equality should compare the relevant connection attributes directly.

    • repr(client) now renders as <ServerlessClient(host=<HOST>)> instead of <my-client>. Update any code or tests that matched the old representation.

    • The name attribute on IBMServerlessClient (used to select a saved account) is unaffected by this change.

Bug Fixes

  • Requesting a job that does not exist through GET /api/v1/jobs/{job_id}/ now returns HTTP 404 with a Job [job_id] not found message instead of HTTP 500. The lookup relied on a None check that the Django ORM never triggers, so Job.DoesNotExist escaped as an unexpected error.

v0.34.0

Prelude

qiskit_serverless v0.34.0 makes ray an optional client dependency (install with pip install qiskit-serverless[ray] when needed), raises the minimum Qiskit version to 2.2, and removes the previously deprecated widget() interface and qiskit_serverless.visualization module. GPU scheduling is now configured via a gpu flag on the Program model, replacing the provider allowlist. This release also deprecates the distribute_task, get, put, and get_refs_by_status helpers in favour of their Ray equivalents, and includes a range of client bug fixes and UX improvements.

New Features

  • Added a gpu boolean field to the Program model. When a program is owned by a provider and has gpu enabled, jobs run from that program are now scheduled with GPU resources. This replaces the previous provider allowlist mechanism, which determined GPU eligibility from a static configuration instead of a per-program flag.

Upgrade Notes

  • cotengrust==0.2.0 is now included in the dynamic dependencies allowlist bundled in the Ray runner image. Qiskit Functions that declare cotengrust as a dependency can use it for tensor network contraction path optimization.

  • The minimum supported Qiskit version for the qiskit-serverless client is now 2.2 (raised from >=1.4). Since Qiskit 2.1, symengine is no longer a Qiskit dependency, so the client no longer installs it. Reading legacy symengine-encoded QPY payloads (formats 10/11/12 written by Qiskit 1.0–1.1 with use_symengine=True) is no longer supported; QPY produced by modern Qiskit is unaffected.

  • Bumped qiskit-addon-aqc-tensor[quimb-jax] from 0.3.0 to 0.3.1 in the dynamic dependencies allowlist bundled in the Ray runner image. Qiskit Functions can now declare and use this newer version.

  • Custom image names must now be nested under the provider registry prefix. The registry check previously used a plain startswith, which could accept look-alike hostnames. The check now requires the image to equal the registry or start with registry/.

    If you use the local development mock provider (registry test), rename your image from test-local-provider-function to test/local-provider-function and update the corresponding docker build -t tag in your local workflow.

  • The GET /api/v1/programs/{id}/get_jobs/ provider listing endpoint now omits the result field, bringing it in line with the other cross-author job listing endpoints. Job results are scoped to their author; provider admins listing jobs across authors see job metadata and status only.

    Provider admins can still read their own job results through the own-scoped job endpoints, which continue to include result.

  • ray was removed from the base client requirements and is now provided by the ray extra (pip install qiskit-serverless[ray]). If you rely on the Ray runner or on the Ray-only helpers (distribute_task, get, put, get_refs_by_status, remote), install the extra. Using any of those symbols without Ray installed now raises a clear ModuleNotFoundError instructing you to pip install qiskit-serverless[ray]. The published Ray node image installs the [ray] extra, so its behavior is unchanged.

  • The deprecated widget() method has been removed from BaseClient, ServerlessClient, and IBMServerlessClient. The visualization module (deprecated in 0.33.0) has been completely removed from the package.

Deprecation Notes

  • The distribute_task decorator and the get, put and get_refs_by_status helper functions have been deprecated and will be removed in following releases. They were thin wrappers around Ray’s native API, which should now be used directly instead:

    To migrate, import ray inside your function and replace the decorator and helpers with their Ray equivalents. You do not need to declare ray as a function dependency: it is already provided by the Qiskit Serverless runtime image that executes your function.

    Before (using the deprecated Qiskit Serverless wrappers):

    from qiskit_serverless import (
        get_arguments,
        save_result,
        distribute_task,
        get,
    )
    
    @distribute_task()
    def distributed_transpilation(circuit, target_backend):
        ...  # returns an ISA circuit
        return isa_circuit
    
    # launch one task per circuit and collect the results
    refs = [distributed_transpilation(circuit, backend) for circuit in circuits]
    isa_circuits = get(refs)
    save_result({"results": isa_circuits})
    

    After (using Ray directly):

    import ray
    from qiskit_serverless import get_arguments, save_result
    
    @ray.remote
    def distributed_transpilation(circuit, target_backend):
        ...  # returns an ISA circuit
        return isa_circuit
    
    # launch one task per circuit and collect the results
    refs = [distributed_transpilation.remote(circuit, backend) for circuit in circuits]
    isa_circuits = ray.get(refs)
    save_result({"results": isa_circuits})
    

    Uploading the function does not change. Ray is part of the serverless runtime image, so it does not need to be listed in dependencies:

    from qiskit_serverless import QiskitFunction
    
    function = QiskitFunction(
        title="function-with-parallel-workflow",
        entrypoint="function_with_parallel_workflow.py",
        working_dir="./source_files/",
    )
    

    To request resources per task (for example GPUs), pass Ray’s native arguments to the decorator, e.g. @ray.remote(num_cpus=1, num_gpus=1). For more details, see the Ray Core walkthrough.

Bug Fixes

  • Fixed status() returning RUNNING for jobs that report a sub_status (e.g. RUNNING: MAPPING). The sub-status is now forwarded correctly when the top-level status is RUNNING.

  • Authentication failures now surface a clear, actionable message instead of a generic “Credentials couldn’t be verified” error.

    • If your IBM Cloud account is not entitled to use Qiskit Serverless, the error now explains that your plan is not authorised and instructs you to check your account entitlements, without leaking internal plan identifiers.

    • If the IBM Runtime API returns an unexpected error during authentication, the response status is forwarded as a friendly message rather than an opaque 500.

    • The client now passes the server-supplied error message through to the QiskitServerlessException it raises, so the real reason always reaches the caller.

  • The ServerlessClient now automatically removes trailing slashes from the gateway host parameter, preventing URL formatting issues when constructing API endpoints. Users can now provide gateway hosts with or without trailing slashes (e.g., https://gateway.example.com or https://gateway.example.com/).

  • Non-JSON error responses from the gateway (for example an HTTP 403 block page returned by an ingress proxy) no longer surface as a misleading QiskitServerlessException: JSON1001: Expecting value: line 1 column 1 (char 0). The client now checks whether the response indicates success before attempting to parse the body as JSON. When the response is not successful and the body is not valid JSON, the HTTP status code and the raw body text are included in the exception message so the real cause is visible to the caller.

    This affects upload(), result(), logs(), and most other client API calls.

v0.33.0

Prelude

qiskit_serverless v0.33.0 is a feature release that expands the IBMServerlessClient with methods to inspect IBM Quantum backends and runtime usage through the Qiskit IBM Runtime service, and adds pre-flight validation to run() so that quota and backend-access problems surface immediately instead of after a silent queue wait. Cloud (instance) authentication now goes through QiskitRuntimeService to align with the Qiskit Runtime APIs. This release also deprecates the Jupyter widget() interface, the unused client name parameter, and the qiskit_serverless.visualization module, updates dependencies, and fixes a couple of bugs from 0.32.0.

New Features

  • IBMServerlessClient now exposes several methods that wrap the Qiskit IBM Runtime QiskitRuntimeService API to interact with IBM Quantum backends:

    • IBMServerlessClient.backends() — Lists IBM Quantum backends accessible through the configured instance. Results are cached per client instance, making repeated calls within a session inexpensive. Use refresh_cache=True to force a refresh. Wraps QiskitRuntimeService.backends().

    • IBMServerlessClient.backend() — Fetches a single backend by name and updates the cache. More efficient than listing all backends and validates access. Wraps QiskitRuntimeService.backend().

    • IBMServerlessClient.least_busy() — Returns the backend with the fewest pending jobs that matches the specified criteria. This updates the cache with the retrieved backend. Wraps QiskitRuntimeService.least_busy().

    • IBMServerlessClient.usage() — Returns runtime usage information for the active instance, including usage_remaining_seconds and usage_limit_reached. Wraps QiskitRuntimeService.usage().

    All methods include comprehensive error handling and raise QiskitServerlessException with clear error messages when operations fail.

  • IBMServerlessClient.run() performs three pre-flight checks before submitting a job to the gateway, providing early and clear error messages instead of silent queue waits:

    • Usage quota — raises QiskitServerlessException when the instance has no remaining runtime quota (within USAGE_ZERO_EPSILON_SECONDS); emits a UserWarning when quota is low (below USAGE_LOW_THRESHOLD_SECONDS). Transient failures of the usage endpoint produce a warning rather than blocking the run. The low quota warning can be suppressed by passing suppress_low_usage_warning=True to the IBMServerlessClient.run() method.

    • Backend access — when arguments["backend_name"] is set, validates access via a single-backend lookup. Raises QiskitServerlessException if the backend is not found or is inaccessible.

    • Instance has backends — when no backend_name is given, verifies the instance exposes at least one backend (result cached). Raises QiskitServerlessException if none are found.

Upgrade Notes

  • The qiskit-addon-aqc-tensor package bundled in the Ray runner image has been updated from 0.2.0 to 0.3.0 (installed with the quimb-jax extra). Qiskit Functions that rely on qiskit-addon-aqc-tensor should review the addon’s 0.3.0 release notes for any API changes.

  • The minimum supported version of qiskit-ibm-runtime has been raised to 0.47.0 (the supported range is now >=0.47.0,<0.48.0). Users should ensure their environment has a compatible qiskit-ibm-runtime installed before upgrading.

  • Several dependencies bundled in the Ray runner image have been updated:

    • pyscf from 2.11.0 to 2.13.1

    • qiskit-addon-sqd from 0.12.0 to 0.12.1

    Qiskit Functions that rely on these packages should review their respective release notes for any API changes.

  • ServerlessClient.file_download() and ServerlessClient.provider_file_download() now always write the downloaded file into the requested download_location using only the base name of the target file. Any directory component in the target_name argument (for example subdir/result.json or a path-traversal sequence such as ../result.json) is stripped, so the file is written directly under download_location. Code that relied on target_name creating or writing into subdirectories must now pass that subdirectory as part of download_location instead. This prevents a crafted file name from escaping the download location.

  • Two environment variables control the usage quota thresholds used by IBMServerlessClient.run(). Their defaults are provisional and may be adjusted in a future release:

    • USAGE_LOW_THRESHOLD_SECONDS (default 600 s) — warn when remaining quota falls below this value.

    • USAGE_ZERO_EPSILON_SECONDS (default 1 s) — treat remaining quota at or below this as zero and raise an error.

  • Improved docstrings for IBMServerlessClient methods (usage(), backends(), backend(), least_busy(), and _check_usage) to provide clearer descriptions and better examples. All methods now use named parameters when calling the underlying Qiskit IBM Runtime QiskitRuntimeService methods for improved code clarity and maintainability.

  • Updated IBMServerlessClient cloud (instance) authentication to use QiskitRuntimeService for authentication handling. This aligns authentication behavior with Qiskit Runtime APIs and centralizes credential management.

Deprecation Notes

  • The name parameter in BaseClient, ServerlessClient, and IBMServerlessClient is deprecated and will be removed in a future release. The parameter is no longer used for client functionality. Users should remove this parameter from their client instantiation code.

    Note: The name parameter in IBMServerlessClient is NOT deprecated as it is used to load saved accounts.

  • The BaseClient.widget() method (and its subclasses ServerlessClient and IBMServerlessClient) has been deprecated and will be removed in a future release. The Jupyter widget interface is no longer maintained. Use BaseClient.jobs() and BaseClient.functions() directly to retrieve job and function information, or visit the IBM Quantum web dashboard instead.

  • The Widget class in qiskit_serverless.visualization.widget and the entire qiskit_serverless.visualization module have been deprecated and will be removed in a future release. The ipywidgets and ipython dependencies will also be removed at that time.

Bug Fixes

  • Fixed authentication failures for IBM Cloud instances in regions other than the gateway’s default region (for example eu-de). During authentication the gateway resolves accessible functions via the region-scoped Runtime API, but it previously used a single fixed RUNTIME_API_BASE_URL for every CRN. A CRN from a non-default region was sent to the default-region host, which returned 404 Instance not found and failed authentication. The gateway now derives the region from the CRN (the crn:v1:bluemix:public:quantum-computing:<region>:... segment) and routes the functions-access request to the matching regional endpoint. The default region is configurable via the RUNTIME_API_DEFAULT_REGION environment variable (default us-east), which is served by the bare RUNTIME_API_BASE_URL host; other regions use a {region}. host prefix.

  • Fixed result retrieval for provider (custom-image) functions running on the Ray runner. The function writes its result to a provider-scoped path ({username}/{provider}/{title}/results/{job_id}.json, via the RESULTS_PATH environment variable), but the gateway read the result from a username-only path ({username}/results/{job_id}.json). As a result, job.result() could return an empty result for a provider function even though the job completed successfully. RayResultStorage is now provider-aware, mirroring RayArgumentsStorage, so the read path matches where the function writes.

v0.32.0

Prelude

qiskit_serverless v0.32.0 is a feature release that improves telemetry collection, updates dependencies and drops Python 3.9 support. Additionally, it fixes a couple of bugs from 0.31.0.

Upgrade Notes

  • Job time limiting is now enforced while a job is in the PENDING or RUNNING state, rather than being applied once when the job is first queued. This means long-running or stuck jobs will be reliably stopped regardless of how long they spent waiting before execution.

    Jobs that exceed the time limit are transitioned to the STOPPED state and a warning message is logged.

    For administrators: the gateway configuration key programTimeoutDays has been renamed to programTimeoutHours. The default timeout is 24 hours and can be overridden with the PROGRAM_TIMEOUT environment variable.

  • Python 3.9 has reached end-of-life and is no longer supported. The minimum supported Python version is now 3.10. Users still on Python 3.9 should upgrade before installing this release.

  • Qiskit Serverless is now compatible with qiskit-ibm-runtime<0.48.0. Users can upgrade their local installation before using this release:

    pip install "qiskit-ibm-runtime==0.47.0"
    

Bug Fixes

  • The Job.error_message() method now correctly returns error details for jobs that use the events-based error reporting introduced in a previous release, while remaining backward compatible with jobs whose errors were stored in the legacy format.

Other Notes

  • Internal improvements to telemetry collection and observability infrastructure, including better integration of OpenTelemetry instrumentation across the API layer.

0.31.0

Prelude

qiskit_serverless v0.31.0 introduces error event tracking for Qiskit Functions, structured JSON logging across all components, and a range of scheduler observability and reliability improvements.

New Features

  • Added comprehensive error tracking and event management capabilities for Qiskit Functions. This feature enables function developers to send custom error events via send_error() or raise structured exceptions via ServerlessError, and allows users to retrieve and inspect these events for debugging and monitoring purposes.

    Function development

    Function developers can now use two new capabilities:

    • A new send_error() function to register custom error events that will be stored in the gateway and associated with the job. This is particularly useful for tracking application-specific errors, validation failures, or custom error conditions. Each event is represented by a JobEvent object.

      It can be used like this:

      from qiskit_serverless import send_error
      
      # Send a simple error
      send_error(
          code="ERR001",
          message="Invalid input parameter",
          exception="ValueError"
      )
      
      # Send an error with additional context
      send_error(
          code="ERR002",
          message="Circuit optimization failed",
          exception="OptimizationError",
          args={
              "circuit_depth": 150,
              "num_qubits": 127,
              "optimization_level": 3
          }
      )
      
    • A new ServerlessError exception class to raise structured, trackable errors. When a ServerlessError is raised in a function, it is automatically caught by the framework, logged as an ERROR event, and can be retrieved by users through the events API.

      The ServerlessError class provides a structured way to communicate errors with:

      • code: A unique error code for categorization

      • message: Human-readable error description

      • details: Additional context (can be any JSON-serializable data)

      Example usage:

      from qiskit_serverless import ServerlessError
      
      # Raise a structured error
      raise ServerlessError(
          code="VAL001",
          message="Invalid circuit depth",
          details={"max_depth": 100, "provided_depth": 150}
      )
      

    Designing custom error classes

    Function developers can subclass ServerlessError to build domain-specific exception hierarchies. The framework records the name as the exception field in the event, so subclass names appear directly in the events retrieved by users, making errors easier to categorize and filter.

    The simplest form requires no __init__ override:

    from qiskit_serverless import ServerlessError
    
    class ProviderError(ServerlessError):
        """Base error for all errors raised by this provider."""
    
    class ValidationError(ProviderError):
        """Raised when input arguments fail validation."""
    
    class ResourceError(ProviderError):
        """Raised when a required resource is unavailable."""
    

    Raise them exactly as you would ServerlessError:

    raise ValidationError(
        code="VAL001",
        message="num_qubits exceeds the maximum allowed value",
        details={"max": 127, "provided": 256}
    )
    

    On the client side, the exception field of the retrieved event will be "ValidationError" rather than "ServerlessError", which lets users filter precisely:

    error_events = job.events(type="ERROR")
    for event in error_events:
        if event.data.get("exception") == "ValidationError":
            print("Input problem:", event.data["message"])
    

    You can also override __init__ to fix a code prefix or add mandatory fields, so callers only need to provide the varying parts:

    class CircuitError(ProviderError):
        """Raised for circuit-related failures."""
    
        def __init__(self, message: str, circuit_id: str):
            super().__init__(
                code="CIR001",
                message=message,
                details={"circuit_id": circuit_id},
            )
    
    raise CircuitError("Circuit depth exceeds limit", circuit_id="circ-42")
    

    When to use ServerlessError vs send_error

    • Use ServerlessError when you want to stop execution and signal a fatal error that prevents the function from completing successfully. The error will be automatically logged, the job will transition to ERROR status, and the structured error message will be raised as a QiskitServerlessException when the user calls job.result():

      QiskitServerlessException:
      | Message: num_qubits exceeds the maximum allowed value
      | Code: VAL001
      | Exception: ValidationError
      | Details:
      |   - max: 127
      |   - provided: 256
      
    • Use send_error() when you want to log an error but continue execution. This is useful for non-fatal errors, warnings, or when you want to track multiple issues without stopping the function. Events sent this way are still surfaced in job.result() if the job ultimately ends in ERROR status.

    Example combining both approaches:

    from qiskit_serverless import ServerlessError, send_error
    
    def process_circuits(circuits):
        failed = 0
        for circuit in circuits:
            if circuit.num_qubits > 127:
                # Log non-fatal error, continue processing
                send_error(
                    code="SKIP001",
                    message="Circuit exceeds qubit limit",
                    exception="ValidationError",
                    args={"num_qubits": circuit.num_qubits}
                )
                failed += 1
                continue
            process(circuit)
        
        # Raise fatal error if all circuits failed
        if failed == len(circuits):
            raise ServerlessError(
                code="FATAL001",
                message="All circuits failed",
                details={"total": len(circuits)}
            )
    

    Client side

    Users can retrieve job events using the new events() method available on both class:.Job objects and class:.ServerlessClient instances. Events can be filtered by type or retrieved in bulk for comprehensive debugging. Each event is represented by a JobEvent object with the following attributes:

    • event_type: Type of event (e.g., “ERROR”, “STATUS_CHANGE”)

    • origin: Where the event originated (e.g., “API”, “SCHEDULER”, “FUNCTION”)

    • context: Context in which event was raised (e.g., “SEND_ERROR”, “SET_SUB_STATUS”)

    • created: ISO 8601 timestamp of event creation

    • data: Dictionary containing event-specific data (for ERROR events: code, message, exception, args)

    Retrieve all events for a job:

    from qiskit_serverless import ServerlessClient
    
    serverless = ServerlessClient(...)
    job = serverless.run("my-function", arguments={"param": "value"})
    
    # Get all events
    all_events = job.events()
    for event in all_events:
        print(f"Type: {event.event_type}")
        print(f"Created: {event.created}")
        print(f"Data: {event.data}")
    

    To retrieve error events, users can filter events by type:

    # Get only error events
    error_events = job.events(type="ERROR")
    
    for error in error_events:
        print(f"Error Code: {error.data.get('code')}")
        print(f"Message: {error.data.get('message')}")
        print(f"Exception: {error.data.get('exception')}")
        if 'args' in error.data:
            print(f"Additional Info: {error.data['args']}")
    

    On top of the Job methods, users can retrieve events using the client directly:

    # Alternative: use client to get events by job_id
    events = serverless.events(job_id="my-job-id", type="ERROR")
    

    API

    Two new REST API endpoints have been added:

    • GET /api/v1/jobs/{job_id}/events/: Retrieve job events

    • POST /api/v1/jobs/{job_id}/event/: Create a new job event

Upgrade Notes

  • The gateway, scheduler, and core components now emit structured JSON logs by default. Operators using log aggregation tools (e.g. Elasticsearch, Splunk) will see log lines formatted as JSON objects rather than plain text. The log level can still be configured via the existing LOG_LEVEL environment variable.

  • The scheduler now tracks metrics broken down per provider, enabling more granular observability of job throughput and resource usage when multiple providers are configured. No configuration change is required; existing dashboards will see new label dimensions on scheduler metrics.

  • Qiskit Serverless is now compatible with qiskit-ibm-runtime==0.47. Users can upgrade their local installation before using this release:

    pip install "qiskit-ibm-runtime==0.47"
    

v0.30.1

Prelude

qiskit_serverless v0.30.1 is a small patch release that fixes a minor client issue, introduces a series of API/Gateway/Scheduler improvements that increase the health of the deployed service, and updates the base image with the newest dependency versions.

Bug Fixes

  • Fixed gateway triggering out of memory errors when downloading a big file. Now, the download uses a 64Kb stream buffer that prevents it from crashing.

  • Fixed a bug in ServerlessClient.upload() where invalid files would not raise an error and instead fail to upload silently. The method now correctly raises a QiskitServerlessException.

v0.30.0

Prelude

The main feature in this release is the major refactor in logs to differentiate between provider logs and user logs and optimize storage. The logs are now stored in COS while keeping backward compatibility.

New Features

  • The qiskit-serverless Python library added two new utilities, get_logger() and get_provider_logger(), available to Qiskit Function developers. Developers should use these functions within their source code to distinguish which logging information is relevant for users (get_logger()) or providers (get_provider_logger()) respectively. These functions create [PUBLIC] and [PRIVATE] prefixes under the hood to allow the service to filter the information appropriately. To keep intellectual property safe, only function users can access user logs, and only providers can access provider logs. For example:

    from qiskit_serverless import get_logger, get_provider_logger
    
    user_logger = get_logger()
    provider_logger = get_provider_logger()
    
    get_logger().info("User log")
    user_logger.info("User multiline\nlog")
    user_logger.warning("User log")
    user_logger.error("User log")
    
    get_provider_logger().info("Provider log")
    provider_logger.info("Provider multiline\nlog")
    provider_logger.warning("Provider log")
    provider_logger.error("Provider log")
    
  • The ServerlessClient and Job classes’ logs() method will from now on only return user logs. To access provider logs, we have added a new provider_logs() method. For example:

    runnable_function = serverless_client.function("my_function")
    job = runnable_function.run()
    
    print(job.logs())  # only users
    print(job.provider_logs())  # only providers
    
  • Uploaded files now should have a valid file type in order to be accepted by the platform.

    The accepted MIME types for uploaded files are:

    • application/x-tar (.tar)

    • application/gzip (.gz, .tgz)

    • application/json (.json)

    • application/octet-stream (generic binary; may include .bin, .dat, .exe)

    • application/zip (.zip)

    • text/plain (.txt, .log)

    • text/csv (.csv)

Upgrade Notes

  • Added a new version field to the Program Django model. This field is used to track the version of the program (function).

  • Added a new JobEvents table to our database to improve metrics and provide a more flexible foundation for future event-tracking features. Each time a job changes state, a new event is now recorded in the database.

    The new JobEvents table includes:
    • id: auto-generated UUID

    • job: foreign key referencing the jobs table

    • created: timestamp automatically set at event creation

    • event_type: string describing the event type (currently "Status change")

    • context: string indicating where the change originated (e.g., Scheduler, API)

    • data: JSON payload containing event‑specific details

  • Added several quality of life improvements in testing.

  • The Scheduler is modified to upload logs to COS when a job transitions from running to a terminal state. At that moment, the logs will be filtered based on the prefixes, and the information will be saved in the corresponding folder inside COS.

  • The Gateway endpoint /logs has been modified to only target user logs, and a new /provider_logs` has been added for provider logs. Both should return: 1. The COS log for finished jobs, if it exists. This was previously filtered in the Scheduler. 2. The Ray console logs if the job is running. This is filtered live. 3. As a fallback, logs from the database.

  • Four new exception classes have been added to the gateway API to better differentiate between different failure scenarios: JobNotFoundException, ProviderNotFoundException, FunctionNotFoundException and FileNotFoundException that inherit from an abstract NotFoundError base class.

  • Added a new gateway setting to limit the number of active jobs per user. By setting the environment variable LIMITS_ACTIVE_JOBS_PER_USER, you can restrict the total number of jobs a user can have across QUEUED, PENDING, and RUNNING states. The default limit is 50.

  • Requests to run programs will now return an HTTP 429 Too Many Requests response if a user attempts to start a new job while they are at their active job limit.

  • The Scheduler and API code have been refactored to improve their maintainability and scalability. Most changes are internal and do not affect the user experience, but some additions have been made, for example, the scheduler can now respond to SIGINT and SIGTERM signals, and the API now accepts a dynamic configuration system that allows changing application settings at runtime.

Deprecation Notes

  • The @distribute_qiskit_function decorator has been deprecated and will be removed in following releases. The decorator was designed for remote program execution, a functionality that is now provided through the different serverless clients through the upload method.

Bug Fixes

  • Fixed a bug in the function versioning workflow where the version set upon function creation couldn’t be uploaded to the platform, stored in the database, or retrieved upon function retrieval. After the fix, the function version can be tracked through the whole life cycle. For example:

    from qiskit_serverless import QiskitFunction
    
    my-func = QiskitFunction(
        title="my_func_title",
        entrypoint="my_func_entrypoint.py",
        working_dir="./source_files/",
        version="2.0.0",
    )
    
    # this print outputs 2.0.0
    print(my-func.version)
    
    serverless.upload(my-func)
    my-func-load = serverless.function("my_func_title")
    
    # this print now also outputs 2.0.0 (previously empty)
    print(my-func-load.version)
    
  • Fixed an issue that affected local testing when using custom functions packaged as Docker images. These custom functions are registered as “provider” functions, however, their data path was resolved to a “custom” function data path, resulting in a “File not found” error when trying to fetch function arguments. After the fix, the data paths are resolved correctly, and no error is raised:

    • For user functions: DATA_PATH = \data\username

    • For provider functions: DATA_PATH = \data\username\providername\imagename

v0.29.0

Upgrade Notes

  • Refactored storage service initialization to accept Program model instances instead of individual parameters. The FileStorage and ArgumentsStorage classes now accept a function parameter (Program model instance) rather than separate username, function_title, and provider_name parameters. This change improves code cohesion by reducing the number of parameters passed between components and ensures consistent access to program metadata across storage services.

  • Removed unused function_title and provider_name parameters from ProgramViewSet.run() method’s call to job_serializer.save() as these parameters are no longer used after the storage service refactoring.

  • Removed LocalClient and RayClient classes and their usages in the tests. For testing, one should use ServerlessClient with default values as in docs/deployment/example_custom_image_function.rst. In addition, updated tests and removed irrelevant comments.

  • The legacy IBM Quantum authentication channel (ibm_quantum) has been removed. Users should migrate to using the IBM Quantum Platform channel (ibm_quantum_platform) for authentication. The authentication logic has been simplified to support only ibm_cloud and ibm_quantum_platform channels. When no channel header is provided and a CRN is present, the system defaults to ibm_quantum_platform.

  • Qiskit Serverless now supports the latest version of the qiskit-ibm-runtime package:qiskit-ibm-runtime==0.45.

Bug Fixes

  • Fixed a bug in the json decoder that would crop the response details to the first character when gateway errors were raised. This utility now forwards the full error message to the raised QiskitServerlessException.

  • Fixed error handling when a function is not found. Instead of providing a bare 404 error, the QiskitServerlessException now contains a more meaningful error message:

    QiskitServerlessException:
    | Message: Http bad request.
    | Code: 404
    | Details: User program 'my-program' was not found or you do not have permission to view it.
    

    Fixes https://github.com/Qiskit/qiskit-serverless/issues/1773.

  • Fixed a bug in FunctionRepository.get_user_function() where functions with providers could be incorrectly returned when searching for user functions without providers. The method now correctly filters by provider=None to ensure only user-owned functions without providers are returned. This prevents incorrect storage path resolution when users have multiple functions with the same title.

v0.28.0

New Features

  • IBMServerlessClient now supports custom gateway host URLs through the host parameter. This allows for a more streamlined testing of the client connecting to non-production gateways.

  • Added a new ProgramHistory model and database table to track when entities perform actions to add or remove an instance from a program. These allow to audit access changes for both instances and trial_instances.

v0.27.1

Upgrade Notes

  • The scheduling logic that was managing non GPU and GPU jobs was improved. Now the scheduler manages both type of jobs independently so you can adjust separately where each of these jobs are run.

  • Qiskit Serverless now supports the latest version of the qiskit-ibm-runtime package:qiskit-ibm-runtime==0.43.

Bug Fixes

  • Fixed a bug in the scheduler that was preventing it from stopping jobs automatically after the timeout threshold specified with the environment variable PROGRAM_TIMEOUT.

v0.27.0

New Features

  • A new utility get_runtime_service() is now available to simplify the instantiation of a QiskitRuntimeService within a function. It automatically pulls credentials from environment variables, reducing boilerplate code and improving usability for function developers.

    Example usage:

    from qiskit_serverless import get_runtime_service
    service = get_runtime_service()
    backend = service.backend("ibm_fez")
    

    This is equivalent to:

    import os
    from qiskit_serverless import get_arguments, save_result
    from qiskit_ibm_runtime import QiskitRuntimeService
    service = QiskitRuntimeService(
      channel=os.environ["QISKIT_IBM_CHANNEL"],
      instance=os.environ["QISKIT_IBM_INSTANCE"],
      token=os.environ["QISKIT_IBM_TOKEN"],
    )
    backend = service.backend("ibm_fez")
    

    This function also enables automatic tracking of runtime job and session IDs created during function execution. It’s important to note that tracking will only be possible if the function runtime jobs are submitted through a service instantiated with this function.

    On top of the default credentials, function developers can customize the call to get_runtime_service() with custom input parameters, including token, instance, channel and url. For example:

    from qiskit_serverless import get_runtime_service
    service = get_runtime_service(channel="ibm_quantum_platform",
                                  token="staging_token",
                                  instance="staging_crn",
                                  url="staging_url")
    backend = service.backend("staging_backend")
    
  • When using get_runtime_service() inside a serverless function, the resulting job object now supports two new methods: job.runtime_jobs() and job.runtime_sessions(). These methods return lists of job/session IDs that can be used to fetch job objects from a QiskitRuntimeService or access IQP dashboards. job.runtime_jobs() accepts an optional runtime_session parameter that allows to filter the returned jobs by associated session id. For example:

    job = function.run(...)
    
    runtime_ids = job.runtime_jobs()
    # out = ["job_id_1", "job_id_2", "job_id_3"...]
    runtime_sessions =  job.runtime_sessions()
    # out = ["session_id_1", "session_id_2"]
    
    # a specific session id can be passed to the runtime_jobs() method to filter by session:
    
    session_id = job.runtime_sessions()[0]
    # out = "session_id_1"
    session_job_ids =  job.runtime_jobs(session_id)
    # in this example, only job ids 1 and 3 correspond to session_id_1:
    # out = ["job_id_1", "job_id_3"]
    

Upgrade Notes

  • Added a new AccessPolicy for User model to be able to allow or not the access to the service by the is_active parameter.

  • Enhanced job.cancel() behavior to use the newly introduced features. job.cancel() now attempts to instantiate a QiskitRuntimeService using the credentials from the ServerlessClient, allowing automatic canceling of associated runtime jobs. This works when the credentials used in the function match those in the client, which happens by default when using get_runtime_service() with no additional inputs.

    To support local testing or non-standard runtime URLs (e.g., staging environments), where the ServerlessClient don’t match those in the QiskitRuntimeService used to submit the jobs, job.cancel() accepts a service parameter:

    service = QiskitRuntimeService(
      channel="ibm_quantum_platform",
      token="MY_TOKEN",
      instance="MY_CRN",
      url="my.staging.url.com"
    )
    job.cancel(service)
    
  • The ServerlessRuntimeService class has been updated to support changes introduced in qiskit_ibm_runtime>=0.42, unlocking compatibility of the qiskit-serverless>=0.27.0 package with qiskit_ibm_runtime>=0.42. For older versions of qiskit-serverless, qiskit_ibm_runtime<0.42 will still be required.

  • The Scheduler will start to manage the size of the logs generated by the Qiskit Functions. The environment variable FUNCTIONS_LOGS_SIZE_LIMIT will be in charge of the maximum size that the system will allow to store.

  • Enhanced client.jobs() and function.jobs() behavior with new filters to improve the management of the different jobs. Now you can filter by status and by created_after to retrieve those specific jobs. For example:

    # Filtering by status it will retrieve all the jobs with that status
    client.jobs(status="SUCCEEDED")
    
    # The same it will apply after a specific date
    time_filter = datetime.now(timezone.utc)
    client.jobs(created_after=time_filter)
    
    # And all these new filters can be combined with the Qiskit Function filter
    # to be able to return running jobs from the specific Qiskit Function
    function.jobs(status="RUNNING")