uidudevelopers
Domains

Calls

Fetch job openings, grant calls, and any other application-driven opportunity. Apply via the call's embedded form.

A call is anything in uidu that accepts applications: a job opening, a grant call, a residency, a casting call. Each one carries its own application form, so a call page is a rich-text body plus a form to submit. For job postings and their first-class applications, see Jobs & applications.

Mental model

Call                       ← listCalls (summaries) / getCall (full)
  ├─ kind                  'job' | 'open_call' | 'opportunity'
  ├─ body, cover           rich text (Tiptap JSON) and cover image URL
  ├─ quantity              number of openings
  ├─ salaryMin / salaryMax whole currency units
  ├─ expiresAt             the application deadline
  ├─ primaryAddress        for location-bound calls
  └─ form                  the application form — same shape as getForm()
        └─→ FormResponse   createFormResponse (Forms domain)
  • One primitive, three kinds. kind is job, open_call or opportunity. listCalls returns all of them; filter on kind yourself, or use listJobs for job only.
  • The list is a summary. listCalls returns id, name, cover, kind, status, quantity, salaries, publishedAt, expiresAt, publicUrl, publicPath, published and a short primaryAddress (formattedAddress, city). There is no body and no form: fetch one call with getCall for those.
  • The form is inlined. getCall selects call.form with the same FormFields fragment as getForm, so call.form goes straight into <DynamicForm> or any renderer you wrote for Forms. Each question is at call.form.inputs.edges[].node.
  • Salaries are whole units, not cents — unlike every other amount in uidu (see Data model). Both are number | null. The call's currency is not part of the SDK's selection, so pass it yourself when you format (formatSalaryRange).
  • No deadline filter. listCalls takes only first/after and sends no filter, so filter on expiresAt yourself if the page should list open calls only.
  • The calls domain is read-only. Applying is a write in another domain: submit the call's form with createFormResponse, or, for a job, record an application with createApplication. Either way it runs in a server action with the apiKey client — see Authentication.

Use cases

The examples import uidu from lib/uidu.ts, a client built with publicToken and apiKey and imported only from server code, as in Authentication.

Listing open calls

Goal: a page that lists every call still accepting applications, with its place and deadline.

// app/calls/page.tsx
import { listCalls } from '@uidu/client';
import { uidu } from '@/lib/uidu';

export default async function CallsPage() {
  const calls = await listCalls(uidu, { first: 50 });
  const now = new Date();
  const open = calls
    .filter((c) => c.kind !== 'job') // jobs have their own careers page
    .filter((c) => !c.expiresAt || new Date(c.expiresAt as string) > now)
    .sort((a, b) =>
      String(a.expiresAt ?? '').localeCompare(String(b.expiresAt ?? '')),
    );

  return (
    <ul>
      {open.map((call) => (
        <li key={call.id}>
          <a href={`/calls/${encodeURIComponent(call.id)}`}>{call.name}</a>
          {call.primaryAddress?.city && ` · ${call.primaryAddress.city}`}
          {call.expiresAt &&
            ` · apply by ${new Date(call.expiresAt as string).toLocaleDateString()}`}
        </li>
      ))}
    </ul>
  );
}
  • listCalls returns one page as a flat array (empty if none). To read every call, pass ListCallsDocument to paginate — see Pagination & field values.
  • Call ids are GlobalIDs: encode them in a URL and decode them in the route, as above.

Open-call page with its application form

Goal: one call's page — body, deadline, and the application form, submitted from the server.

// app/calls/[id]/page.tsx
import { getCall, createFormResponse } from '@uidu/client';
import { DynamicForm, RichText, type DynamicFormValues } from '@uidu/react';
import { notFound } from 'next/navigation';
import { uidu } from '@/lib/uidu';

export default async function CallPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const call = await getCall(uidu, { id: decodeURIComponent(id) });
  if (!call) notFound();

  const form = call.form;
  const formId = form?.id;

  async function apply(values: DynamicFormValues) {
    'use server';
    if (!formId)
      return { ok: false as const, errors: [{ name: 'No application form' }] };
    const result = await createFormResponse(uidu, {
      input: {
        attributes: {
          formId,
          fieldValuesAttributes: values.fieldValues.map((fv) => ({
            fieldId: fv.fieldId,
            content: { value: fv.value }, // always wrapped
          })),
          contactAttributes: values.contact && {
            email: values.contact.email,
            contactableAttributes: {
              kind: 'person',
              firstName: values.contact.firstName,
              lastName: values.contact.lastName || undefined,
            },
          },
        },
        event: 'complete!', // submit it, don't leave a draft
      },
    });
    return result?.errors?.length
      ? { ok: false as const, errors: result.errors }
      : { ok: true as const, meta: { responseId: result?.formResponse?.id } };
  }

  return (
    <article>
      <h1>{call.name}</h1>
      {call.expiresAt && (
        <p>
          Apply by {new Date(call.expiresAt as string).toLocaleDateString()}
        </p>
      )}
      {call.body && <RichText doc={call.body} />}
      {form && <DynamicForm form={form} action={apply} submitLabel="Apply" />}
    </article>
  );
}
  • getCall returns null for an unknown id; call.form can be null too (a call that takes no applications).
  • <DynamicForm> collects the candidate's first name, last name and email (includeContactFields, on by default) plus one value per visible question.
  • createFormResponse doesn't throw on validation errors: check errors. A transport failure throws a UiduError — see Errors.
  • Attachments (CV, portfolio): each media question carries its upload target at call.form.inputs.edges[].node.field — uploadPresignedUrl is a presigned POST as JSON ({ url, fields }), split into uploadUrl and uploadParams; follow the file upload flow.
  • For a call of kind job, record a first-class application instead, so it shows up on the call's board: Job page with an apply form.

API at a glance

Every @uidu/client function in Calls, generated from the source — the full reference has the rest of the SDK.

FunctionKindAuthCLIDescription
getCallreadpublic tokencalls getOne call (a job opening or open call) with its application form, same shape as getForm, or null.
listCallsreadpublic tokencalls listThe workspace's calls as a flat array of summaries, without their forms; page with first/after.

Types: Call

CLI:

Entitylistgetcreateupdatedelete
calls✓✓———

On this page