Reference
Models & raw data
Data comes back from the SDK as typed objects: 313 models generated from Adobe's own API specification, plus a handful of hand-written ones for the results you use most. This page explains both, how to reach the raw JSON when you need it, what you can pass into methods, and how the generated code is kept in step with Adobe's spec.
Three kinds of objects
- Generated models
Conceptlz\AdobeSign\Generated\Model\*. One class per schema in Adobe's v6 spec, with getters and setters. Most methods return these. - Result models
Conceptlz\AdobeSign\Model\*. Five hand-written, read-only classes for the most common results, with plain properties and helper methods. - Builders
Conceptlz\AdobeSign\Builder\*. Fluent objects that produce request payloads and validate them before sending. Covered in Creating agreements.
| You call | You get |
|---|---|
agreements()->get($id) | Model\Agreement |
agreements()->all() | a generator of Model\AgreementSummary |
agreements()->sendReminder($id) | Model\ReminderCreationResult |
agreements()->signatureEvidence($id) | Model\SignatureEvidence holding Model\SignerEvidence objects |
agreements()->members($id) | a raw array (the MembersInfo JSON) |
agreements()->events($id), signingUrls($id), documents($id), users()->me(), webhooks()->get($id), … | a generated model |
create() on any resource | the new resource's id, a string |
combinedDocument(), auditTrail(), formData(), … | a PSR-7 StreamInterface |
Generated models
src/Generated/Model holds 313 classes, one for each schema in Adobe's
published OpenAPI document for REST API v6 (shipped in the repository as
spec/adobe-sign-v6.json). They are produced by the Jane code generator and are
never edited by hand: a fix to a model is a fix to the spec normaliser followed by a
regeneration, and CI fails if the generated code and the spec drift apart.
Every generated model follows the same pattern:
- One
protectedproperty per JSON key, with the same name. - A getter and a fluent setter for each:
getStatus()/setStatus(). - Everything is nullable, because Adobe may leave any field out.
isInitialized('status')tells you whether a field was set, as opposed to set tonull.- Status and enum values are plain strings. Adobe adds values without changing the API version, so a PHP enum would break the day a new one appears.
- No runtime dependencies: plain PHP classes you can construct with
new.
Reading them
$me = $sign->users()->me(); // Generated\Model\DetailedUserInfo
echo $me->getEmail(), ' in account ', $me->getAccountId();
$events = $sign->agreements()->events($agreementId); // Generated\Model\AgreementEventList
foreach ($events->getEvents() ?? [] as $event) { // list of Generated\Model\AgreementEvent
printf("%s %s %s\n", $event->getDate(), $event->getType(), $event->getParticipantEmail());
}
Lists are typed too: getEvents() returns AgreementEvent objects, not arrays.
Remember the ?? []: a getter returns null when Adobe left the field out.
Building and changing them
Use generated models when you need a field the builders don't expose, or when updating a resource: fetch it, change it, send it back.
// Update: Adobe expects the full object, so start from what get() returned
$details = $sign->agreements()->get($agreementId)->details(); // FetchedAgreementInfo
$details->setMessage('Please sign by Friday.');
$sign->agreements()->update($agreementId, $details); // If-Match added automatically
// Create: start from the builder, then set what it doesn't cover
$model = AgreementInfo::create('NDA')->file($documentId)->signers('jane@example.com')->toModel();
$model->setFirstReminderDelay(24);
$sign->agreements()->create($model);
Two classes called AgreementInfo, two called WebhookInfo
Builder\AgreementInfo and Builder\WebhookInfo are the fluent builders.
Generated\Model\AgreementInfo (what agreements()->update() accepts besides
FetchedAgreementInfo) and Generated\Model\WebhookInfo (what
webhooks()->get() returns) are the spec's models. Alias one of each pair in your
use statements if a file needs both, as the SDK itself does.
ModelMapper
Conceptlz\AdobeSign\Hydration\ModelMapper converts between decoded JSON arrays and generated
models. The SDK uses it for every typed response, and it is public so you can do the same with data you
already hold, such as a response you stored, a webhook payload or a fixture.
ModelMapper::hydrate(string $class, array $data): object
Builds a generated model from an array, recursing into nested models and lists of models.
ModelMapper::toArray(object $model): array
Serialises a model back to an API array. Only fields that were set are included, and null values are dropped.
use Conceptlz\AdobeSign\Generated\Model\FetchedAgreementInfo;
use Conceptlz\AdobeSign\Hydration\ModelMapper;
$stored = json_decode($row->payload, true);
$info = ModelMapper::hydrate(FetchedAgreementInfo::class, $stored);
echo $info->getStatus();
$array = ModelMapper::toArray($info); // back to the shape Adobe sends and expects
It is deliberately forgiving, because Adobe's real responses don't always match the spec:
- Unknown keys are ignored. A field Adobe adds tomorrow doesn't break hydration.
- Mistyped values are skipped, not fatal: an object where a string was expected leaves that property unset.
- Loose scalars are coerced:
"42"becomes42for an integer field,"true"or"0"become booleans. - Only generated models are accepted. Any class outside
ModelMapper::MODEL_NAMESPACEthrowsInvalidArgumentException.
The class metadata is read by reflection once per class and cached for the life of the process, so hydrating thousands of list items stays cheap.
Hand-written result models
Five classes in Conceptlz\AdobeSign\Model are written by hand. They are
final, their properties are public readonly, and they exist where a typed
property is much nicer to use than a getter on a large generated model, or where the SDK combines several
responses.
Agreement
Returned by agreements()->get().
| Member | Type | Notes |
|---|---|---|
$id, $name | string | "" if missing |
$status | string | OUT_FOR_SIGNATURE, SIGNED, CANCELLED, …; compare as a string |
$signatureType | ?string | ESIGN or WRITTEN |
$senderEmail | ?string | |
$createdDate, $expirationTime | ?string | ISO 8601, exactly as Adobe sent it |
$etag | ?string | The response's ETag; also replayed automatically as If-Match |
$raw | array | The decoded response body, unmodified |
details() | FetchedAgreementInfo | The full response as the generated model |
isSigned(), isOutForSignature() | bool | Status shortcuts |
AgreementSummary
Yielded by agreements()->all(), one per row of GET /agreements. Properties:
$id, $name, $status (relative to the calling user, e.g.
WAITING_FOR_MY_SIGNATURE), $type, $groupId,
$displayDate, $hidden and $raw. details() returns the
generated AgreementAgreement model for the row.
ReminderCreationResult
Returned by sendReminder() on agreements and bulk sends: $id (the reminder's
id, nullable) and $disabledRecipients, the participants who won't receive it, for example
because they turned reminder emails off.
SignatureEvidence and SignerEvidence
Built by agreements()->signatureEvidence(), which combines the agreement's members and
events into a per-signer record of what was actually reached. SignatureEvidence has
$agreementId, $status and $signers, plus allSigned(),
meets($level, $qualifiedIssuers), shortfalls($level, $qualifiedIssuers) and
lowestLevel($qualifiedIssuers).
SignerEvidence member | Type | Meaning |
|---|---|---|
$email, $name | string, ?string | Who signed |
$order | int | Step in the signing order |
$signed, $signedAt | bool, ?string | Whether and when (ISO 8601) |
$signatureInputType | ?string | As Adobe reports it: TYPED, SCRIBBLE (drawn), … |
$digitalSignatureRequired | bool | AES or QES was requested for this signer |
$digitallySigned | bool | A certificate-based signature was actually applied |
$certificateIssuer, $signingProvider | ?string | The certificate's issuer and the cloud signature provider, for digital signatures |
$verification | string | Identity check before signing: NONE, EMAIL_OTP, PHONE, KBA, GOV_ID, DIG_ID, … |
achievedLevel($qualifiedIssuers) | ?SignatureLevel | SES, AES, or QES when the issuer is in your list; null if not signed |
issuedByAnyOf($issuers) | bool | Case-insensitive substring match against the issuer |
How these are used to prove a signature level is covered in Signature levels.
The $raw escape hatch
Agreement and AgreementSummary keep the complete decoded JSON in
$raw, untouched. Use it for a field the typed properties don't cover, or one Adobe added after
this release, without waiting for an SDK update:
$agreement = $sign->agreements()->get($agreementId);
$agreement->status; // typed, the common case
$agreement->raw['locale'] ?? null; // anything else in the response
$agreement->raw['participantSetsInfo'][0]['memberInfos'][0]['email'] ?? null;
$agreement->details()->getVaultingInfo(); // or the full generated model
The other three result models are derived values with no single response behind them, so they have no
$raw. Webhook events have one too: $event->raw holds the notification
exactly as Adobe posted it (see Webhooks).
Methods that return arrays
agreements()->members() returns the decoded MembersInfo array rather than a
model. The live API's member data differs from the spec in places (it omits
nextParticipantSets unless asked, for example), and an array lets you read what actually
arrived. Hydrate it yourself if you prefer a model:
use Conceptlz\AdobeSign\Generated\Model\MembersInfo;
$members = ModelMapper::hydrate(MembersInfo::class, $sign->agreements()->members($agreementId, includeNextParticipantSet: true));
Passing data in: arrays, models or builders
Methods that send a payload accept a generated model or a plain array, and some also accept a builder. The rule is the same everywhere:
| You pass | What is sent |
|---|---|
| An array | Exactly that array, JSON-encoded |
| A generated model | ModelMapper::toArray($model): only the fields you set |
A builder (AgreementInfo, WebhookInfo) | Its toArray(), after local validation |
| Any other object | Nothing: InvalidArgumentException before sending |
| Method | Accepts |
|---|---|
agreements()->create() | AgreementInfo|AgreementCreationInfo|array |
agreements()->update() | Generated\Model\AgreementInfo|FetchedAgreementInfo|array |
webhooks()->create(), update() | Builder\WebhookInfo|Generated\Model\WebhookInfo|array |
libraryDocuments()->create() | LibraryDocumentCreationInfoV6|array |
widgets()->create() | WidgetCreationInfoV6|array |
megaSigns()->create() | MegaSignInfo|array |
users()->create(), update() | DetailedUserInfo|array |
Which to choose:
- Builders for agreements and webhooks. They check signing orders, emails and signature
levels before anything is sent, and only they apply
ClientOptions::$defaultSignatureLevel. - Generated models for updates (start from what
get()returned) and for fields the builders don't cover. - Arrays when you already have the JSON, for example from a template stored in your database. They are sent as written, with no checks and no default signature level.
The builders also have a with() method that merges raw spec fields into what they produce,
which is often simpler than switching to a model:
AgreementInfo::create('NDA')
->file($documentId)
->signers('jane@example.com')
->with(['emailOption' => ['sendOptions' => ['initEmails' => 'NONE']]]);
Why there is no symfony/serializer
Jane normally generates normalizer classes alongside the models, and those depend on
symfony/serializer and Jane's runtime package. The SDK deletes them after generation and
uses its own small ModelMapper instead. The reasons:
- The core depends only on PSR interfaces. A Serializer dependency would pin a Symfony major version on every project that installs the SDK, including projects that already use a different one. An architecture test fails the build if a non-PSR runtime dependency creeps in.
- Leniency is a feature. The real API sends fields and values the spec doesn't list.
ModelMapperskips what doesn't fit instead of throwing, which is what a client of a changing API needs. - It is small and pinned down by tests.
GeneratedModelsContractTestchecks that every generated model follows the accessor convention the mapper relies on, and that generated code has no runtime dependencies.
Regenerating the models
You never need to do this to use the SDK. It is for contributors updating to a newer copy of Adobe's spec, or fixing a spec problem. Everything runs from the root of the development monorepo:
- Install the generator
Jane lives in an isolated tool directory, so it is never a dependency of any package.
composer --working-dir=tools/jane install - Update or fix the spec
Replace
packages/adobe-sign/spec/adobe-sign-v6.jsonwith Adobe's newer version, or fix a quirk inpackages/adobe-sign/spec/normalize.php. The same normaliser feeds the generator and the test suite's spec validator, so both read the spec identically. - Generate
This normalises the spec, runs Jane, and deletes the normalizer and runtime classes so only plain models remain.
composer generate - Run the suites
Both test suites and PHPStan must pass. CI also fails if
src/Generatedis out of sync with the spec.composer test composer analyse
Never patch a generated file
A hand edit in src/Generated is lost on the next run and breaks the sync check. If a
generated model is wrong, the fix belongs in spec/normalize.php; if the live API disagrees
with the spec, the SDK accepts both shapes in hand-written code and the difference is recorded with a
regression test.
Common questions
Why does a getter return null when the agreement clearly has that value?
Either the endpoint you called doesn't include that field, or the value had a type the spec doesn't
allow and ModelMapper skipped it. Check $raw on the result model, or the
response body in a logger middleware, to see what Adobe actually sent.
How do I store a model as JSON?
json_encode(ModelMapper::toArray($model)), and ModelMapper::hydrate() to read
it back. For Agreement, store $agreement->raw and rebuild with
Agreement::fromArray().
Why are statuses strings and not enums?
Adobe's spec warns that new values can appear without a new API version. A string keeps working when that happens; an enum would throw. Compare with string literals or your own constants.
How do I find which generated model a method uses?
Look at the method's return type in your IDE, or its docblock, whose first line names the endpoint.
The model names match the schema names in Adobe's API reference, so FetchedAgreementInfo
there is FetchedAgreementInfo here.
Can I extend a result model with my own methods?
The hand-written models are final. Wrap them in your own class, or work from
$raw or details(). Keeping them final lets the SDK add properties in minor
releases without breaking subclasses.