profileReference vs esimReference vs ICCID: which identifier when?
Three identifiers, three meanings, frequently confused. Here is which one to use where.
profileReference vs esimReference vs ICCID: which identifier when?
When you provision an eSIM via the Firsty API, you get back multiple identifiers:
json{ "data": { "profileReference": "1234567890123456", "esimReference": "123456000000000001", "iccid": "89012345678901234567", "activationCode": "LPA:1$provider.example.com$ABC123" } }
When you attach a package, you get another one:
packageReferenceThe most common question we get from new integrators: which one do I store? Which one do I pass back when I want to look up this SIM?
Here's the answer, plus the practical implications of using each.
What each identifier means
profileReference (16-digit numeric): identifies an eSIM profile slot. Think of this as the "account" the eSIM belongs to. One profile can hold multiple eSIMs across its lifecycle.
esimReference (18-digit opaque): identifies a specific eSIM instance within a profile. This is what you'll reference in most API calls.
ICCID (19-20 digit numeric): the SIM's globally unique serial number, defined by GSMA. Every SIM card in the world has an ICCID. It's the "MAC address" of a SIM.
packageReference (23-character opaque, e.g.
C123456XYZDUSR_A1B2C3D4planReference (14-character opaque, e.g.
C123456XYZDUSRThe hierarchy: a profile contains eSIMs. Each eSIM has an ICCID. Each eSIM can have multiple packages over time. Each package is an instance of a plan from the catalog.
When to use each
Try it yourself
Free sandbox. Real Tier-1 carriers. 60 seconds from signup to credentials.
Get started →For API calls about a specific eSIM: use
profileReferenceesimReferencejavascriptawait axios.get(`/profiles/${profileRef}/esims/${esimRef}`); await axios.post(`/profiles/${profileRef}/esims/${esimRef}/packages`, { planReference: 'C123456XYZDUSR' });
Most operations on an eSIM need both. Store both.
For API calls about a specific package: use
packageReferencejavascriptawait axios.get(`/profiles/${profileRef}/esims/${esimRef}/packages/${packageRef}`); await axios.delete(`/profiles/${profileRef}/esims/${esimRef}/packages/${packageRef}`);
For displaying to the customer: use
iccidCustomers occasionally need to give their ICCID to support. It's the identifier that's also visible on their phone (Settings, General, About, ICCID). It's the universal identifier across the telecom world.
Don't show profileReference, esimReference, or packageReference to customers. These are internal to our API.
For your database primary keys: use your own UUIDs, not Firsty's identifiers.
sqlCREATE TABLE customer_sims ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), customer_id UUID REFERENCES customers(id), profile_reference TEXT NOT NULL, esim_reference TEXT NOT NULL, iccid TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() );
Your UUID is your primary key. The Firsty identifiers are foreign keys to our system.
Treat identifiers as opaque
This is in our docs but worth repeating: treat all identifiers as opaque strings. Do not parse, decode, or make assumptions about their internal structure. The format may change without notice, and any code that depends on parsing identifiers will break.
For example, packageReference looks like
C123456XYZDUSR_A1B2C3D4Why so many identifiers?
You might reasonably ask why we have so many. The reason is:
- ICCID is a GSMA standard. Every SIM has one. We can't change this format.
- profileReference corresponds to a profile slot on the carrier side. One profile can have multiple eSIMs over time.
- esimReference identifies a specific eSIM within a profile.
- packageReference identifies a specific package instance. Each eSIM can have multiple packages.
- planReference identifies a plan in the catalog (the blueprint, not the instance).
In simpler eSIM APIs, you might see only ICCID. Those APIs don't support multiple packages per eSIM or eSIM replacement. Once an ICCID is provisioned with a plan, that's it.
In our model, you can suspend an eSIM, order new packages, terminate and replace. The customer's "account" (profile) persists across all of this.
Lookup patterns
Customer reports a problem with their eSIM: search by ICCID first. Customers can find this in their phone settings; you can find it in their order in your database.
sqlSELECT * FROM customer_sims WHERE iccid = $1;
From that row, you have the profileReference and esimReference for API calls.
Internal admin lookup: use your own UUID. Faster index, predictable format.
API operations on an eSIM: profileReference + esimReference together.
API operations on a package: packageReference (plus profile/esim references for routing).
Quick reference
| Identifier | Length | Format | Use for |
|---|---|---|---|
| profileReference | 16 digits | Numeric | API calls about the profile |
| esimReference | 18 digits | Opaque numeric | API calls about a specific eSIM |
| ICCID | 19-20 digits | Numeric | Display to customer, support lookups |
| packageReference | 23 chars | Opaque alphanumeric | API calls about a specific package |
| planReference | 14 chars | Opaque alphanumeric | Reference plans from the catalog |
Store the first three when you provision an eSIM. Store packageReference when you attach a package. Treat them all as opaque. Reference them appropriately.
Related guides
How to provision your first eSIM via API in 30 minutes
From OAuth token to first eSIM activated, with QR code generation server-side. Real code, real credentials, real eSIM, in about 30 minutes.
OAuth2 client credentials in production: what most tutorials get wrong
Token caching, refresh strategy, and the security mistakes we see in production integrations every week.
The LPA activation code format explained
What the LPA format actually is, how to render QR codes from it correctly, and why some QR codes work on iPhone but not Android.