API referenceTenvelo API
https://api.tenvelo.com The contract, as OpenAPI

Contract 2026-10-10. Every path below carries its own major version.

Platform · Reference

Jobs

Long-running work started by a 202 Accepted, such as a catalog import.

Operations
2
Base
https://api.tenvelo.com
  1. GET/v1/jobs

    List jobs

    No. listJobsAuth jobs:read

    Returns a page of the merchant's jobs, newest first, optionally of one type — the imports a back office has run, say, to show which finished and which failed.

    Parameters

    • typequery · JobType

      Only jobs of this type.

    • cursorquery · string

      Opaque cursor from a previous page's next_cursor. Omit to start from the beginning.

      Pass it back exactly as received. It is base64 and its structure is not part of this contract — decoding it, or constructing one by hand, will break without notice.

    • limitquery · integer · int32

      Maximum items to return. The server may return fewer, so always drive the loop from has_more rather than from the size of the page you got back.

    Responses

    • 200

      A page of jobs.

      JobPage · application/json
      • has_moreboolean

        Required

        Whether another page follows.

      • itemsarray of Job

        Required

        • idstring · uuid

          Required

        • typeJobType

          Required

          What a job is doing, named for what started it: a catalog_import is created by POST /v1/catalog-imports. report_export and settlement_run are reserved for work not built yet.

          catalog_importreport_exportsettlement_run

        • created_atTimestamp

          RequiredRead only

          Server-assigned RFC 3339 timestamp, always UTC.

          Server-assigned because device clocks drift and cannot be trusted for ordering — a till that has been powered off for a week may come back with a clock hours out. Where the device's own view of time matters it is carried separately in client_created_at.

        • finished_atstring · date-time

          Nullable

        • problemProblem

          Nullable

          Present when status is failed.

          • typestring · uri

            Required

            A stable URI identifying the problem kind. This is the field to branch on.

            default about:blank

          • api_versionstring

            Present on an api-version-retired or api-version-unknown refusal: the API-Version this server speaks (docs/decisions/0011).

          • detailstring

            A human-readable explanation specific to this occurrence. Safe to show to a developer; never contains internal state, SQL, or stack traces.

          • instancestring · uri-reference

            A URI reference identifying the specific occurrence.

          • invalid_paramsarray of InvalidParam

            Present on 422 responses. Lists every field that failed validation, rather than only the first, so a client can highlight an entire form in one pass.

            • codestring

              Stable machine-readable code for this failure, for clients that localise their own messages rather than showing reason.

            • pointerstring

              Required

              RFC 6901 JSON Pointer to the offending value within the request body. A pointer rather than a field name so that failures nested inside arrays — the third line item of an order — are addressable at all.

            • reasonstring

              Required

              Why this value was rejected, in human-readable form.

          • refusalsarray of Location refusal

            Present on 409 responses to a withdrawal refused by the state it depends on (deleting a location with tills still assigned, open orders, …). Lists every reason at once, each with how much stands in the way, so a client can say what to put right and link to it rather than walking a person through one refusal after another.

            • kindstring

              Required

              A stable refusal kind — devices, open_orders, drawer_sessions, bookings or stock — that a client links to the screen that puts it right. Branch on this field, never on the surrounding prose.

            • countinteger

              Required

              How many things stand in the way of the withdrawal, so a client can repeat the number rather than re-read prose.

              ≥ 1

          • statusinteger

            Required

            The HTTP status code, repeated here so the body is self-contained.

            100–599

          • titlestring

            Required

            A short, human-readable summary of the problem kind.

          • trace_idstring

            The OpenTelemetry trace id for this request. Quote it in a support ticket and the whole request can be pulled up.

        • progressobject

          Nullable

          Best-effort progress. Advisory only — total_units may be revised upward as work is discovered, so never treat completed_units == total_units as completion. status is the only authority on whether the job is done.

          • completed_unitsinteger · int64

            Required

            Units of work finished so far.

            ≥ 0

          • total_unitsinteger · int64

            Required

            Units of work known about. Named total_units rather than total because a bare total beside monetary fields reads as an amount, and in an API where every other total is money that ambiguity is worth spending a word on.

            ≥ 0

        • resultobject

          Nullable

          What the job produced, present when status is succeeded. For an export this carries a time-limited download URL.

        • started_atstring · date-time

          Nullable

        • statusstring · enum

          Required

          pending — accepted and queued, not yet picked up by a worker.

          running — in progress; progress may be populated.

          succeeded — finished; result is populated.

          failed — finished unsuccessfully; problem explains why. Terminal: the job will not be retried on its own, though the request may be re-submitted.

          canceled — stopped before it finished. Terminal.

          pendingrunningsucceededfailedcanceled

      • next_cursorstring

        Nullable

        Opaque cursor for the next page, or null on the last page. Pass it back verbatim as the cursor query parameter; its contents are not part of this contract.

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 422

      The request parsed but failed validation.

      invalid_params lists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.

      Also returned when an Idempotency-Key is replayed with a different body, and when device-computed order totals disagree with the server's recomputation.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.

  2. GET/v1/jobs/{id}

    Get a job

    No. getJobAuth jobs:read catalog:write

    Returns the current state of work started by a 202 Accepted response.

    Poll no faster than the Retry-After given on the 202. Treat only status as authoritative for completion — progress.total_units is discovered as work proceeds and may be revised upward, so completed_units == total_units does not mean finished.

    Terminal states are succeeded, failed and canceled. A failed job carries a problem explaining why and will not retry itself; resubmit the original request, with a new idempotency key, if you want to try again.

    An integration reads a job with jobs:read, or one it could have started — a catalog import with catalog:write.

    Parameters

    • idpath · string · uuid

      Required

      UUIDv7 of the resource.

      For resources a device can originate — orders, and their line items — this id is minted by the client before the record is ever synced, which is what makes an offline create idempotent.

    Responses

    • 200

      The job.

      Job · application/json
      • idstring · uuid

        Required

      • typeJobType

        Required

        What a job is doing, named for what started it: a catalog_import is created by POST /v1/catalog-imports. report_export and settlement_run are reserved for work not built yet.

        catalog_importreport_exportsettlement_run

      • created_atTimestamp

        RequiredRead only

        Server-assigned RFC 3339 timestamp, always UTC.

        Server-assigned because device clocks drift and cannot be trusted for ordering — a till that has been powered off for a week may come back with a clock hours out. Where the device's own view of time matters it is carried separately in client_created_at.

      • finished_atstring · date-time

        Nullable

      • problemProblem

        Nullable

        Present when status is failed.

        • typestring · uri

          Required

          A stable URI identifying the problem kind. This is the field to branch on.

          default about:blank

        • api_versionstring

          Present on an api-version-retired or api-version-unknown refusal: the API-Version this server speaks (docs/decisions/0011).

        • detailstring

          A human-readable explanation specific to this occurrence. Safe to show to a developer; never contains internal state, SQL, or stack traces.

        • instancestring · uri-reference

          A URI reference identifying the specific occurrence.

        • invalid_paramsarray of InvalidParam

          Present on 422 responses. Lists every field that failed validation, rather than only the first, so a client can highlight an entire form in one pass.

          • codestring

            Stable machine-readable code for this failure, for clients that localise their own messages rather than showing reason.

          • pointerstring

            Required

            RFC 6901 JSON Pointer to the offending value within the request body. A pointer rather than a field name so that failures nested inside arrays — the third line item of an order — are addressable at all.

          • reasonstring

            Required

            Why this value was rejected, in human-readable form.

        • refusalsarray of Location refusal

          Present on 409 responses to a withdrawal refused by the state it depends on (deleting a location with tills still assigned, open orders, …). Lists every reason at once, each with how much stands in the way, so a client can say what to put right and link to it rather than walking a person through one refusal after another.

          • kindstring

            Required

            A stable refusal kind — devices, open_orders, drawer_sessions, bookings or stock — that a client links to the screen that puts it right. Branch on this field, never on the surrounding prose.

          • countinteger

            Required

            How many things stand in the way of the withdrawal, so a client can repeat the number rather than re-read prose.

            ≥ 1

        • statusinteger

          Required

          The HTTP status code, repeated here so the body is self-contained.

          100–599

        • titlestring

          Required

          A short, human-readable summary of the problem kind.

        • trace_idstring

          The OpenTelemetry trace id for this request. Quote it in a support ticket and the whole request can be pulled up.

      • progressobject

        Nullable

        Best-effort progress. Advisory only — total_units may be revised upward as work is discovered, so never treat completed_units == total_units as completion. status is the only authority on whether the job is done.

        • completed_unitsinteger · int64

          Required

          Units of work finished so far.

          ≥ 0

        • total_unitsinteger · int64

          Required

          Units of work known about. Named total_units rather than total because a bare total beside monetary fields reads as an amount, and in an API where every other total is money that ambiguity is worth spending a word on.

          ≥ 0

      • resultobject

        Nullable

        What the job produced, present when status is succeeded. For an export this carries a time-limited download URL.

      • started_atstring · date-time

        Nullable

      • statusstring · enum

        Required

        pending — accepted and queued, not yet picked up by a worker.

        running — in progress; progress may be populated.

        succeeded — finished; result is populated.

        failed — finished unsuccessfully; problem explains why. Terminal: the job will not be retried on its own, though the request may be re-submitted.

        canceled — stopped before it finished. Terminal.

        pendingrunningsucceededfailedcanceled

    • 400

      The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an API-Version later than the server speaks or not a date (api-version-unknown, with the server's own in api_version).

      Distinct from 422: a 400 never reached validation, so invalid_params is absent. Retrying without changing the request will fail again, except that a request refused for its API-Version goes once the server is updated.

    • 401

      No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.

      Refresh the token and retry once. Repeated 401 after a refresh means the device needs to re-enrol.

    • 403

      Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.

      Cross-tenant access is reported as 403 on a resource the caller may not see, and as 404 where even confirming existence would leak information. Retrying will not help.

    • 404

      No such resource, or none visible to this caller.

      Also returned instead of 403 where acknowledging existence would itself leak information — so a 404 does not prove the id is unused globally, only that it is not yours.

    • 410

      The request names an API-Version this server no longer serves (docs/decisions/0011). Nothing in it was read. api_version names the version the server speaks: the client is behind, and sends the request again once it is updated.

    • 429

      Rate limit exceeded. Honour Retry-After rather than applying your own backoff.

      A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per (client, merchant) for everything else; no RateLimit field is sent, so this response and its Retry-After are the whole of the contract.

    • 500

      Something failed on our side. trace_id identifies the request in our tracing — quote it in a support ticket.

      The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same Idempotency-Key.

    • 503

      Temporarily unable to serve the request — a dependency is down or the service is shedding load.

      Retry with the same Idempotency-Key after Retry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.