space.schedule.verify()

Check in your endpoint that a call came from the scheduler, for your audience, and from a job you expect.

Checks that a call to your endpoint came from the scheduler, was meant for your endpoint, and came from a job you expect. Use it in the endpoint a signed job calls. See Verify calls to your app.

function verify(
  request: Request | { headers: Headers | Record<string, string | undefined> },
  options: VerifyOptions
): Promise<SchedulerCall>

Parameters

requestRequest or objectRequired

The incoming call: a Request, or any object with its headers, as a Headers or a plain object. Only the Authorization header is read, whatever its case.

options.audiencestringRequired

The audience your job’s calls carry: the job’s target URL without its query, or the audience you set on the job. It cannot be empty. A job’s signing.effectiveAudience shows it.

options.jobsstring[]

The jobs allowed to call this endpoint, each as a job ID or as projects/{project}/jobs/{job}. Any job is accepted when you leave it out.

options.nownumber

The time to check the token against, in milliseconds since 1 January 1970. The current time when you leave it out.

What it checks

  1. The call carries Authorization: Bearer and a JSON Web Token.
  2. The token is signed with RS256, by a key the scheduler publishes, named by the token’s kid.
  3. Its issuer is the scheduler, and its audience includes options.audience.
  4. It has not expired and is already valid, allowing 30 seconds either way for clocks that differ.
  5. It names a job, and when you give options.jobs, that job is one of them.

The scheduler’s public keys are fetched on the first call and kept for 5 minutes. A token signed with a key not among them fetches the keys again, at most once every 30 seconds, so a new key is picked up after the scheduler rotates its keys. See Key rotation.

verify() needs no API key and no project, so an endpoint that only receives calls can use space without one.

Returns

A promise of the call’s details:

{
  "project": "01k5ztq3jb9y2a1m9v2xw8e0ha",
  "job": "01m3c081d2wj4g63qcr53pa1yh",
  "subject": "projects/01k5ztq3jb9y2a1m9v2xw8e0ha/jobs/01m3c081d2wj4g63qcr53pa1yh",
  "expiresAt": "2026-09-29T08:40:00.000Z",
  "claims": {
    "iss": "https://scheduler-api.digitospace.com",
    "sub": "projects/01k5ztq3jb9y2a1m9v2xw8e0ha/jobs/01m3c081d2wj4g63qcr53pa1yh",
    "aud": "https://shop.example/tasks/reminders",
    "exp": 1790671200,
    "iat": 1790670600
  }
}
projectstring

The ID of the project the job belongs to.

jobstring

The ID of the job that made the call.

subjectstring

The token’s subject, projects/{project}/jobs/{job}.

expiresAtDate

When the token expires, 10 minutes after it was made.

claimsobject

Every claim the token carries: iss, sub, aud (a string or a list), exp, and nbf and iat when it has them, in seconds since 1 January 1970.

Errors

CodeWhen
invalid_signatureThe call has no bearer token, or the token cannot be read, is not signed with RS256 by a published key, is from another issuer or for another audience, has expired, is not valid yet, or names no job. error.status is 401
unexpected_jobThe token is from a job that is not in options.jobs. error.status is 403
keys_unavailableThe scheduler’s public keys could not be fetched. It is retryable, so answer with a 5xx status, and the run is retried if its job allows it
validation_failedoptions.audience is missing or empty, or options has a field this page does not list

Answer invalid_signature and unexpected_job with error.status, and do none of the work. See SpaceError.

Example

app/tasks/reminders/route.ts
import { space, SpaceError } from "digitospace"

export async function POST(request: Request) {
  try {
    const call = await space.schedule.verify(request, {
      audience: "https://shop.example/tasks/reminders",
      jobs: ["01m3c081d2wj4g63qcr53pa1yh"],
    })
    console.log(`Sending reminders for job ${call.job}`)
    return new Response(null, { status: 204 })
  } catch (error) {
    if (
      error instanceof SpaceError &&
      (error.code === "invalid_signature" || error.code === "unexpected_job")
    ) {
      return new Response(error.message, { status: error.status })
    }
    return new Response("Could not check the call", { status: 503 })
  }
}

Last updated