---
title: "Describe the CRM model, pipelines, custom fields, limits, and starter models"
method: GET
path: "/v1/schema"
tags: ["schema"]
---

# Describe the CRM model, pipelines, custom fields, limits, and starter models

`GET /v1/schema`

## Request

- Base URL: `https://api.indraft.io`
- URL: `https://api.indraft.io/v1/schema`
- Auth: HTTP bearer

Describe the CRM: object types and whether they can be extended, pipelines and stages, custom fields, tags, your own capabilities, the workspace timezone and default currency, ID prefixes, every stated limit, every error code, and the derived values the database computes on read and nothing can write. Call this first; it removes the need to probe.

## Query parameters

- `starterModel` string: The key of one starter model to return in full. Absent returns only the summaries, which is enough to choose or to decide none of them fits. A key nobody defined is not an error.

## Response `200`

Success

- object
  - `schema` object, required
    - `objects` object, required
      - `types` string[], required
      - `canonical` string[], required
      - `custom` object[], required
        - `key` string, required
        - `displayName` string, required
        - `displayNamePlural` string, required
        - `idPrefix` string, required
        - `fields` object[], required
        - `relationships` object[], required
      - `extensible` boolean, required
      - `note` string, required
    - `queries` object, required
      - `operators` string[], required
      - `maxPredicates` integer, required
      - `filterExpression` string, required
      - `sortExpression` string, required
      - `defaultSort` string, required
      - `filterable` object, required
      - `note` string, required
    - `aggregates` object, required
      - `functions` string[], required
      - `dateBuckets` string[], required
      - `maxGroups` integer, required
      - `maxDimensions` integer, required
      - `groupable` object, required
      - `reducible` object, required
      - `joins` object, required
      - `calendar` object, required
        - `fiscalYearStartMonth` integer, required
        - `isCalendarYear` boolean, required
        - `note` string, required
      - `conversion` object, required
        - `note` string, required
        - `rates` object[], required
          - `id` string, required
          - `from` string, required
          - `to` string, required
          - `ratePpm` integer, required
          - `effectiveFrom` string, required
          - `sourceLabel` string, required
          - `assertedByActorId` string, required
          - `assertionKind` string, required
      - `note` string, required
    - `derived` object, required
      - `values` object, required
      - `computable` object, required
      - `note` string, required
    - `pipelines` object[], required
      - `id` string, required
      - `name` string, required
      - `isDefault` boolean, required
      - `stages` object[], required
        - `id` string, required
        - `pipelineId` string, required
        - `name` string, required
        - `position` integer, required
        - `stageKind` 'open' | 'won' | 'lost', required
        - `defaultProbabilityBps` integer, required
    - `customFields` object[], required
      - `id` string, required
      - `key` string, required
      - `label` string, required
      - `target` 'company' | 'contact' | 'opportunity' | 'interaction' | 'task', required
      - `valueType` 'text' | 'number' | 'boolean' | 'timestamp' | 'enum' | 'json', required
      - `required` boolean, required
      - `searchable` boolean, required
      - `enumOptions` string[], required
      - `computed` object, required
      - `archivedAt` string, required
      - `createdAt` string, required
      - `updatedAt` string, required
    - `tags` object[], required
      - `id` string, required
      - `name` string, required
    - `workspace` object, required
      - `name` string, required
      - `displayTimezone` string, required
      - `defaultCurrency` string, required
      - `fiscalYearStartMonth` integer, required
    - `caller` object, required
      - `capabilities` string[], required
      - `role` string, required
      - `actorId` string, required
    - `idPrefixes` object, required
    - `limits` object, required
    - `configurable` string[], required
    - `notConfigurable` string[], required
    - `errors` object[], required
      - `code` 'unauthorized' | 'forbidden_scope' | 'standing_override_refused' | 'workspace_not_found' | 'validation_error' | 'record_not_found' | 'ambiguous_identity' | 'duplicate_identity' | 'stale_version' | 'invalid_stage_transition' | 'idempotency_conflict' | 'not_reversible' | 'merge_conflict' | 'rate_limited' | 'quota_exceeded' | 'payload_too_large' | 'workspace_capacity' | 'payment_required' | 'billing_state_stale' | 'operation_in_flight' | 'internal_error', required
      - `meaning` string, required
      - `recoveryHint` string, required
      - `retryable` boolean, required
    - `starterModels` object[], required
      - `key` string, required
      - `title` string, required
      - `summary` string, required
      - `bestFor` string, required
      - `theDecision` string, required
    - `starterModel` object
      - `key` string, required
      - `title` string, required
      - `summary` string, required
      - `bestFor` string, required
      - `theDecision` string, required
      - `objectTypes` object[], required
        - `key` string, required
        - `displayName` string, required: What one record of this type is called, singular. A person reads this.
        - `displayNamePlural` string: The plural, for lists. Left out, a reasonable one is derived.
        - `idPrefix` string, required
        - `fields` object[], required
          - `key` string, required: The name this field is addressed by, in every later read and write. Lower-case, starting with a letter. It is permanent: renaming a field is not offered, because every value already written is addressed by it.
          - `label` string, required: What a person sees. Change this freely; it names nothing.
          - `valueType` 'text' | 'number' | 'boolean' | 'timestamp' | 'enum' | 'json', required: What kind of value this holds. Permanent, and chosen for what the field IS rather than for what is convenient to send: a date stored as text cannot be sorted, filtered by range, or counted by month, and converting it later is not offered.
          - `required` boolean: Whether a record without this value can exist. Applies to records written from now on; records already stored are not made invalid by it.
          - `indexed` boolean: Set this on a field you will filter or sort by. Without it those still work and get slower as the type fills up. There is a published cap on how many fields one type may index, so spend it on the ones a person actually searches.
          - `enumOptions` string[]: The allowed values, required when valueType is enum and refused otherwise. A value outside this list is rejected rather than stored, so state every option you expect.
        - `relationships` object[]
          - `name` string, required: How this link is addressed when writing a record. Permanent, like a field key.
          - `label` string, required: What a person sees for this link.
          - `target` string, required: What it points at: a canonical type (company, contact, opportunity, task) or the key of another type this workspace defined. The target must already exist.
          - `cardinality` 'one' | 'many', required: Whether one record may point at one of the target or at many. Pick from the record's side: an Application belongs to one Candidate and a Candidate has many Applications, so on Application this is one.
        - `identity` object
          - `field` string, required: The declared field whose value says two records are the same record. It must be a field of this type. Declare one only where the value really is unique per record: an upsert then matches on it instead of creating a second copy.
          - `normalize` 'exact' | 'trimmed_casefold' | 'email' | 'domain' | 'phone', required: How the value is compared. `exact` compares as stored; `trimmed_casefold` ignores surrounding space and letter case; `email`, `domain` and `phone` apply the same rules the canonical types use, so a Candidate keyed on email and a contact keyed on email agree about what one address is.
      - `pipelines` object[], required
        - `objectType` string, required
        - `name` string, required
        - `stages` object[], required
          - `name` string, required
          - `stageKind` 'open' | 'won' | 'lost', required
  - `requestId` string, required

## Other responses

- `401`: No credential, or one that has been revoked.
- `403`: Authenticated, and this capability is not held.
- `422`: The request does not satisfy the schema on this page.
- `429`: Infrastructure protection tripped. This is not a product quota.
- `500`: A defect on our side. The hint says so, and carries an incident id.

---

[API](https://skmtc.dev/indraft/apis/indraft-api.md) · [All operations](https://skmtc.dev/indraft/apis/indraft-api/llms.txt) · [OpenAPI document](https://skmtc.dev/indraft/apis/indraft-api/revisions/d9bb05346d66?raw)
