Skip to content
← BlogDeveloperAnalysis

Why Norbelys's API uses givenName, not first_name

Norbelys's API uses Schema.org fields — givenName, affiliation, sameAs — instead of bespoke names like first_name. The reasoning, plus the mapping table.

By Gabriel Lara, Developer Relations, Norbelys

Founder-reviewed ·How we research and correct articles

Open the response from GET /v1/people/:id and you won’t see first_name, company, or linkedin_url. You’ll see givenName, affiliation, and sameAs. That’s not an aesthetic choice or a nod to some internal style guide — it’s Schema.org, the vocabulary schema.org’s Person and Organization types already define, reused instead of reinvented. The reasoning behind it is worth understanding, because it explains a design decision that shows up in every response the API returns.

The problem with inventing your own names

Every API that models a person ends up needing a name for “the person’s first name.” Most invent one: first_name, firstName, fname, given_name. All four exist across real production APIs today, and none of them is wrong — they’re just arbitrary, which means every integration has to look them up. That’s a cost paid once per API, forever, by every developer and every tool that touches it, for a decision that had no reason to be made fresh in the first place.

Schema.org already solved this. It’s the vocabulary search engines, structured-data tooling, and a large share of the web’s own JSON-LD already speak — Person.givenName, Person.familyName, Organization.name, Thing.sameAs. It’s not a cold-email-specific vocabulary; it’s a general one that happens to cover exactly the entities a CRM-shaped API needs to name: people, organizations, addresses, URLs. Reusing it means the names on the wire are recognizable before you’ve read a single line of documentation, because they’re the same names schema.org, and by extension a large amount of existing tooling, already treats as standard.

curl https://api.norbelys.com/v1/people/con_8h3k \
  -H "Authorization: Bearer ak_live_..."
{
  "id": "con_8h3k",
  "email": "jane@acme.com",
  "givenName": "Jane",
  "familyName": "Doe",
  "jobTitle": "VP Marketing",
  "affiliation": "Acme Corp",
  "url": "https://acme.com",
  "sameAs": ["https://www.linkedin.com/in/janedoe"],
  "dateCreated": "2026-05-11T14:02:00Z"
}

Every one of those field names — givenName, jobTitle, affiliation, sameAs, dateCreated — is a real Schema.org property. None of it is Norbelys-invented.

Why this matters more with AI clients in the loop

The “already invented” argument gets stronger, not weaker, once you consider who else is reading these field names. A large language model has seen schema.org/Person and schema.org/Organization millions of times in its training data — in JSON-LD embedded across the web, in documentation, in other APIs that made the same choice. An AI agent driving Norbelys through the MCP server or generated SDKs is far more likely to correctly guess that affiliation means “company” than it is to guess that a bespoke company_name field does, because affiliation is vocabulary it’s already seen attached to exactly this meaning, thousands of times over. That’s the “AI-first” half of developer-first-and-AI-first: pick names a model already understands instead of ones it has to learn from your docs alone.

The full mapping

The person resource is the clearest example, and it’s also where this mattered enough to become a real migration: the resource used to be called contact, with firstName/lastName/phone fields, and both the API and the underlying Postgres columns were renamed to match Schema.org — not translated by a serializer, physically renamed in the database. There’s no wrapper layer converting first_name to givenName on the way out; the column is named given_name and the wire field is givenName. The mapping below is what that rename produced, and it’s the same mapping applied everywhere a comparable concept appears in the API:

DB column API field (Schema.org) Type
given_name givenName Person
family_name familyName Person
email email Person
telephone telephone Person
title jobTitle Person
company_name affiliation (or worksFor.name) Organization
website url Thing
linkedin sameAs Thing
location address PostalAddress
custom_fields additionalProperty PropertyValue[]
created_at dateCreated
updated_at dateModified

additionalProperty is worth calling out specifically — it’s Schema.org’s own mechanism for “a named value that doesn’t fit the standard vocabulary,” which is exactly what a custom field is. Rather than inventing yet another bespoke shape for user-defined fields, the API reuses the vocabulary’s own extension point.

Where Schema.org doesn’t reach

Not everything in a cold-email platform has a Schema.org equivalent, and the rule isn’t to force one where it doesn’t fit. Fields with no real counterpart — status, source, dailyLimit, and similar Norbelys-specific concepts — stay as descriptive lowerCamelCase names, exactly as bespoke as they’d be in any other API. The rule is narrower and more useful than “always use Schema.org”: use it where it already exists, and don’t invent around it where it doesn’t. That keeps the vocabulary honest instead of stretching it to cover things it was never meant to describe.

No wrapper layer, on purpose

It’s worth being precise about what “the DB column names already equal the wire names” actually means, because it’s a stronger claim than most APIs can make. In a typical setup, an API field like givenName is produced by a serializer that reads a differently-named database column and renames it on the way out — a translation layer that has to be kept correct forever, usually in a file nobody enjoys touching. After the migration that renamed the contact resource to person, that translation layer doesn’t exist for these fields at all. The Postgres column is literally given_name; the API field is givenName; the difference between them is snake_case versus camelCase, nothing more. There’s no intermediate mapping to get wrong, because the database and the API are describing the same fact using the same name, just formatted for their respective conventions.

That matters for correctness, not just cleanliness. A wrapper layer is a place bugs hide — a rename on one side that doesn’t get mirrored on the other, a field that’s present in the database but silently dropped by the serializer. Removing the layer removes the class of bug along with it. It’s the same “fewest moving parts” instinct that puts a plain Idempotency-Key header directly on state-changing requests instead of asking integrators to build their own out-of-band deduplication — solve the problem once, at the source, rather than papering over it downstream.

The resource rename that made this literal

The person resource is also a useful case study in what it costs to take Schema.org naming seriously instead of half-adopting it. It would have been easy to keep the database as contact/first_name/last_name and just rename the fields in an API response layer — cheaper, lower-risk, shippable in an afternoon. Norbelys did the more expensive thing instead: an actual schema migration that renamed the table and its columns, so contact became person, first_name became given_name, last_name became family_name, phone became telephone, and import_batch_id became import_id. The ID prefix (con_…) stayed the same on purpose — it’s an opaque identifier, not a name, so there was nothing to correct there, and changing it would have broken every existing reference for no benefit.

The IDs are unaffected, but every other name in the resource now matches Schema.org from the database up, which is the version of this decision that actually pays off — a half-measure translation layer would have left the “no wrapper” claim false the moment anyone looked at the schema underneath.

What this buys you as an integrator

In practice this means you can often guess a field name correctly before checking the OpenAPI spec, and when you do check it, the name will already make sense rather than needing a lookup table of your own. It’s the same principle behind the five endpoint patterns the whole API follows — fewer arbitrary decisions on the wire means less the API asks you to memorize and more it lets you predict. A Person is the same Person whether Schema.org, Norbelys, or the LLM reading your integration code is the one looking at it.

Frequently asked questions

Why does the person resource still use the con_ ID prefix if it's no longer called contact?

IDs are opaque identifiers, not names — they're prefixed cuid2 values used to identify a record type in logs and URLs, not to describe what the resource is called in the API. The rename changed every field name to Schema.org vocabulary; it left the con_ prefix alone because there was nothing wrong with it.

Do all Norbelys resources use Schema.org names?

Only where a genuine equivalent exists, which is mostly the person and organization-shaped fields. Domain-specific concepts like sending status, daily limits, or campaign state have no Schema.org counterpart and use descriptive camelCase names instead, same as any other API.

What is additionalProperty for?

It's Schema.org's own extension mechanism for values that don't map to a standard property. Norbelys uses it to represent custom fields you define on a person, so user-defined data has a real Schema.org shape instead of an ad hoc key-value bag.