Skip to content

Write an entry

The one write call: records.emit in the SDKs, POST /api/v1/records/{recordType}/{record}/transitions on the wire.

http
POST /api/v1/records/{recordType}/{record}/transitions

One call writes one entry. The first entry creates the record. The answer is queued; the entry is committed when it shows committed in the history or when record.committed reaches your webhook.

Call

typescript
const entry = await pac.records.emit({
  record: 'build-2026-09-15-0417',
  title: 'Model build passed testing, 15 September',
  kind: 'model-build',
  occurredAt: '2026-09-15T16:04:17Z',
  actorId: 'ci-runner-7',
  instructedBy: 'release-manager',
  payloads: [ref],
  description: 'Build 0417 passed the full suite on the 15 September runner.',
  idempotencyKey: 'build-2026-09-15-0417:2026-09-15T16:04:17Z',
});

Fields

The SDKs speak in the words on this page: record is your id for the record and entry is an entry's number, counted from 1 as every page counts it. On the wire the route keeps its own names, entityId and seq, in the path, the answer, and the webhook payloads, and seq counts the same positions from 0; the SDKs map between the two.

SDK fieldWireRequiredWhat it is
recordpath {record} (entityId)yesYour id for the record: letters, digits, _ - . :, up to 255 characters. Every entry about the same thing uses the same id.
recordTypepath {recordType}noThe entry's shape. Defaults to machine-action-record, the general shape; you name a record type only when we have given you a different one.
titlecontent.titleyesWhat this entry records, in your words. At most 120 characters.
occurredAtcontent.occurredAtyesWhen it happened, RFC 3339 in UTC: when the machine acted. The time of your call is recorded on its own, as receivedAt in the answer.
actorIdcontent.actorIdyesWho or what did it, named by you.
payloadscontent.payloadsyesThe fingerprints of the files this entry is about, each { alg, digest, byteLength } from fingerprint. At least one.
kindcontent.kindnoYour own word for the sort of thing this is, lower case with hyphens.
instructedBycontent.instructedBynoWho told the actor to do it.
description, notecontent.description, content.notenoText sealed with the entry.
referencescontent.referencesnoEntries of other records this one refers to, each { recordKey, entry } (seq on the wire, entry - 1). Each must already be committed in your workspace.
amendscontent.amendsnoThe committed entry this one corrects, { recordKey, entry } (seq on the wire), sent with lifecycle: amended. In the same record it must already be committed. An entry of another record can be corrected from a new record once that record is closed; the correction then also names the entry in references. See Correcting an entry.
lifecyclelifecyclenorecorded, amended, or closed. The SDKs send recorded; send amended with amends. See Lifecycle.
idempotencyKeyreferenceIdno, and you shouldOne key is one write. Use a new key for every entry and reuse one only to retry the same entry. Never the same as record.

Everything under content is sealed. Storage locations, file names, and the file itself are never part of the record; a file is named only by its fingerprint.

Answer

json
{
  "success": true,
  "data": {
    "intakeId": "rit_9Xq2mP4vL8sT",
    "receiptId": "anc_01j7z3k9m2q4",
    "recordType": "machine-action-record",
    "entityId": "build-2026-09-15-0417",
    "recordKey": "0x5e1f...c3a9",
    "lifecycle": "recorded",
    "status": "QUEUED",
    "referenceId": "build-2026-09-15-0417:2026-09-15T16:04:17Z",
    "receivedAt": "2026-09-15T16:04:18.102Z",
    "message": "Transition queued. The record is committed when the entry appears in your webhook or the record's history. The webhook carries the entry number; fetch the receipt with it."
  }
}

The answer carries no entry number, because the number is assigned when the entry is committed. Read it from records[].seq in the record.committed webhook or from the history. A repeat of a finished idempotency key returns this same answer with "idempotent": true.

Attempts

A record opens with recorded. closed is final: an entry sent after the record is closed is recorded as an attempt, marked so, and the record's state does not move. The attempt is in the history like any entry, so the record shows that something tried to write after the close.

When the call is refused

Every code reaches your code as error.code in the SDKs and as code in the body on the wire. Each sentence states the rule.

CodeThe ruleWhat to do
UNAUTHORIZED (401)A key is one your workspace lists under Settings, Developer, API keys, sent as x-api-key.Check that the key is set and has not been disabled.
RECORDS_API_NOT_ENABLED (403)Records are written from a records workspace.Pick the records option at signup, or ask us to switch the workspace.
RECORD_LAYOUT_UNKNOWN (400)A record type is one of the published names.Use machine-action-record, or leave recordType out.
RECORD_LAYOUT_NOT_ADMITTED (422)The record type is published but not yet admitted for writes in this environment. Nothing was queued.It becomes writable when the registration runs.
RECORD_LAYOUT_MISMATCH (409)A record keeps the record type it started with.Use a new record id for the new record type.
RECORD_STATE_UNKNOWN (400)A state comes from the record type's declared list.Use recorded, amended, or closed.
RECORD_CONTENT_INVALID (400)The sealed record carries the fields the record type names, each in its declared form.Fix the field the message names and send again.
RECORD_PAYLOAD_ALG_UNSUPPORTED (400)A fingerprint is made with sha-256.Fingerprint the file with the helper and send again.
RECORD_PAYLOADS_REQUIRED (422)Every entry carries at least one fingerprint.Fingerprint the file or the payload and put the result in payloads.
RECORD_TITLE_TOO_LONG (422)A title fits in 120 characters.Shorten the title; the rest belongs in description.
RECORD_REFERENCE_NOT_FOUND (422)A reference names an entry that is already committed in your workspace.Check the record key and entry number, or wait for that entry to reach committed.
RECORD_AMENDS_NOT_FOUND (422)A correction names an entry the record has. Nothing was queued.Check the record key and the entry number; on the wire it is seq, counted from 0.
RECORD_AMENDS_NOT_PRECEDING (422)A correction names an entry that is already committed and comes before it. Nothing was queued.Wait for the entry to commit, or name the entry you meant.
RECORDS_CONTRACT_NOT_READY (412)A workspace writes once its recording environment is ready.Wait for the dashboard to show ready, then send again.
PLAN_LIMIT_REACHED (402)A Free workspace writes 100 entries a month. Nothing was queued.Move to a paid plan, then send again with the same idempotency key. See Plan and usage.
RECORD_INTAKE_FAILED (500)A write that did not land is always reported.Send the same request again with the same idempotency key.
IDEMPOTENCY_PENDING (202)One idempotency key is one write; a repeat waits for the first to finish.Wait retryAfterSeconds, then send the same request again. The SDKs return this rather than throwing.
IDEMPOTENCY_CONFLICT (409)One idempotency key is one write; a different body under the same key is a different write.Send the new entry with a new key.

One failure arrives after QUEUED rather than in the answer. RECORD_GENESIS_MISMATCH: a record opens with recorded; amended and closed come after it. If a record's first entry is sent as closed, the write is queued and then fails to commit. The failure reaches you as record.failed at your webhook and as status: failed with the code in the history. Send the first entry as recorded, then the entry you meant, with a new idempotency key.

Limits

A workspace writes up to 120 entries a minute. Every answer under the limit carries X-RateLimit-Remaining. Past it, the answer is 429 RATE_LIMIT_EXCEEDED with retryAfterSeconds in the body and the same number in a Retry-After header; the SDKs wait exactly that long and send again. From your own code, send again after that many seconds with the same idempotency key.